Verwirrt durch sed-Ausgabe bei Verwendung von N. Kann jemand diese Ergebnisse erklären?

8

Ich lerne sed. Alles schien gut zu laufen, bis ich auf das N stieß (als nächstes mehrzeilig). Ich habe diese Datei (guide.txt) zum Üben / Verstehen / Kontext erstellt. Hier ist der Inhalt dieser Datei ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Mein Ziel ist es daher, ALLE Instanzen von "Netzwerkadministrator" durch "Systembenutzer" zu ersetzen. Da die erste Instanz von "Netzwerkadministrator" durch eine neue Zeile (\ n) getrennt ist, muss der mehrzeilige nächste Operator (N) die Zeile, die mit "Administrator" beginnt, an die vorherige Zeile anhängen, die mit "Netzwerk \ n" endet. . Kein Problem. Ich möchte aber auch alle anderen einzeiligen "Netzwerkadministrator" -Instanzen abfangen.

Durch meine Forschung habe ich gelernt, dass ich zwei Substitutionsbefehle benötige; eine für die durch Zeilenumbrüche getrennte Zeichenfolge und eine für die anderen. Außerdem passiert ein Jive aufgrund der letzten Zeile, die die Substitutionsübereinstimmung enthält, und der nächsten Zeile mit mehreren Zeilen. Also bastle ich das ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Dies gibt diese Ergebnisse zurück ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Ich dachte, dass die einzeilige Ersetzung alle "normalen" Instanzen von "Network Administrator" abfangen und gegen "Systembenutzer" austauschen würde, während die mehrzeilige Anweisung ihre Magie auf die durch neue Zeilen getrennte Instanz ausüben würde, aber wie Sie Ich kann sehen, dass es zurückgegeben wird, was ich für unerwartete Ergebnisse halte.

Nach einigem Fummeln landete ich auf diesem ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

Und voilà, ich bekomme die gewünschte Ausgabe von ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Warum funktioniert das und das ursprüngliche sed-Skript nicht? Ich möchte das wirklich verstehen.

Vielen Dank im Voraus für jede Hilfe.

dlowrie290
quelle
Gut, dass du Sed gelernt hast! Ich habe Sed verwendet, um eine sehr ähnliche Frage früher auf dieser Site zu lösen . kann von Interesse sein.
Wildcard
Und zwei weitere knifflige Sed-Beispiele: unix.stackexchange.com/a/277375/135943 , unix.stackexchange.com/a/257913/135943
Wildcard

Antworten:

6

Während Sie lernen sed, werde ich mir die Zeit nehmen, um die Antwort von @ John1024 zu ergänzen:

1) Bitte beachten Sie, dass Sie \nin der Ersatzzeichenfolge verwenden. Dies funktioniert in GNU sed, ist jedoch nicht Teil von POSIX, sodass ein Backslash und ein nin vielen anderen seds \neingefügt werden (die Verwendung im Muster ist übrigens portabel).

Stattdessen schlage ich Folgendes vor s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: Das entspricht [[:space:]]Zeilenumbruch oder Leerzeichen, sodass Sie nicht zwei sBefehle benötigen , sondern diese in einem kombinieren. Wenn Sie es mit umgeben \(...\), können Sie im Ersatz darauf verweisen: Das \1wird durch das ersetzt, was im ersten Paar von übereinstimmte \(\).

2) Um Muster über zwei Linien richtig abzugleichen, sollten Sie das N;P;DMuster kennen:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

Das Nist immer append die nächste Zeile ( mit Ausnahme der letzten Zeile, das ist , warum es „adressierte“ mit $!(= wenn nicht letzte Zeile, man sollte immer vorausgehen betrachtet Nmit $!versehentlich zu vermeiden , um das Skript endet) Dann nach dem Austausch der. PDruckt nur Die erste Zeile im Musterraum und die Dlöscht diese Zeile und beginnt den nächsten Zyklus mit den Resten des Musterraums (ohne die nächste Zeile zu lesen). Dies ist wahrscheinlich das, was Sie ursprünglich beabsichtigt hatten.

Denken Sie an dieses Muster, Sie werden es oft brauchen.

3) Ein weiteres nützliches Muster für die mehrzeilige Bearbeitung, insbesondere wenn mehr als zwei Zeilen beteiligt sind: Halten Sie das Sammeln von Speicherplatz, wie ich John vorgeschlagen habe:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

Ich wiederhole es, um es zu erklären: HHängt jede Zeile an den Haltebereich an. Da dies zu einer zusätzlichen neuen Zeile vor der ersten Zeile führen würde, muss die erste Zeile verschoben und nicht angehängt werden 1h. Das Folgende $!dbedeutet "für alle Zeilen außer der letzten den Musterraum löschen und von vorne beginnen". Daher wird der Rest des Skripts nur für die letzte Zeile ausgeführt. Zu diesem Zeitpunkt wird die gesamte Datei im Haltebereich gesammelt (verwenden Sie diese also nicht für sehr große Dateien!) Und gin den Musterbereich verschoben, sodass Sie alle Ersetzungen auf einmal durchführen können, wie Sie es mit der -zOption können GNU sed.

Dies ist ein weiteres nützliches Muster, das ich beachten sollte.

Philippos
quelle
Beeindruckend! Tolle Erklärung! Dies zusammen mit Johns Antwort gab mir wirklich einen besseren Einblick in dieses Problem und beruhigte mich im Allgemeinen. Sieht so aus, als hätte ich noch viel zu lernen. Ich wünschte, ich könnte Ihre beiden Lösungen als Antworten prüfen. Vielen Dank für Ihre Bemühungen. Sie werden sehr geschätzt.
dlowrie290
7

Beachten Sie zunächst, dass Ihre Lösung nicht wirklich funktioniert. Betrachten Sie diese Testdatei:

$ cat test1
Network
Administrator Network
Administrator

Führen Sie dann den folgenden Befehl aus:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

Das Problem ist, dass der Code den letzten nicht ersetzt Network\nAdministrator.

Diese Lösung funktioniert:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

Wir können dies auch auf Ihre anwenden guide.txt:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Der Schlüssel ist, weiter in Zeilen zu lesen, bis Sie eine finden, die nicht mit endet Network. Wenn dies erreicht ist, können die Substitutionen durchgeführt werden.

Kompatibilitätshinweis: Alle oben genannten werden \nim Ersatztext verwendet. Dies erfordert GNU sed. Es wird nicht auf BSD / OSX sed funktionieren.

[Hutspitze zu Philippos .]

Mehrzeilige Version

Wenn dies zur Verdeutlichung beiträgt, finden Sie hier denselben Befehl, der auf mehrere Zeilen verteilt ist:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Wie es funktioniert

  1. :a

    Dadurch wird ein Etikett erstellt a.

  2. /Network$/{ $!{N;ba} }

    Wenn diese Zeile mit endet Network, lesen und hängen Sie die nächste Zeile ( ) an und verzweigen Sie zurück zu label ( ) , wenn dies nicht die letzte Zeile ( ) ist.$!Naba

  3. s/Network\nAdministrator/System\nUser/g

    Nehmen Sie die Ersetzung durch die Zwischen-Newline vor.

  4. s/Network Administrator/System User/g

    Nehmen Sie die Ersetzung mit dem Zwischenrohling vor.

Einfachere Lösung (nur GNU)

Mit GNU sed ( nicht BSD / OSX) benötigen wir nur einen Ersatzbefehl:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User

Und in der guide.txtAkte:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

In diesem Fall -zweist sed an, bis zum ersten NUL-Zeichen einzulesen. Da Textdateien niemals ein Nullzeichen haben, wird die gesamte Datei auf einmal eingelesen. Wir können dann die Ersetzung vornehmen, ohne uns Sorgen machen zu müssen, dass eine Zeile fehlt.

Diese Methode ist nicht gut, wenn die Datei sehr groß ist (was normalerweise Gigabyte bedeutet). Wenn es so groß ist, kann das gleichzeitige Einlesen den System-RAM belasten.

Lösung, die sowohl auf GNU als auch auf BSD sed funktioniert

Wie von Phillipos vorgeschlagen , ist Folgendes eine tragbare Lösung:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'
John1024
quelle
1
Hervorragende Informationen, John! Vielen Dank, dass Sie etwas Licht ins Dunkel gebracht haben, und Ihre alternative Lösung ist sehr schön. Trotzdem verstehe ich immer noch nicht, warum meine Lösung keine Lösung ist. Es scheint zu funktionieren, aber mit Ihrer test.txt-Datei nicht. Warum scheint meine Lösung zu funktionieren, aber nicht wirklich? Vielen Dank für die Hilfe.
dlowrie290
1
@ dlowrie290 Ihre Lösung liest paarweise Zeilen. Wenn Network Administratorzwischen der ersten und der zweiten Zeile dieses Paares aufgeteilt wird, führt Ihre Lösung die Substitution erfolgreich durch. Anschließend werden diese beiden Zeilen gedruckt und das nächste Paar eingelesen. Wenn jedoch die zweite Zeile des ersten Paares mit endet Networkund die erste Zeile des zweiten Paares mit beginnt Administrator, fehlt der Code. Mein Code vermeidet dies, indem er Zeilen einliest, bis er eine findet, die nicht mit endet Network.
John1024
2
Bitte beachten Sie, dass Ihre erste mehrzeilige Lösung auch von GNU-Erweiterungen abhängt sed: Die \nim Ersatz ist nicht im Standard definiert. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'ist eine tragbare Möglichkeit, dies zu tun.
Philippos
@Philippos Ausgezeichnete Punkte. Antwort aktualisiert, um die tragbare Lösung einzuschließen.
John1024
1
Danke für die Klarstellung, John! Auch hier werden großartige Dinge und Ihre Zeit / Bemühungen sehr geschätzt!
dlowrie290