Verwenden Sie den Befehl ex, um zu überprüfen, ob zwei Zeilen identisch sind.

9

Ich habe mir diese Frage angesehen und mich dann gefragt, wie ich meine Antwortsed implementieren könnte, die nur POSIX verwendet ex .

Der Trick ist, dass sedich zwar den Haltebereich mit dem Musterbereich vergleichen kann, um festzustellen, ob sie genau gleichwertig sind (mit G;/^\(.*\)\n\1$/{do something}), aber keine Möglichkeit kenne, einen solchen Test durchzuführen ex.

Ich weiß, dass ich in Vim Ydie erste Zeile ankeln und dann eingeben könnte, :2,$g/<C-r>0/dum fast das zu tun, was ich spezifiziere - aber wenn die erste Zeile alles andere als sehr einfachen alphanumerischen Text enthält, wird dies in der Tat zu einem Zufall, da die Zeile als Regex eingefügt wird , nicht nur eine Zeichenfolge zum Vergleich. (Und wenn die erste Zeile einen Schrägstrich enthält, wird der Rest der Zeile als Befehl interpretiert!)

Wenn ich also alle Zeilen löschen möchte myfile, die mit der ersten Zeile identisch sind - aber nicht die erste Zeile löschen -, wie kann ich das verwenden ex? Wie könnte ich das überhaupt tun vi?

Gibt es eine POSIX-Möglichkeit, eine Zeile zu löschen, wenn sie genau mit einer anderen Zeile übereinstimmt?

Vielleicht so etwas wie diese imaginäre Syntax:

:2,$g/**lines equal to "0**/d
Platzhalter
quelle
3
Sie könnten den Befehl erstellen, aber es würde ein wenig Vimscript benötigen und es wäre wahrscheinlich kein POSIX-Weg::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw
1
@saginaw, danke. Bisher ist mir nur der POSIX-Ansatz eingefallen, nur sedals Filter von innen zu verwenden exund meine gesamte sedAntwort auf dem gesamten Puffer auszuführen ... was natürlich funktionieren würde (und im Gegensatz dazu tatsächlich portabel ist sed -i).
Wildcard
Sie haben Recht und ich finde Ihren ersten Ansatz <C-r>0sehr gut. Ich bin mir nicht sicher, ob Sie nur mit Ex-Befehlen besser abschneiden können, da Sie Sonderzeichen schützen müssen. Ohne die POSIX-konforme Einschränkung würden Sie wahrscheinlich den sehr nomagischen Schalter verwenden \Vund dann den Backslash (da er auch \Vmit dieser escape()Funktion seine besondere Bedeutung behält ) mit der Funktion schützen, deren zweites Argument eine Zeichenfolge ist, die alle Zeichen enthält, die Sie maskieren möchten .
Saginaw
Im vorherigen Befehl habe ich jedoch vergessen, auch den Schrägstrich zu schützen, da er auch eine besondere Bedeutung für den globalen Befehl hat, nämlich den Musterbegrenzer. Der richtige Befehl wäre also wahrscheinlich etwa: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'Oder Sie könnten ein anderes Zeichen für das Mustertrennzeichen wie ein Semikolon verwenden. In diesem Fall müssten Sie keinen Schrägstrich im Muster schützen. Es würde so etwas wie geben::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
saginaw
1
Ich finde deinen zweiten Ansatz sedauch sehr gut. Mit Vim delegieren Sie häufig bestimmte spezielle Aufgaben an andere Programme und sedsind wahrscheinlich ein gutes Beispiel dafür. Übrigens müssen Sie nicht sedauf Ihrem gesamten Puffer laufen . Wenn Sie es nur für einen Teil des Puffers ausführen möchten, können Sie einen Bereich angeben. Wenn Sie beispielsweise nur die Zeilen zwischen 50 und 100 filtern möchten, können Sie Folgendes eingeben : :50,100!<your sed command>.
Saginaw

Antworten:

3

Vim

In Vim können Sie jedes Zeichen einschließlich Zeilenumbruch mit abgleichen \_.. Sie können dies verwenden, um ein Muster zu erstellen, das einer ganzen Linie, einer beliebigen Menge an Material und dann derselben Linie entspricht:

/\(^.*$\)\_.*\n\1$/

Jetzt möchten Sie alle Zeilen in einer Datei löschen, die mit der ersten übereinstimmen, ohne die erste. Die Ersetzung zum Löschen der letzten Zeile, die mit der ersten übereinstimmt, lautet:

:1 s/\(^.*$\)\_.*\zs\n\1$//

Mit können Sie :globalsicherstellen, dass die Ersetzung so oft wiederholt wird, dass alle Zeilen gelöscht werden:

:g/^/ 1s/\(^.*$\)\_.*\zs\n\1$//

POSIX ex

@saginaw zeigt in einem Kommentar zu Ihrer Frage eine bessere Möglichkeit, dies in Vim zu tun, aber wir können die obige Technik für POSIX ex anpassen.

Um dies auf POSIX-kompatible Weise zu tun, müssen Sie den mehrzeiligen Abgleich nicht zulassen, können jedoch weiterhin Rückreferenzen verwenden. Dies erfordert einige zusätzliche Arbeit:

:g/^/ t- | s/^/@@@/ | 1t- | s/^/"/ | j! | s/^"\(.*\)@@@\1$/d/ | d x | @x

Hier ist die Aufschlüsselung:

: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

Wenn die aktuelle Zeile also ein Duplikat von Zeile 1 ist, enthält Register x d. Durch Ausführen wird die aktuelle Zeile gelöscht. Wenn es sich nicht um ein Duplikat handelt, enthält es einen vorangestellten Unsinn, bei "dessen Ausführung ein No-Op angezeigt wird, da " ein Kommentar gestartet wird. Ich weiß nicht, ob dies der beste Weg ist, dies zu erreichen. Es ist nur der erste, der mir in den Sinn kam!

Es kommt einfach so vor, dass die erste Zeile nicht gelöscht werden kann, da der Kopiervorgang vorübergehend die Zeile 1 ändert. Wenn dies nicht der Fall gewesen wäre, könnten Sie dem stattdessen :geinen 2,$Bereich voranstellen .

Getestet in Vim und Ex-Vi Version 4.0.

BEARBEITEN

Und eine einfachere Methode, bei der Sonderzeichen zum Erstellen eines Suchmusters (mit 'nomagic'set) vermieden werden, erstellt einen :globalBefehl und führt ihn dann aus:

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

Sie können dies jedoch nicht als Einzeiler tun, da Sie einen verschachtelten haben würden :global, was nicht erlaubt ist.

Antonius
quelle
2

Es scheint, dass der einzige POSIX-Weg, dies zu tun, die Verwendung eines externen Filters ist, wie z sed.

Wenn Sie beispielsweise die 17. Zeile Ihrer Datei nur löschen möchten, wenn sie genau mit der 5. Zeile identisch ist, und sie ansonsten unverändert lassen, können Sie Folgendes tun:

:1,17!sed '5h;17{G;/^\(.*\)\n\1$/d;s/\n.*$//;}'

(Sie könnten hier sedden gesamten Puffer ausführen , oder Sie könnten ihn nur in den Zeilen 5-17 ausführen, aber im ersten Fall führen Sie unnötige Filterung durch - keine große Sache - und im letzteren Fall müssten Sie den verwenden Nummern 1 und 13 in Ihrem sedBefehl anstelle von 5 und 17. Verwirrend.)

Da sednur ein einziger Vorwärtsdurchlauf ausgeführt wird, gibt es keine einfache Möglichkeit, den Rückwärtsgang durchzuführen und die 5. Zeile nur dann zu löschen, wenn sie mit der 17. Zeile identisch ist. Ich habe es eine Weile aus Neugier versucht ... es ist schwierig .


Durchbruch - Sie können es so machen:

:17t 5
:5,5+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Dies ist eigentlich die allgemeinere Methode. Es kann ebenfalls verwendet werden, um das gleiche Ergebnis wie beim ersten Befehl zu erhalten (und die 17. Zeile nur zu löschen, wenn sie mit der 5. Zeile identisch ist).

:5t 17
:17,17+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Für breitere Verwendungszwecke wie das Löschen aller Zeilen der Datei, die mit Zeile 37 identisch sind, während Zeile 37 intakt bleibt, können Sie Folgendes tun:

: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.*$//'

Die Schlussfolgerung hier ist, zu überprüfen , ob zwei Linien identisch sind, das beste Werkzeug ist sed , nicht ex. Aber wie DevSolar in einem Kommentar angedeutet hat , ist dies kein Fehler von vioder ex- sie sind für die Arbeit mit Unix-Tools ausgelegt. das ist eine große Stärke.

Platzhalter
quelle
Viel, viel schwieriger ist: Einfügen einer Zeile am Ende einer Datei, nur wenn die Zeile noch nicht irgendwo in der Datei vorhanden ist.
Wildcard
Das sollte mit einem ähnlichen Ansatz wie meiner Antwort machbar sein. Ich denke nicht, dass es ein Einzeiler wäre!
Antony