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 und sed
EOF angetroffen wird, wird die Verarbeitung beendet. Wenn Sie also in der letzten Zeile stehen und anweisen sed
, 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
und ex
zum Beispiel können sie viel von dem sed
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 umgewandelt sed
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, die sed
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 machen y///
. Wenn Sie häufig feststellen, dass Sie g
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 Fall y/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 :
Die POSIXLY_CORRECT
Umgebungsvariable wird erwähnt, da POSIX angibt, dass sed
EOF beim Versuch, eine EOF zu N
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 jeder sed
Befehl, der einen beliebigen Parameter akzeptiert, an einer \n
neuen Zeile im Skript abgegrenzt wird. Also die Befehle ...
:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...
... sind alle sehr wahrscheinlich fehlerhaft, abhängig von der sed
Implementierung, die sie liest. Tragbar sollten sie geschrieben werden:
...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}
Das gleiche gilt für r
, w
, t
, a
, i
, und c
(und möglicherweise ein paar mehr , dass ich im Moment bin zu vergessen) . In fast allen Fällen könnten sie auch geschrieben werden:
sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
"//{ do arbitrary list of commands" -e \}
... wo die neue -e
xecution-Anweisung für das \n
ewline-Trennzeichen steht. Wenn der GNU- info
Text eine traditionelle sed
Implementierung vorschlägt , müssen Sie Folgendes tun :
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
... es sollte eher sein ...
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}
... 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:
printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
//!g;x;$!d;:nd' -e 'l;$a\' \
-e 'this is the last line'
... welche druckt:
foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line
... weil der t
Befehl est - wie die meisten sed
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 der h
alte Bereich überschrieben . Die Puffer werden als nächstes x
geändert, und eine bedingte s///
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/
. Wenn t
dies erfolgreich ist , verzweigt sich das Skript zum n
ot d
elete-Label, das das Skript überprüft l
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 und n
ext für Lookahead - weil sie vor dem Leitungszyklus vorrücken . Anstatt eine geschlossene Schleife redundant innerhalb einer Schleife zu implementieren - da der sed
Leitungszyklus ohnehin nur eine einfache Leseschleife ist -, ist es wahrscheinlich einfacher, Eingaben wahllos zu sammeln:
sed 'H;1h;$!d;x;...'
... die die gesamte Datei sammeln oder pleite gehen.
eine Randnotiz über N
und Verhalten der letzten Zeile ...
Ich habe zwar nicht die Tools zum Testen zur Verfügung, aber bedenken Sie, dass sich das N
Lesen und die direkte Bearbeitung anders verhält, wenn die bearbeitete Datei die Skriptdatei für das nächste Durchlesen ist.