Видалити всі послідовні дублікати


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 міг швидко це зробити, але я не можу зовсім обернути голову як. Невже це не під силу макросам і потребує вимскрипту?

Також добре, якщо мені потрібно застосувати один і той же макрос до кожного блоку "Утримує". Це не повинен бути єдиний макрос, який отримує весь файл, хоча це було б дивним.

Відповіді:


13

Я думаю, що наступна команда повинна працювати:

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

Пояснення:

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

:%s/pattern/string/

Ось patternце ^\(.*\)\(\n\1\)\+$і stringє \1.

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

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

^і $збігаються відповідно початок рядка і кінець рядка.

\(і \)використовуються для додавання, subpattern1щоб ми могли посилатися на це пізніше за спеціальним номером \1.
Вони також використовуються для укладання, subpattern2щоб ми могли повторити це 1 або більше разів із кількісним показником \+.

subpattern1є .*
.метахарактором, що відповідає будь-якому символу, крім нового рядка, і *є кількісним показником, що відповідає останньому символу 0, 1 або більше разів.
Так .*відповідає будь-якому тексту, що не містить нового рядка.

subpattern2є збігом \n\1
\nнового рядка та \1збігається з тим самим текстом, який був узгоджений усередині першого \(, \)що тут subpattern1.

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

Де б patternне було зібрано (блок однакових рядків), команда supstitution замінює його, stringяким є тут \1(перший рядок блоку).

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

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

Для більш детального контролю ви також можете попросити підтвердження перед зміною кожного блоку рядків, додавши cнатомість прапор заміни:

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

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

Редагувати: Wildcard вирішив проблему в команді, додавши в $кінці pattern.

Також BloodGain має більш коротку та читану версію тієї ж команди.


1
Ніцца; ваша команда потребує $в цьому, хоча. В іншому випадку він буде робити несподівані речі з рядком, який починається з ідентичного тексту з попереднім рядком, але має деякі інші символи. Також зауважте, що основна команда, яку ви дали, є функціонально еквівалентною моїй відповіді :%!uniq, але прапорці підсвітки та підтвердження приємні.
Wildcard

Ви маєте рацію, я щойно перевірив, і якщо один з повторюваних рядків містить інший задні символ, команда не веде себе так, як очікувалося. Я не знаю, як це виправити, атом \nвідповідає кінці рядка і повинен запобігти цьому, але це не так. Я намагався додати $щойно після .*успіху. Я спробую це виправити, але якщо не зможу, можливо, я видалю свою відповідь або додам попередження наприкінці. Дякуємо, що вказали на цю проблему.
saginaw

1
Спробуйте:%s/^\(.*\)\(\n\1\)\+$/\1/
Wildcard

1
Вам слід врахувати, що $збіги закінчуються рядком , а не кінцем рядка. Це технічно не відповідає дійсності, але коли ви ставите після нього символи, окрім кількох винятків, воно відповідає буквальному, $а не що-небудь особливе. Тож використання \nкраще для багаторядкових матчів. (Див. :help /$)
Wildcard

Я думаю, ви маєте рацію в тому, що \nйого можна використовувати в будь-якому місці регексу, тоді як, $мабуть, його слід використовувати лише в кінці. Тільки для того, щоб зробити різницю між цими двома, я відредагував відповідь, написавши, що \nвідповідає новому $рядку (що інстинктивно змушує вас думати, що після цього ще є якийсь текст), тоді як відповідає кінці рядка (завдяки чому ви думаєте, що нічого немає зліва).
saginaw

10

Спробуйте наступне:

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

Як і у відповіді saginaw , і тут використовується команда Vim: substitute. Однак для покращення читабельності використовується декілька додаткових функцій:

  1. Vim дозволяє нам використовувати будь-який не буквено-цифровий символ ASCII, окрім зворотної косої риски ( \ ), подвійної цитати ( " ) або труби ( | ) для поділу нашого тексту на відповідність / заміну / прапорці. Тут я вибрав крапку з комою ( ; ), але ви можете вибрати іншого.
  2. Vim надає "магічні" параметри для регулярних виразів, щоб символи інтерпретувались за їх спеціальними значеннями, а не вимагали втечі зворотньої косої риски. Це корисно для зменшення багатослівності, і тому, що воно є більш послідовним, ніж "номагічний" за замовчуванням. Починаючи з \vозначає "дуже магічно", або всі символи, крім буквено-цифрових ( A-z0-9 ) та підкреслення ( _ ), мають особливе значення.

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

% для всього файлу

s замінник

; почати заміну рядка

\ v "дуже магія"

^ початок рядка

(. *) 0 або більше будь-якого символу (група 1)

(\ n \ 1) + новий рядок, за яким слідує (текст матчу групи 1), 1 або більше разів (група 2)

$ закінчення рядка (або в цьому випадку подумайте, що наступним символом повинен бути новий рядок )

; почати замінювати рядок

\ 1 збіг тексту 1 групи

; кінець команди або почати прапори


1
Мені дуже подобається ваша відповідь, тому що вона є більш читаною, але також тому, що вона змусила мене краще зрозуміти різницю між \nі $. \nдодає щось до шаблону: символ нового рядка, який повідомляє vim, що наступний текст знаходиться в новому рядку. Хоча $нічого не додає до шаблону, він просто забороняє проводити відповідність, якщо наступний символ поза шаблоном не є новим рядком. Принаймні, це те, що я зрозумів, читаючи вашу відповідь і :help zero-width.
saginaw

І те ж саме має бути правдою для цього ^, він нічого не додає до шаблону, він просто перешкоджає збігу, якщо попередній символ поза шаблоном не є новим рядком ...
saginaw

@saginaw У вас це правильно, і це хороше пояснення. У регулярних виразах деякі символи можуть бути як контрольними . Наприклад, +означає "повторити попередній вираз (символ чи групу) 1 чи більше разів", але нічого не відповідає собі. Засіб ^"не може починатися в середині рядка" і $означає "не може закінчуватися в середині рядка". Зверніть увагу, я там не сказав "рядок", а "рядок". Vim розглядає кожний рядок як рядок за замовчуванням - і ось де воно \nвходить. Він каже, що Vim споживає новий рядок, щоб спробувати зробити це збіг.
Закриття крові

8

Якщо ви хочете видалити ВСІ суміжні однакові лінії, а не просто Hold, ви можете зробити це надзвичайно легко за допомогою зовнішнього фільтра зсередини vim:

:%!uniq (в середовищі Unix).

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

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

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

+Чи означає рядок після поточного рядка. The. відноситься до поточного рядка. /^[^H]/-Чи означає лінію до ( -) в наступному рядку , яка починається не з H.

Тоді d - видалити.


3
Хоча команди-замінники та глобальні команди Vim - це хороші вправи, виклик uniq(зсередини vim чи використання оболонки) - це я вирішував би це. По-перше, я впевнений, що uniqбуде обробляти рядки, які є порожніми / усі пробіли, як еквівалентні (не перевіряв це), але це було б набагато важче захопити за допомогою регулярного вираження. Це також означає не "винаходити колесо", коли я намагаюся завершити роботу.
Кривавий виїзд

2
Можливість подавати текст за допомогою зовнішніх інструментів, тому я зазвичай рекомендую Vim та Cygwin у Windows. Vim і оболонка просто належать разом.
DevSolar

2

Відповідь на основі Vim:

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

= Замініть кожен рядок, який слідує за ним хоча б раз , цим самим.


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 (гольф) Пребен Гулберг та Піт Делпорт.

  • Це коріння %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'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.