Використовуйте команду 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

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()функцією, другий аргумент якої є рядком, що містить усіх символів, з яких ви хочете вийти / захистити .
saginaw

Однак у попередній команді я забув захистити також пряму косу рису, тому що вона також має особливе значення для глобальної команди, це роздільник візерунка. Отже, правильна команда, ймовірно, буде щось на кшталт: :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.

EDIT

І більш простий спосіб, який уникає спеціальних символів для створення шаблону пошуку (з 'nomagic'набором), будує :globalкоманду, а потім виконує її:

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

Ти не можеш це зробити як однолінійний, оскільки у вас було вкладене :global, що заборонено.


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

Це має бути здійснено з підходом, аналогічним моїй відповіді. Я не думаю, що це буде однолінійний лайнер!
Антоній
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.