Используйте ex-команду, чтобы проверить, идентичны ли две строки?

9

Я смотрел на этот вопрос, а потом удивлялся, как мне реализовать свой ответ, использующий sed исключительно POSIX ex .

Хитрость заключается в том, что хотя sedя могу сравнить пространство удержания с пространством шаблона, чтобы увидеть, являются ли они в точности эквивалентными (с G;/^\(.*\)\n\1$/{do something}), я не знаю способа выполнить такой тест ex.

Я знаю, что в Vim я мог бы Yприкрепить первую строку, а затем набрать, :2,$g/<C-r>0/dчтобы почти выполнить то, что я указываю, но если первая строка содержит что-либо, кроме очень простого буквенно-цифрового текста, это действительно становится случайным, поскольку строка выводится как регулярное выражение , а не просто строка для сравнения. (И если первая строка содержит косую черту, остальная часть будет интерпретирована как команда!)

Поэтому, если я хочу удалить все строки в myfileэтой строке , идентичные первой строке, но не удалить первую строку, как я могу это сделать, используя ex? В этом отношении, как я мог сделать это, используя vi?

Есть ли способ POSIX удалить строку, если она точно совпадает с другой строкой?

Возможно, что-то вроде этого воображаемого синтаксиса:

:2,$g/**lines equal to "0**/d
Wildcard
источник
3
Вы можете создать команду, но для этого потребуется немного vimscript и, вероятно, это не будет способ POSIX::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw
1
@saginaw, спасибо. До сих пор единственный подход POSIX, который мне приходил в голову, - это просто использовать sedв качестве фильтра изнутри exи запустить весь мой sedответ по всему буферу ... который , конечно, будет работать (и на самом деле переносим в отличие от sed -i).
Wildcard
Вы правы, и я считаю ваш первоначальный подход <C-r>0очень хорошим. Я не уверен, что вы могли бы добиться большего успеха только с помощью команд Ex, потому что вы должны защищать специальные символы. Без ограничения, совместимого с POSIX, я думаю, что вы бы использовали очень номагический переключатель, \Vа затем защитили бы обратную косую черту (потому что она сохраняет свое особое значение даже с помощью \V) с помощью escape()функции, второй аргумент которой является строкой, содержащей все символы, которые вы хотите экранировать / защитить ,
Сагино
Однако в предыдущей команде я также забыл защитить косую черту, поскольку она также имеет особое значение для глобальной команды - это разделитель шаблонов. Поэтому правильная команда, вероятно, будет выглядеть примерно так: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'Или вы можете использовать другой символ для разделителя шаблонов, например точку с запятой. В этом случае вам не нужно защищать косую черту в шаблоне. Было бы что-то вроде::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
saginaw
1
Я считаю, что ваш второй подход sedтоже очень хорош. С Vim вы часто делегируете определенные специальные задачи другим программам, и sed, вероятно, хороший пример этого. Кстати, вам не нужно запускать sedвесь буфер. Если вы хотите запустить его только на части буфера, вы можете указать диапазон. Например, если вы хотите , чтобы отфильтровать только строки между 50 и 100, вы можете набрать: :50,100!<your sed command>.
saginaw

Ответы:

3

напор

В Vim вы можете сопоставить любой символ, включая перевод строки \_.. Вы можете использовать это для построения шаблона, который соответствует целой строке, любому количеству материала, а затем той же строке:

/\(^.*$\)\_.*\n\1$/

Теперь вы хотите удалить все строки в файле, которые соответствуют первому, кроме первого. Подстановка для удаления последней строки, которая соответствует первой:

:1 s/\(^.*$\)\_.*\zs\n\1$//

Вы можете использовать, :globalчтобы убедиться, что подстановка повторяется достаточно раз, чтобы удалить все строки:

:g/^/ 1s/\(^.*$\)\_.*\zs\n\1$//

POSIX ex

@saginaw демонстрирует изящный способ сделать это в Vim в комментарии к вашему вопросу, но мы можем адаптировать вышеописанную технику для POSIX ex.

Чтобы сделать это POSIX-совместимым способом, вам нужно запретить многострочное сопоставление, но вы все равно можете использовать обратные ссылки. Это требует дополнительной работы:

:g/^/ t- | s/^/@@@/ | 1t- | s/^/"/ | j! | s/^"\(.*\)@@@\1$/d/ | d x | @x

Вот разбивка:

:g/^/                   for each line

t- |                    copy it above

s/^/@@@/ |              prefix it with something unique (@@@)
                        (do a search in the buffer first to make
                        sure it really is unique)

1t- |                   copy the first line above this one

s/^/"/ |                prefix with "

j! |                    join those two lines (no spaces)

s/^"\(.*\)@@@\1$/d/ |   if the part after the " and before the @@@
                        matches the part after the @@@, replace the line
                        with d

d x |                   delete the line into register x

@x                      execute it

Таким образом, если текущая строка является дубликатом строки 1, регистр x будет содержать d. Выполнение этого удалит текущую строку. Если он не является дубликатом, он будет содержать бессмысленный префикс, с "которым при выполнении будет запрещен, так как " начинается комментарий. Я не знаю, если это самый лучший способ сделать это, это только первое, что пришло в голову!

Просто так получилось, что первую строку удалить нельзя, потому что процесс копирования временно меняет, что такое строка 1. Если бы это было не так, вы можете вместо этого поставить префикс :gс 2,$диапазоном.

Протестировано в Vim и ex-vi версии 4.0.

РЕДАКТИРОВАТЬ

И более простой способ, который экранирует специальные символы для создания шаблона поиска (с помощью 'nomagic'set), создает :globalкоманду, а затем выполняет ее:

:set nomagic
:1t1 | .g/^/ s#\[$^\/]#\\\&#g | s#\.\*#2,$g/^\&$/d# | d x
:@x
:set magic

Вы не можете сделать это как одну строку, так как у вас будет вложенный :global, что не разрешено.

Antony
источник
2

Казалось бы, единственный способ сделать это в POSIX - это использовать внешний фильтр, например sed.

Например, чтобы удалить 17-ю строку вашего файла, только если она в точности совпадает с 5-й строкой, и в противном случае оставить ее без изменений, вы можете сделать следующее:

:1,17!sed '5h;17{G;/^\(.*\)\n\1$/d;s/\n.*$//;}'

(Вы можете запустить sedвесь буфер здесь, или вы можете запустить его только в строках 5-17, но в первом случае вы выполняете ненужную фильтрацию - ничего страшного - и в последнем случае вам придется использовать цифры 1 и 13 в вашей sedкоманде вместо 5 и 17. сбивает с толку.)

Поскольку sedвыполняется только один проход вперед, простого способа сделать обратный ход и удалить 5-ю строку нет, только если она идентична 17-й строке. Некоторое время я пытался из любопытства ... это сложно .


Прорыв - Вы можете сделать это так:

:17t 5
:5,5+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Это на самом деле более общий метод. Он также может быть использован для получения того же результата, что и первая команда (и удаление 17-й строки, только если она идентична 5-й строке), например так:

:5t 17
:17,17+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Для более широкого использования, такого как удаление всех строк файла, идентичных строке 37, при этом оставляя строку 37 без изменений, вы можете сделать следующее:

:37,$!sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//'
:37t 0
:1,37!sed '1{h;d;};G;/^\(.*\)\n\1$/d;s/\n.*$//'

Вывод здесь заключается в том, что для проверки, идентичны ли две строки, лучший инструмент - sed нет ex. Но, как отметил DevSolar в комментарии , это не является ошибкойvi или ex- они предназначены для работы с инструментами Unix; это главная сила.

Wildcard
источник
Гораздо сложнее: вставить строку в конец файла, только если строка не существует где-то в файле.
Wildcard
Это должно быть выполнимо с подходом, подобным моему ответу. Я не думаю, что это будет однострочник, хотя!
Энтони