Usuń wszystkie kolejne duplikaty

13

Mam plik, który wygląda tak.

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

Chciałbym, aby wyglądało to tak:

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

Jestem pewien, że musi istnieć sposób, aby vim mógł to szybko zrobić, ale nie jestem w stanie całkowicie owinąć głowy. Czy to wykracza poza możliwości makr i potrzebuje vimscript?

Ponadto jest OK, jeśli muszę zastosować to samo makro do każdego bloku „Holds”. Nie musi to być pojedyncze makro, które pobiera cały plik, choć byłoby to niesamowite.

James
źródło

Odpowiedzi:

13

Myślę, że następujące polecenie powinno działać:

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

Objaśnienie:

Używamy polecenia podstawienia dla całego pliku, aby zmienić patternna string:

:%s/pattern/string/

Oto patternjest ^\(.*\)\(\n\1\)\+$i stringjest \1.

pattern można podzielić w następujący sposób:

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

^i $dopasuj odpowiednio początek linii i koniec linii.

\(i \)służą do załączenia subpattern1, abyśmy mogli odwołać się później do specjalnego numeru \1.
Są one również używane do zamykania, subpattern2dzięki czemu możemy powtórzyć to 1 lub więcej razy z kwantyfikatorem \+.

subpattern1jest .*
.metaznakiem pasującym do dowolnego znaku z wyjątkiem nowej linii i *jest kwantyfikatorem, który pasuje do ostatniego znaku 0, 1 lub więcej razy. Dopasowuje
więc .*dowolny tekst nie zawierający nowej linii.

subpattern2to \n\1
\npasuje do nowej linii i \1pasuje ten sam tekst, który został dopasowany do środka pierwszy \(, \)który tutaj jest subpattern1.

patternMożna więc odczytać w ten sposób:
początek wiersza ( ^), po którym następuje dowolny tekst nie zawierający nowej linii ( .*), a następnie nowy wiersz ( \n), a następnie ten sam tekst ( \1), przy czym dwa ostatnie są powtarzane raz lub więcej razy ( \+), oraz wreszcie koniec linii ( $) .

Gdziekolwiek patternjest dopasowany (blok identycznych linii), polecenie zamiany zastępuje go tym, stringco jest tutaj \1(pierwsza linia bloku).

Jeśli chcesz zobaczyć, które bloki linii zostaną zmienione bez zmiany czegokolwiek w pliku, możesz włączyć tę hlsearchopcję i dodać nflagę podstawienia na końcu polecenia:

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

Aby uzyskać bardziej szczegółową kontrolę, możesz również poprosić o potwierdzenie przed zmianą każdego bloku linii, dodając czamiast tego flagę podstawienia:

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

Aby uzyskać więcej informacji na temat poleceń odczytu podstawienia :help :s,
dla flagi podmiany :help s_flags,
na różne metaznakami i kwantyfikatory czytać :help pattern-atoms,
a dla wyrażenia regularne w vim czytać tego .

Edycja: symbol wieloznaczny naprawił problem w poleceniu, dodając $na końcu pattern.

Również BloodGain ma krótszą i bardziej czytelną wersję tego samego polecenia.

saginaw
źródło
1
Ładny; jednak twoje polecenie potrzebuje $w tym. W przeciwnym razie zrobi nieoczekiwane rzeczy z linią, która zaczyna się identycznym tekstem jak poprzednia linia, ale ma inne końcowe znaki. Zauważ też, że podstawowe polecenie, które wydałeś, jest funkcjonalnie równoważne z moją odpowiedzią :%!uniq, ale flagi wyróżnienia i potwierdzenia są ładne.
Wildcard
Masz rację, właśnie sprawdziłem i jeśli jedna ze zduplikowanych linii zawiera inny znak końcowy, polecenie nie działa zgodnie z oczekiwaniami. Nie wiem, jak to naprawić, atom \npasuje do końca linii i powinien temu zapobiec, ale tak nie jest. Próbowałem dodać $zaraz potem .*bez powodzenia. Spróbuję to naprawić, ale jeśli nie mogę, może usunę odpowiedź lub dodam ostrzeżenie na końcu. Dziękujemy za wskazanie tego problemu.
saginaw
1
Spróbuj:%s/^\(.*\)\(\n\1\)\+$/\1/
Wildcard,
1
Należy wziąć pod uwagę, że $pasuje on do końca łańcucha , a nie do końca linii. Z technicznego punktu widzenia nie jest to prawdą, ale po umieszczeniu po nim znaków innych niż kilka wyjątków, pasuje ono dosłownie $zamiast czegoś specjalnego. Dlatego używanie \njest lepsze w przypadku meczów wieloliniowych. (Patrz :help /$)
Wildcard,
Myślę, że masz rację, że \nmożna go użyć w dowolnym miejscu wyrażenia regularnego, podczas gdy $prawdopodobnie powinien być używany tylko na końcu. Aby zrobić różnicę między tymi dwoma, zredagowałem odpowiedź, pisząc, która \npasuje do nowego wiersza (co instynktownie sprawia, że ​​myślisz, że nadal jest jakiś tekst po), podczas gdy $pasuje do końca linii (co sprawia, że ​​myślisz, że nic nie ma lewo).
saginaw
10

Spróbuj wykonać następujące czynności:

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

Podobnie jak w przypadku odpowiedzi saginaw , wykorzystuje to polecenie Vima: substytut. Wykorzystuje jednak kilka dodatkowych funkcji w celu poprawy czytelności:

  1. Vim pozwala nam używać dowolnego niealfanumerycznego znaku ASCII oprócz ukośnika odwrotnego ( \ ), podwójnego cudzysłowu ( " ) lub potoku ( | ) do podzielenia tekstu dopasowania / zamiany / flagi. Tutaj wybrałem średnik ( ; ), ale możesz Wybierz inny.
  2. Vim zapewnia „magiczne” ustawienia dla wyrażeń regularnych, dzięki czemu znaki są interpretowane ze względu na ich specjalne znaczenia, zamiast wymagać ucieczki w odwrotnym ukośniku. Jest to pomocne w celu zmniejszenia gadatliwości, a ponieważ jest bardziej spójne niż domyślne ustawienie „nomagiczny”. Rozpoczęcie od \voznacza „bardzo magiczne” lub wszystkie znaki z wyjątkiem alfanumerycznych ( A-z0-9 ) i podkreślenia ( _ ) mają specjalne znaczenie.

Znaczenie komponentów to:

% dla całego pliku

s substytut

; rozpocząć łańcuch zastępczy

\ v „bardzo magiczne”

^ początek linii

(. *) 0 lub więcej dowolnych znaków (grupa 1)

(\ n \ 1) + nowa linia, po której następuje (tekst dopasowania grupy 1), 1 lub więcej razy (grupa 2)

$ koniec linii (lub w tym przypadku, myśl, że następny znak musi być nową linią )

; zacznij zastępować ciąg

\ 1 grupa 1 pasuje do tekstu

; koniec komendy lub flagi rozpoczęcia

Zysk krwi
źródło
1
Naprawdę podoba mi się twoja odpowiedź, ponieważ jest bardziej czytelna, ale także dlatego, że pozwoliła mi lepiej zrozumieć różnicę między \ni $. \ndodaje coś do wzoru: znak nowa linia, który mówi vimowi, że następujący tekst znajduje się w nowej linii. Chociaż $nic nie dodaje do wzoru, po prostu zabrania dopasowania, jeśli następny znak poza wzorem nie jest nową linią. Przynajmniej to zrozumiałem, czytając twoją odpowiedź i :help zero-width.
saginaw
To samo musi być prawdą ^, ponieważ nie dodaje niczego do wzoru, po prostu uniemożliwia dopasowanie, jeśli poprzedni znak poza wzorem nie jest nową linią ...
saginaw
@saginaw Masz dokładnie rację i to dobre wytłumaczenie. W wyrażeniach regularnych niektóre znaki mogą być traktowane jako znaki kontrolne . Na przykład +oznacza „powtórz poprzednie wyrażenie (znak lub grupę) 1 lub więcej razy”, ale nie pasuje do niczego. Te ^środki „nie może rozpocząć się w środku łańcucha” i $oznacza „nie można zakończyć w środku łańcucha.” Zauważ, że nie powiedziałem tam „line”, ale „string”. Vim domyślnie traktuje każdą linię jako ciąg znaków - i to jest miejsce, w którym się \npojawia. Mówi Vimowi, aby użył nowej linii, aby spróbować dopasować.
Bloodgain,
8

Jeśli chcesz usunąć WSZYSTKIE sąsiednie identyczne linie, nie tylko Hold, możesz to zrobić niezwykle łatwo z zewnętrznym filtrem od wewnątrz vim:

:%!uniq (w środowisku Unix).

Jeśli chcesz to zrobić bezpośrednio vim, jest to bardzo trudne. Myślę, że jest na to sposób, ale w ogólnym przypadku jest to bardzo trudne, aby uczynić go w 100% funkcjonalnym i nie opracowałem jeszcze wszystkich błędów.

Jednak w tym konkretnym przypadku, ponieważ możesz wizualnie zobaczyć, że następny wiersz, który nie jest duplikatem, nie zaczyna się od tego samego znaku, możesz użyć:

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

+Oznacza linię po aktualnej linii. The. odnosi się do bieżącej linii. /^[^H]/-Oznacza linię wcześniej ( -) następnej linii, która nie zaczyna H.

Następnie d jest usuwane.

Dzika karta
źródło
3
Podczas gdy zastępcze i globalne polecenia Vima są dobrymi ćwiczeniami, uniqto w jaki sposób bym to rozwiązał , wywoływanie (z poziomu vima lub za pomocą powłoki). Po pierwsze, jestem prawie pewien, uniqże poradzą sobie z liniami, które są puste / wszystkie spacje jako równoważne (nie testowałem tego), ale byłoby to znacznie trudniejsze do uchwycenia za pomocą wyrażenia regularnego. Oznacza to również, że nie „odkrywam na nowo koła”, gdy próbuję wykonać pracę.
Bloodgain,
2
Możliwość podawania tekstu za pomocą zewnętrznych narzędzi jest tym, dlatego zazwyczaj polecam Vimowi i Cygwinowi w systemie Windows. Vim i shell po prostu należą do siebie.
DevSolar,
2

Odpowiedź oparta na Vimie:

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

= Zastąp każdą linię, po której następuje co najmniej raz , tą samą linią.

VanLaser
źródło
2

Jeszcze jedno, zakładając, że Vim 7.4.218 lub nowszy:

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>)

Jednak niekoniecznie jest to lepsze niż inne rozwiązania.

Sato Katsura
źródło
2

Oto rozwiązanie oparte na starym (2003) vim (golf) autorstwa Prebena Gulberga i Piet Delport.

  • Jego korzenie tkwią w %g/^\v(.*)\n\1$/d
  • W przeciwieństwie do innych rozwiązań, został on enkapsulowany w funkcję, więc nie modyfikuje rejestru wyszukiwania ani rejestru nienazwanego.
  • Został także umieszczony w komendzie, aby uprościć jej użycie:
    • :Uniq(odpowiednik :%Uniq),
    • :1,Uniq (od początku bufora do bieżącej linii),
    • wizualnie wybierz linie + hit :Uniq<cr>(rozwinięty przez vim do :'<,'>Uniq)
    • etc ( :h range)

Oto kod:

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

Uwaga: ich pierwsze próby to:

" 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'
Luc Hermitte
źródło