Das Einlesen einer ganzen Datei in den Musterbereich ist nützlich, um Zeilenumbrüche usw. zu ersetzen. und es gibt viele Fälle, in denen Folgendes empfohlen wird:
sed ':a;N;$!ba; [commands...]'
Es schlägt jedoch fehl, wenn die Eingabe nur eine Zeile enthält.
Bei einer Eingabe mit zwei Zeilen wird beispielsweise jede Zeile dem Ersetzungsbefehl unterzogen:
$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt
Bei der Eingabe einer einzelnen Zeile wird jedoch keine Ersetzung durchgeführt:
$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc
Wie schreibt man einen sed
Befehl, um alle Eingaben auf einmal einzulesen und dieses Problem nicht zu haben?
sed -z
Option von GNU . Wenn Ihre Datei nicht null hat, wird sie bis zum Ende der Datei gelesen! Gefunden von diesem: stackoverflow.com/a/30049447/582917Antworten:
Es gibt viele Gründe, warum das Einlesen einer ganzen Datei in den Musterbereich schief gehen kann. Das logische Problem in der Frage um die letzte Zeile ist ein häufiges. Es hängt mit dem
sed
Zeilenzyklus zusammen - wenn keine Zeilen mehr vorhanden sind undsed
EOF angetroffen wird, wird die Verarbeitung beendet. Wenn Sie also in der letzten Zeile stehen und anweisensed
, eine andere zu bekommen, wird sie genau dort anhalten und nichts mehr tun.Das heißt, wenn Sie wirklich eine ganze Datei in den Musterbereich lesen müssen, lohnt es sich wahrscheinlich, ein anderes Tool in Betracht zu ziehen. Tatsache ist,
sed
ist gleichbedeutend mit dem Stream- Editor - er ist so konzipiert, dass er jeweils eine Zeile oder einen logischen Datenblock bearbeitet.Es gibt viele ähnliche Tools, die besser für die Verarbeitung vollständiger Dateiblöcke geeignet sind.
ed
undex
zum Beispiel können sie viel von demsed
tun, was sie können, und zwar mit ähnlicher Syntax - und noch viel mehr -, aber anstatt nur einen Eingabestream zu bearbeiten, während er wie ausgegeben in eine Ausgabe umgewandeltsed
wird, verwalten sie auch temporäre Sicherungsdateien im Dateisystem . Ihre Arbeit wird nach Bedarf auf die Festplatte gepuffert, und sie werden am Ende der Datei nicht abrupt beendet (und implodieren unter Pufferbelastung viel seltener) . Darüber hinaus bieten sie viele nützliche Funktionen, diesed
in einem Stream-Kontext einfach nicht sinnvoll sind, wie Linienmarkierungen, Rückgängigmachen, benannte Puffer, Verknüpfungen und mehr.sed
Die Hauptstärke liegt in der Fähigkeit, Daten zu verarbeiten, sobald sie gelesen werden - schnell, effizient und im Stream. Wenn Sie eine Datei schlürfen, werfen Sie diese weg, und es treten häufig Randprobleme wie das zuletzt erwähnte Zeilenproblem, Pufferüberläufe und eine miserable Leistung auf. Wenn die analysierten Daten bei der Aufzählung von Übereinstimmungen länger werden, wird die Verarbeitungszeit einer Regexp-Engine länger steigt exponentiell an .In Bezug auf diesen letzten Punkt übrigens: Obwohl ich verstehe, dass der Beispielfall
s/a/A/g
sehr wahrscheinlich nur ein naives Beispiel ist und wahrscheinlich nicht das eigentliche Skript ist, für das Sie eine Eingabe sammeln möchten, lohnt es sich möglicherweise, sich mit ihm vertraut zu macheny///
. Wenn Sie häufig feststellen, dass Sieg
ein einzelnes Zeichen durch ein anderes ersetzen,y
kann dies für Sie sehr nützlich sein. Es ist eine Transformation im Gegensatz zu einer Substitution und geht viel schneller, da es keinen regulären Ausdruck impliziert. Dieser letztere Punkt kann auch nützlich sein, wenn versucht wird, leere//
Adressen beizubehalten und zu wiederholen , da er sie nicht betrifft, aber von ihnen beeinflusst werden kann. In jedem Fally/a/A/
ist dies ein einfacheres Mittel, um dasselbe zu erreichen - und Swaps sind ebenso möglich wie:y/aA/Aa/
Dies würde alle Groß- / Kleinbuchstaben wie in einer Zeile gegeneinander austauschen.Sie sollten auch beachten, dass das Verhalten, das Sie beschreiben, wirklich nicht das ist, was sowieso passieren soll.
Von GNUs
info sed
im Abschnitt GEMEINSAM BERICHTETE BUGS :N
Befehl in der letzten ZeileDie meisten Versionen
sed
beenden, ohne etwas zu drucken, wenn derN
Befehl in der letzten Zeile einer Datei ausgegeben wird. GNUsed
druckt den Musterbereich vor dem Beenden, es sei denn, der-n
Befehlsschalter wurde angegeben. Diese Wahl ist beabsichtigt.Zum Beispiel
sed N foo bar
würde das Verhalten von davon abhängen, ob foo eine gerade oder eine ungerade Anzahl von Zeilen hat. Wenn Sie ein Skript schreiben, um die nächsten Zeilen nach einer Musterübereinstimmung zu lesen, werden Sie bei herkömmlichen Implementierungen vonsed
gezwungen, so etwas wie/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
nur zu schreiben/foo/{ N;N;N;N;N;N;N;N;N; }
.In jedem Fall besteht die einfachste Problemumgehung darin,
$d;N
Skripte zu verwenden, die auf dem herkömmlichen Verhalten basieren, oder diePOSIXLY_CORRECT
Variable auf einen nicht leeren Wert zu setzen.Die
POSIXLY_CORRECT
Umgebungsvariable wird erwähnt, da POSIX angibt, dasssed
EOF beim Versuch, eine EOF zuN
verwenden, ohne Ausgabe beendet werden soll. In diesem Fall verstößt die GNU-Version jedoch absichtlich gegen den Standard. Beachten Sie auch, dass, selbst wenn das Verhalten oben gerechtfertigt ist, davon ausgegangen wird, dass es sich bei dem Fehler um eine Stream-Bearbeitung handelt, bei der nicht eine ganze Datei in den Speicher geschlürft wird.Der Standard definiert
N
das Verhalten folgendermaßen:N
Hängen Sie die nächste Eingabezeile abzüglich der abschließenden
\n
Ewline an den Musterbereich an und verwenden Sie eine eingebettete\n
Ewline, um das angehängte Material vom Originalmaterial zu trennen. Beachten Sie, dass sich die aktuelle Zeilennummer ändert.Wenn keine nächste Eingabezeile verfügbar ist,
N
verzweigt das Befehlsverb zum Ende des Skripts und wird beendet, ohne einen neuen Zyklus zu starten oder den Musterbereich in die Standardausgabe zu kopieren.In diesem Sinne werden in der Frage einige andere GNU-Ismen demonstriert - insbesondere die Verwendung der Klammern für
:
Label,b
Ranch und{
Funktionskontext}
. Als Faustregel gilt, dass jedersed
Befehl, der einen beliebigen Parameter akzeptiert, an einer\n
neuen Zeile im Skript abgegrenzt wird. Also die Befehle ...... sind alle sehr wahrscheinlich fehlerhaft, abhängig von der
sed
Implementierung, die sie liest. Tragbar sollten sie geschrieben werden:Das gleiche gilt für
r
,w
,t
,a
,i
, undc
(und möglicherweise ein paar mehr , dass ich im Moment bin zu vergessen) . In fast allen Fällen könnten sie auch geschrieben werden:... wo die neue
-e
xecution-Anweisung für das\n
ewline-Trennzeichen steht. Wenn der GNU-info
Text eine traditionellesed
Implementierung vorschlägt , müssen Sie Folgendes tun :... es sollte eher sein ...
... das stimmt natürlich auch nicht. Das Skript auf diese Weise zu schreiben ist ein wenig albern. Es gibt viel einfachere Mittel, um dasselbe zu tun, wie:
... welche druckt:
... weil der
t
Befehl est - wie die meistensed
Befehle - vom Zeilenzyklus abhängt, um sein Rückgaberegister zu aktualisieren, und hier der Zeilenzyklus den größten Teil der Arbeit ausführen darf. Dies ist ein weiterer Kompromiss, den Sie eingehen, wenn Sie eine Datei schlürfen. Der Zeilenzyklus wird nie wieder aktualisiert, und so viele Tests verhalten sich abnormal.Der obige Befehl riskiert nicht, die Eingabe zu überschreiten, da nur einige einfache Tests durchgeführt werden, um zu überprüfen, was beim Lesen gelesen wird. Bei
H
alt werden alle Zeilen an den Haltebereich angehängt, aber wenn eine Zeile übereinstimmt/foo/
, wird derh
alte Bereich überschrieben . Die Puffer werden als nächstesx
geändert, und eine bedingtes///
Ersetzung wird versucht, wenn der Inhalt des Puffers mit dem//
zuletzt adressierten Muster übereinstimmt . Mit anderen Worten, es wird//s/\n/&/3p
versucht, die dritte neue Zeile im Haltebereich durch sich selbst zu ersetzen und die Ergebnisse auszudrucken, wenn der Haltebereich derzeit übereinstimmt/foo/
. Wennt
dies erfolgreich ist , verzweigt sich das Skript zumn
otd
elete-Label, das das Skript überprüftl
und abschließt .In dem Fall, dass beide
/foo/
und eine dritte neue Zeile im Haltebereich nicht miteinander abgeglichen werden können,//!g
wird der Puffer überschrieben, wenn er/foo/
nicht übereinstimmt, oder, wenn er übereinstimmt, wird der Puffer überschrieben, wenn eine\n
neue Zeile nicht übereinstimmt (wodurch er/foo/
durch ersetzt wird) selbst) . Dieser kleine subtile Test verhindert, dass sich der Puffer für lange Strecken unnötig füllt,/foo/
und stellt sicher, dass der Prozess schnell bleibt, da sich die Eingabe nicht stapelt. In einem No-/foo/
oder//s/\n/&/3p
Fail-Fall werden die Puffer erneut ausgetauscht und jede Zeile bis auf die letzte wird dort gelöscht.Das Letzte - die letzte Zeile
$!d
- ist eine einfache Demonstration, wie ein Top-Down-sed
Skript erstellt werden kann, um mehrere Fälle einfach zu behandeln. Wenn Ihre allgemeine Methode darin besteht, unerwünschte Fälle, die mit den allgemeinsten beginnen und auf die spezifischsten hinarbeiten, zu beseitigen, können Randfälle einfacher behandelt werden, da sie einfach mit Ihren anderen gewünschten Daten und wann bis zum Ende des Skripts durchfallen dürfen Sie haben nur noch die gewünschten Daten. Es kann jedoch weitaus schwieriger sein, solche Randfälle aus einer geschlossenen Schleife abzurufen.Und hier ist das Letzte, was ich zu sagen habe: Wenn Sie wirklich eine ganze Datei einlesen müssen, können Sie es ertragen, etwas weniger Arbeit zu erledigen, indem Sie sich auf den Leitungszyklus verlassen, um dies für Sie zu tun. Normalerweise verwenden Sie
N
ext undn
ext für Lookahead - weil sie vor dem Leitungszyklus vorrücken . Anstatt eine geschlossene Schleife redundant innerhalb einer Schleife zu implementieren - da dersed
Leitungszyklus ohnehin nur eine einfache Leseschleife ist -, ist es wahrscheinlich einfacher, Eingaben wahllos zu sammeln:... die die gesamte Datei sammeln oder pleite gehen.
eine Randnotiz über
N
und Verhalten der letzten Zeile ...quelle
H
erste Stelle zu setzen ist schön.:a;$!{N;ba}
wie oben erwähnt - es ist auf lange Sicht einfacher, Standardformulare zu verwenden, wenn Sie versuchen, reguläre Ausdrücke auf unbekannten Systemen auszuführen. Aber das war nicht wirklich das, was ich meinte: Sie implementieren eine geschlossene Schleife - Sie können nicht so einfach in die Mitte kommen, wenn Sie möchten, wie Sie möchten, indem Sie sich verzweigen - unerwünschte Daten bereinigen - und den Zyklus zulassen. Es ist wie eine Top-Down-Sache - alles,sed
was es tut, ist ein direktes Ergebnis dessen, was es gerade getan hat. Vielleicht sehen Sie es anders - aber wenn Sie es versuchen, wird das Skript möglicherweise einfacher.Es schlägt fehl, weil der
N
Befehl vor dem Mustervergleich$!
(nicht in der letzten Zeile) kommt und sed vor jeder Arbeit beendet wird:Dies kann leicht behoben werden, um auch mit einzeiligen Eingaben zu arbeiten (und in jedem Fall klarer zu sein), indem einfach die Befehle
N
undb
nach dem Muster gruppiert werden :Es funktioniert wie folgt:
:a
Erstellen Sie ein Label mit dem Namen "a".$!
wenn nicht die letzte Zeile, dannN
Fügen Sie die nächste Zeile an den Musterbereich an (oder beenden Sie sie, wenn keine nächste Zeile vorhanden ist) undba
verzweigen Sie die Bezeichnung 'a'.Leider ist es nicht portabel (da es auf GNU-Erweiterungen basiert), aber die folgende Alternative (von @mikeserv vorgeschlagen) ist portabel:
quelle
:a;N;$!ba;
.