Löschen Sie alle aufeinander folgenden Duplikate


13

Ich habe eine Datei, die so aussieht.

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

Ich möchte, dass es so aussieht:

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

Ich bin mir sicher, dass es eine Möglichkeit geben muss, mit der Vim das schnell erledigen kann, aber ich kann mich nicht so recht darum kümmern, wie. Übersteigt dies die Möglichkeiten von Makros und benötigt es Vimscript?

Es ist auch in Ordnung, wenn ich auf jeden Block von "Holds" dasselbe Makro anwenden muss. Es muss kein einziges Makro sein, das die gesamte Datei abruft, obwohl das großartig wäre.

Antworten:


13

Ich denke, der folgende Befehl sollte funktionieren:

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

Erklärung:

Wir verwenden die Substitution Befehl auf die gesamte Datei zu ändern patternin string:

:%s/pattern/string/

Hier patternist ^\(.*\)\(\n\1\)\+$und stringist \1.

pattern kann wie folgt aufgeteilt werden:

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

^und $stimmen jeweils mit einem Zeilenanfang und einem Zeilenende überein.

\(und \)dienen zum Einschließen, subpattern1damit wir später durch die spezielle Nummer darauf verweisen können \1.
Sie werden auch zum Einschließen verwendet, subpattern2damit wir sie mindestens einmal mit dem Quantor wiederholen können \+.

subpattern1ist .*
.ein Metazeichen, das mit einem beliebigen Zeichen außer einer neuen Zeile übereinstimmt, und *ein Quantifizierer, der mit dem letzten Zeichen 0, 1 oder mehrmals übereinstimmt .
Stimmt also .*mit jedem Text überein, der keine neue Zeile enthält.

subpattern2Es \n\1
\nstimmt mit einer neuen Zeile \1überein und stimmt mit dem gleichen Text überein, der in der ersten übereinstimmte. Dies \(ist \)hier subpattern1.

Man patternkann also so lesen:
ein Zeilenanfang ( ^) gefolgt von einem beliebigen Text, der keine neue Zeile ( .*) enthält, gefolgt von einer neuen Zeile ( \n) und demselben Text ( \1), wobei die beiden letzteren ein oder mehrere Male wiederholt werden ( \+) und endlich ein Zeilenende ( $) .

Wo immer patterneine Übereinstimmung vorliegt (ein Block mit identischen Zeilen), ersetzt der Ersetzungsbefehl stringden hier angegebenen \1(die erste Zeile des Blocks).

Wenn Sie sehen möchten, welche Zeilenblöcke betroffen sind, ohne etwas in Ihrer Datei zu ändern, können Sie die hlsearchOption aktivieren und das nSubstitutionsflag am Ende des Befehls hinzufügen :

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

Für eine genauere Kontrolle können Sie auch eine Bestätigung anfordern, bevor Sie jeden Zeilenblock ändern, indem Sie cstattdessen das Substitutionsflag hinzufügen :

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

Weitere Informationen über die Substitution Lesebefehl :help :s,
für die Substitution Fahnen :help s_flags,
für die verschiedenen Metazeichen und quantifiers lesen :help pattern-atoms,
und für reguläre Ausdrücke in vim lesen diese .

Bearbeiten: Platzhalter behebt ein Problem im Befehl durch Hinzufügen eines $am Ende von pattern.

Auch BloodGain hat eine kürzere und besser lesbare Version des gleichen Befehls.


1
Nett; Ihr Befehl benötigt jedoch eine $. Andernfalls werden unerwartete Aktionen mit einer Zeile ausgeführt, die mit dem gleichen Text wie die vorherige Zeile beginnt , jedoch andere nachgestellte Zeichen enthält. Beachten Sie auch, dass der grundlegende Befehl, den Sie gaben, meiner Antwort von funktional entspricht :%!uniq, aber die Markierungs- und Bestätigungs-Flags sind nett.
Wildcard

Sie haben Recht, ich habe gerade geprüft, und wenn eine der doppelten Zeilen ein anderes abschließendes Zeichen enthält, verhält sich der Befehl nicht wie erwartet. Ich weiß nicht, wie ich es beheben soll, das Atom \nstimmt mit einem Zeilenende überein und sollte dies verhindern, tut es aber nicht. Ich habe versucht, ein $kurz nach .*ohne Erfolg hinzuzufügen . Ich werde versuchen, das Problem zu beheben, aber wenn ich es nicht kann, werde ich möglicherweise meine Antwort löschen oder am Ende eine Warnung hinzufügen. Vielen Dank, dass Sie auf dieses Problem hingewiesen haben.
Saginaw

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

1
Sie sollten berücksichtigen, dass das $Ende der Zeichenfolge und nicht das Ende der Zeile übereinstimmt . Dies ist technisch gesehen nicht wahr - aber wenn Sie Zeichen bis auf wenige Ausnahmen nachstellen, entspricht dies einem Literal und $nicht etwas Besonderem. Verwenden \nist also besser für mehrzeilige Matches. (Siehe :help /$)
Wildcard

Ich denke, Sie haben Recht damit, \ndass es überall innerhalb des regulären Ausdrucks verwendet werden kann, wohingegen $es wahrscheinlich nur am Ende verwendet werden sollte. Um einen Unterschied zwischen den beiden zu machen, habe ich die Antwort so bearbeitet, dass sie \nmit einer neuen Zeile übereinstimmt (was Sie instinktiv denken lässt, dass noch Text folgt), wohingegen $ein Zeilenende übereinstimmt (was Sie glauben lässt, dass es nichts gibt) links).
Saginaw

10

Versuche Folgendes:

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

Wie bei der Antwort von saginaw wird der Befehl: substitute von Vim verwendet. Es werden jedoch einige zusätzliche Funktionen zur Verbesserung der Lesbarkeit verwendet:

  1. In Vim können wir alle nicht-alphanumerischen ASCII-Zeichen außer Backslash ( \ ), doppelte Anführungszeichen ( " ) oder Pipe ( | ) verwenden, um unseren Text für Match / Replace / Flags zu teilen. Hier habe ich Semikolon ( ; ) ausgewählt, aber Sie können wähle ein anderes.
  2. Vim bietet "magische" Einstellungen für reguläre Ausdrücke, sodass Zeichen aufgrund ihrer besonderen Bedeutung interpretiert werden, anstatt einen Backslash-Escape zu erfordern. Dies ist hilfreich, um die Ausführlichkeit zu reduzieren, und weil es konsistenter ist als die Standardeinstellung "nomagic". Beginnt mit \v"sehr magisch" oder alle Zeichen außer alphanumerisch ( A-z0-9 ) und Unterstrich ( _ ) haben eine besondere Bedeutung.

Die Komponenten haben folgende Bedeutung:

% für die gesamte Datei

s Ersatz

; beginne Ersatzzeichenfolge

\ v "sehr magisch"

^ Zeilenanfang

(. *) 0 oder mehr eines beliebigen Zeichens (Gruppe 1)

(\ n \ 1) + Zeilenvorschub gefolgt von (Gruppe 1 entspricht dem Text), 1 oder mehrmals (Gruppe 2)

$ end of line (oder in diesem Fall denke, das nächste Zeichen muss eine neue Zeile sein )

; beginne, den String zu ersetzen

\ 1 Gruppe 1 stimmt mit dem Text überein

; Ende des Befehls oder Beginn der Flags


1
Ich mag deine Antwort sehr, weil sie besser lesbar ist, aber auch, weil ich den Unterschied zwischen \nund besser verstehe $. \nfügt dem Muster etwas hinzu: das Zeichen new line, das vim mitteilt, dass sich der folgende Text in einer neuen Zeile befindet. Während $dem Muster nichts hinzugefügt wird, wird lediglich eine Übereinstimmung ausgeschlossen, wenn das nächste Zeichen außerhalb des Musters keine neue Zeile ist. Zumindest habe ich das verstanden, als ich Ihre Antwort gelesen habe und :help zero-width.
Saginaw

Und das muss auch so sein ^, es fügt nichts zum Muster hinzu, es verhindert nur, dass eine Übereinstimmung hergestellt wird, wenn das vorherige Zeichen außerhalb des Musters keine neue Zeile ist ...
saginaw

@saginaw Du hast es genau richtig, und das ist eine gute Erklärung. In regulären Ausdrücken können einige Zeichen als Steuerzeichen verwendet werden . Bedeutet zum Beispiel +"den vorhergehenden Ausdruck (Zeichen oder Gruppe) 1 oder mehrmals wiederholen", entspricht aber selbst nichts. Das ^Mittel "kann nicht in der Mitte der Zeichenfolge beginnen" und das $Mittel "kann nicht in der Mitte der Zeichenfolge enden". Beachten Sie, dass ich dort nicht "line", sondern "string" gesagt habe. Vim behandelt jede Zeile standardmäßig als Zeichenfolge - und \ngenau hier kommt es an. Vim wird angewiesen, eine neue Zeile zu belegen, um zu versuchen, diese Übereinstimmung herzustellen.
Bloodgain

8

Wenn Sie ALLE benachbarten identischen Linien entfernen möchten Hold, können Sie dies äußerst einfach mit einem externen Filter von innen tun vim:

:%!uniq (In einer Unix-Umgebung).

Wenn Sie es direkt in tun möchten vim, ist es tatsächlich sehr schwierig. Ich denke, es gibt einen Weg, aber für den allgemeinen Fall ist es sehr schwierig, ihn zu 100% funktionsfähig zu machen, und ich habe noch nicht alle Fehler behoben.

In diesem speziellen Fall können Sie jedoch Folgendes verwenden, da Sie visuell sehen können, dass die nächste nicht duplizierte Zeile nicht mit demselben Zeichen beginnt:

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

Das +bedeutet die Zeile nach der aktuellen Zeile. Das . bezieht sich auf die aktuelle Zeile. Das /^[^H]/-bedeutet die Zeile vor ( -) der nächsten Zeile, die nicht mit H beginnt.

Dann ist d löschen.


3
Während der Ersatzbefehl und der globale Vim-Befehl gute Übungen sind, kann uniqich diesen Fehler durch Aufrufen (entweder aus Vim heraus oder mithilfe der Shell) lösen. Zum einen bin ich mir ziemlich sicher uniq, dass Zeilen, die leer / alle Leerzeichen sind, als äquivalent behandelt werden (habe es nicht getestet), aber das wäre mit einer Regex viel schwieriger zu erfassen. Es bedeutet auch, das Rad nicht neu zu erfinden, während ich versuche, die Arbeit zu erledigen.
Bloodgain

2
Aufgrund der Möglichkeit, Text über externe Tools einzugeben, empfehle ich normalerweise Vim und Cygwin unter Windows. Vim und Shell gehören einfach zusammen.
DevSolar

2

Eine Vim-basierte Antwort:

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

= Ersetzen Sie jede Zeile, gefolgt von sich selbst, mindestens einmal durch dieselbe Zeile.


2

Eine weitere, vorausgesetzt Vim 7.4.218 oder neuer:

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

Dies ist jedoch nicht unbedingt besser als die anderen Lösungen.


2

Hier ist eine Lösung basierend auf einem alten (2003) Vim (Golf) von Preben Gulberg und Piet Delport.

  • Ihre Wurzeln liegen in %g/^\v(.*)\n\1$/d
  • Im Gegensatz zu den anderen Lösungen wurde es in eine Funktion eingekapselt, sodass weder das Suchregister noch das unbenannte Register geändert werden.
  • Und es wurde auch in einen Befehl gekapselt, um seine Verwendung zu vereinfachen:
    • :Uniq(entspricht :%Uniq),
    • :1,Uniq (vom Anfang des Puffers bis zur aktuellen Zeile),
    • Linien visuell auswählen + treffen :Uniq<cr>(erweitert um vim in :'<,'>Uniq)
    • etc ( :h range)

Hier ist der Code:

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

Hinweis: Ihre ersten Versuche waren:

" 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'
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.