Удалить все последовательные дубликаты

13

У меня есть файл, который выглядит следующим образом.

Move to 230.00
Hold
Hold
Hold
Hold
Hold
Hold
Move to 00.00
Hold 
Hold 
Hold 
Hold 
Hold 
FooBar
Hold 
Spam
Hold

Я хотел бы, чтобы это выглядело так:

Move to 230.00
Hold
Move to 00.00
Hold 
FooBar
Hold
Spam
Hold

Я уверен, что должен быть способ, которым Vim мог бы быстро это сделать, но я не могу понять, как это сделать. Это вне возможностей макросов и нуждается в vimscript?

Кроме того, все в порядке, если мне нужно применить один и тот же макрос к каждому блоку «Holds». Это не должен быть единственный макрос, который получает весь файл, хотя это было бы здорово.

Джеймс
источник

Ответы:

13

Я думаю, что следующая команда должна работать:

 :%s/^\(.*\)\(\n\1\)\+$/\1/

Пояснение:

Мы используем команду замещения для всего файла, чтобы изменить patternна string:

:%s/pattern/string/

Вот patternэто ^\(.*\)\(\n\1\)\+$и stringесть \1.

pattern может быть разбито так:

^\(subpattern1\)\(subpattern2\)\+$

^и $сопоставьте соответственно начало строки и конец строки.

\(и \)используются для включения, subpattern1чтобы мы могли ссылаться на него позже по специальному номеру \1.
Они также используются для включения, subpattern2чтобы мы могли повторить это 1 или более раз с квантификатором \+.

subpattern1is .*
.- это метасимвол, соответствующий любому символу, кроме новой строки, и *квантификатор, соответствующий последнему символу 0, 1 или более раз.
Таким образом, .*соответствует любой текст, не содержащий новой строки.

subpattern2is \n\1
\nсоответствует новой строке и \1соответствует тому же тексту, который был найден внутри первой \(, \)что здесь subpattern1.

Таким образом, patternможно прочитать так:
начало строки ( ^), за которым следует любой текст, не содержащий новой строки ( .*), за которым следует новая строка ( \n), затем тот же текст ( \1), причем последние два повторяются один или несколько раз ( \+), и наконец конец строки ( $) .

Везде, где patternсопоставляется (блок идентичных строк), команда подстановки заменяет его тем, stringчто здесь \1(первая строка блока).

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

:%s/^\(.*\)\(\n\1\)\+$/\1/n

Для более детального контроля вы также можете запросить подтверждение перед изменением каждого блока строк, добавив cвместо этого флаг подстановки:

:%s/^\(.*\)\(\n\1\)\+$/\1/c

Для получения дополнительной информации о команде чтении подстановки :help :s,
для замещения флагов :help s_flags,
для различных метасимволов и кванторы чтения :help pattern-atoms, так
и для регулярных выражения в Vim прочитать это .

Изменить: Подстановочный знак исправил проблему в команде, добавив $в конце pattern.

Также у BloodGain есть более короткая и читаемая версия этой же команды.

Сагино
источник
1
Ницца; ваша команда нуждается $в этом, хотя. В противном случае он будет выполнять неожиданные действия со строкой, которая начинается с идентичного текста предыдущей строки, но содержит некоторые другие завершающие символы. Также обратите внимание, что основная команда, которую вы дали, функционально эквивалентна моему ответу :%!uniq, но флаги выделения и подтверждения хороши.
Подстановочный
Вы правы, я только что проверил, и если одна из повторяющихся строк содержит другой завершающий символ, команда не работает, как ожидалось. Я не знаю, как это исправить, атом \nсоответствует концу строки и должен это предотвратить, но это не так. Я попытался добавить $сразу после .*безуспешно. Я собираюсь попытаться исправить это, но если я не могу, возможно, я удалю свой ответ или добавлю предупреждение в конце. Спасибо за указание на эту проблему.
saginaw
1
Попробуйте:%s/^\(.*\)\(\n\1\)\+$/\1/
Wildcard
1
Вы должны учитывать, что $соответствует концу строки , а не концу строки. Технически это неверно, но когда вы ставите символы после него, кроме нескольких исключений, он соответствует литералу, $а не чему-то особенному. Таким образом, использование \nлучше для многострочных матчей. (Смотрите :help /$)
Wildcard
Я думаю, что вы правы в том, что \nможете использовать в любом месте внутри регулярного выражения, тогда как, $вероятно, следует использовать только в конце. Просто чтобы сделать разницу между ними, я отредактировал ответ, написав, что \nсоответствует новой строке (что инстинктивно заставляет вас думать, что после текста еще есть текст), тогда как $соответствует концу строки (что заставляет вас думать, что ничего нет осталось).
saginaw
10

Попробуйте следующее:

:%s;\v^(.*)(\n\1)+$;\1;

Как и в случае ответа Сагино , здесь используется команда Vim: substitute. Однако для улучшения читабельности используются несколько дополнительных функций:

  1. Vim позволяет нам использовать любой не алфавитно-цифровой символ ASCII, кроме обратной косой черты ( \ ), двойной кавычки ( " ) или трубы ( | ), чтобы разделить наш текст соответствия / замены / флагов. Здесь я выбрал точку с запятой ( ; ), но вы можете выбрать другой.
  2. Vim предоставляет «волшебные» настройки для регулярных выражений, так что символы интерпретируются для их специальных значений вместо необходимости экранирования от обратной косой черты. Это полезно для уменьшения многословия и потому, что оно более последовательное, чем «номагическое» значение по умолчанию. Начиная с \vозначает «очень волшебный», или все символы, кроме буквенно-цифровых ( A-z0-9 ) и подчеркивания ( _ ), имеют особое значение.

Значение компонентов:

% за весь файл

с заменой

; начать замещающую строку

\ v "очень волшебно"

^ начало строки

(. *) 0 или более любого характера (группа 1)

(\ n \ 1) + новая строка, за которой следует (текст совпадения группы 1), 1 или более раз (группа 2)

$ конец строки (или в этом случае подумайте, что следующий символ должен быть символом новой строки )

; начать заменить строку

\ 1 текст совпадения группы 1

; флаги конца команды или начала

Bloodgain
источник
1
Мне очень нравится ваш ответ, потому что он более читабелен, а также потому, что он помог мне лучше понять разницу между \nи $. \nдобавляет что-то к шаблону: символьная новая строка, которая сообщает vim, что следующий текст находится на новой строке. Принимая во внимание, что $ничего не добавляет к шаблону, оно просто запрещает сопоставление, если следующий символ вне шаблона не является новой строкой. По крайней мере, это то, что я понял, прочитав ваш ответ и :help zero-width.
saginaw
И то же самое должно быть верно, поскольку ^это ничего не добавляет к шаблону, оно просто предотвращает сопоставление, если предыдущий символ вне шаблона не является новой строкой ...
saginaw
@saginaw Вы правильно поняли, и это хорошее объяснение. В регулярных выражениях некоторые символы могут быть символами управления . Например, +означает «повторить предыдущее выражение (символ или группу) 1 или более раз», но ничего не соответствует самому себе. В ^означает «не может начаться в середине строки» и $означает «не может заканчиваться в середине строки.» Заметьте, я не сказал "линия", но "строка" там. Vim обрабатывает каждую строку как строку по умолчанию - и вот тут-то и \nприходит. Он говорит Vim использовать новую строку, чтобы попытаться найти совпадение.
Bloodgain
8

Если вы хотите удалить ВСЕ смежные идентичные строки, а не только Hold, вы можете сделать это очень легко с помощью внешнего фильтра изнутри vim:

:%!uniq (в среде Unix).

Если вы хотите сделать это напрямую vim, это на самом деле очень сложно. Я думаю, что есть способ, но для общего случая очень сложно сделать его на 100% функциональным, и я еще не исправил все ошибки.

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

:+,./^[^H]/-d

+Означает строку после текущей строки. . относится к текущей строке. /^[^H]/-Означает линию до ( -) в следующей строке , которая начинается не с H.

Затем я удалю.

Wildcard
источник
3
Хотя замена и глобальные команды Vim являются хорошими упражнениями, вызов uniq(изнутри vim или использование оболочки) - вот как я могу решить эту проблему. Во-первых, я уверен, uniqчто строки, которые будут пустыми / все пробелы будут эквивалентны (не проверял), будут обрабатываться с помощью регулярных выражений. Это также означает не «изобретать велосипед», пока я пытаюсь выполнить работу.
Bloodgain
2
Именно по этой причине я обычно рекомендую Vim и Cygwin для Windows. Vim и shell просто принадлежат друг другу.
DevSolar
2

Ответ на основе Vim:

:%s/\(^.*\n\)\1\{1,}/\1

= Замените каждую строку, за которой следует сама по крайней мере один раз , той же самой строкой.

VanLaser
источник
2

Еще один, предполагая Vim 7.4.218 или позже:

function! s:Uniq(line1, line2)
    let cursor = getcurpos()
    let lines = uniq(getline(a:line1, a:line2))
    if setline(a:line1, lines) == 0 && len(lines) <= a:line2 - a:line1
        silent execute (a:line1 + len(lines)) . ',' . a:line2 . 'd _'
    endif
    call setpos('.', cursor)
endfunction

command! -range=% Uniq call <SID>Uniq(<line1>, <line2>)

Это не обязательно лучше, чем другие решения, хотя.

Сато Кацура
источник
2

Вот решение, основанное на старом (2003) vim (golf) Пребена Гулберга и Пита Дельпорта.

  • Корни кроются в %g/^\v(.*)\n\1$/d
  • В отличие от других решений, он был инкапсулирован в функцию, поэтому он не изменяет ни регистр поиска, ни неназванный регистр.
  • И это также было включено в команду, чтобы упростить ее использование:
    • :Uniq(эквивалентно :%Uniq),
    • :1,Uniq (от начала буфера до текущей строки),
    • визуально выделите строки + хит :Uniq<cr>(расширенный vim в :'<,'>Uniq)
    • и т. д. :h range)

Вот код:

command! -range=% -nargs=0 Uniq <line1>,<line2>call s:EmuleUniq()

function! s:EmuleUniq() range
  let l1 = a:firstline
  let l2 = a:lastline
  if l1 < l2
    " Note the "-" to avoid spilling over the end of the range
    " Note also the use of ":delete", along with the black hole register "_"
    silent exe l1.','l2.'-g/^\(.*\)\n\1$/d _'

    call histdel('search', -1)          " necessary
    " let @/ = histget('search', -1)    " useless within a function
  endif
endfunction

Примечание: их первые попытки были:

" Version1 from: Preben 'Peppe' Guldberg <peppe {at} xs4all {dot} nl>
" silent exe l1 . ',' . (l2 - 1) . 's/^\(.*\)\%(\n\%<' . (l2 + 1)
      " \ . 'l\1$\)\+/\1/e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n\1$/d'
Люк Эрмитт
источник