Diskussion:Sed

Aus Foxwiki
Version vom 12. Dezember 2024, 08:47 Uhr von Dirkwagner (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „= Wikipedia = == Programmierung == <code>sed</code>-Anweisungen können grob in drei Gruppen unterteilt werden: Textmanipulationen, Verzweigungen und sonstige. (Die meisten <code>sed</code>-Handbücher wie auch die POSIX-Spezifikation unterteilen abweichend davon Anweisungen in 2-Adress-, 1-Adress- und adresslose – siehe unten –, aber diese Gruppierung ist für Einführungszwecke nicht geeignet.) === Textmanipulationen ===…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Wikipedia

Programmierung

sed-Anweisungen können grob in drei Gruppen unterteilt werden: Textmanipulationen, Verzweigungen und sonstige. (Die meisten sed-Handbücher wie auch die POSIX-Spezifikation unterteilen abweichend davon Anweisungen in 2-Adress-, 1-Adress- und adresslose – siehe unten –, aber diese Gruppierung ist für Einführungszwecke nicht geeignet.)

Textmanipulationen

Dies ist die am weitaus häufigsten eingesetzte Funktion und der Befehlssatz ist hier auch besonders reichhaltig. Generell hat eine Anweisung folgende Struktur (2-Adress-Kommando):

<Adresse1>,<Adresse2> Kommando [Optionen]

Adresse1 und Adresse2 können auch weggelassen werden. Werden beide Adressen angegeben, so wird Kommando für jede Zeile, beginnend mit jener, die mit Adresse1 übereinstimmt, bis zu der, die mit Adresse2 übereinstimmt, ausgeführt. Werden Adresse1 und Adresse2 nicht angegeben, so wird Kommando für jede Zeile ausgeführt, wird lediglich Adresse2 weggelassen, so wird Kommando nur für Zeilen ausgeführt, die mit Adresse1 übereinstimmen. Eine Adresse ist entweder eine Zeilennummer oder ein regulärer Ausdruck. Reguläre Ausdrücke werden dabei in zwei / eingeschlossen. Zwei Beispiele:

sed '/Beginn/,/Ende/ s/alt/NEU/' inputfile
Input Output
x alt
Beginn
y alt
Ende
z alt
x alt
Beginn
y NEU
Ende
z alt

„alt“ wird durch „NEU“ ersetzt, aber nur ab der Zeile, die „Beginn“ enthält, bis zu der Zeile, die „Ende“ enthält (2-Adress-Variante). Hingegen wird dieselbe Ersetzung im zweiten Beispiel in allen Zeilen durchgeführt, die mit „y“ oder „z“ beginnen (1-Adress-Variante):

sed '/^[yz]/ s/alt/NEU/' inputfile
Input Output
x alt
Beginn
y alt
Ende
z alt
x alt
Beginn
y NEU
Ende
z NEU

Zusammengesetzte Kommandos

Anstatt eines einzelnen Kommandos kann Kommando auch eine Liste von Anweisungen enthalten, die durch { … } umschlossen werden. Für diese Anweisungen gelten wieder die oben beschriebenen Regeln, sie können ihrerseits ebenfalls aus weiteren zusammengesetzten Kommandos bestehen. Ein Beispiel:

sed '/^[yz]/ {
               s/^\([yz]\)/(\1)/
               s/alt/NEU/
             }' inputfile
Input Output
x alt
Beginn
y alt
Ende
z alt
x alt
Beginn
(y) NEU
Ende
(z) NEU

Verzweigungen

sed kennt zwei Arten von Verzweigungen: unbedingte Verzweigungen (Sprunganweisungen) und bedingte, die in Abhängigkeit einer zuvor erfolgten oder nicht erfolgten Ersetzungsoperation zur Ausführung kommen. Ein typisches Anwendungsbeispiel ist das folgende: ein Quelltext wurde mit Hilfe von führenden Tabulatorzeichen eingerückt, diese führenden Tabs sollen durch jeweils 8 Blanks ersetzt werden. Andere als am Zeilenbeginn liegende Tabs können im Text vorkommen, sollen aber nicht verändert werden. Das Problem besteht darin, dass multiplikative Verknüpfungen (ersetze N Tabs durch N * 8 Blanks) nicht als RegExp ausgedrückt werden können. Andererseits würde eine globale Ersetzung auch die Tabulatorzeichen innerhalb des Texts betreffen. Deshalb wird mit Sprunganweisungen eine Schleife gebildet (im Folgenden werden Blanks und Tabs zur besseren Verständlichkeit durch <b> und <t> symbolisiert):

sed ':start
     /^<b>*<t>/ {
                  s/^\(<b>*\)<t>/\1<b><b><b><b><b><b><b><b>/
                  b start
                }' inputfile

In jeder Zeile wird das erste Tabulatorzeichen, sofern davor lediglich null oder mehr Leerzeichen stehen, durch 8 Leerzeichen ersetzt, danach sorgt die Sprunganweisung b <Sprungzielname> dafür, dass die Programmausführung wieder zur ersten Zeile zurückkehrt. Ist das letzte führende Tabulatorzeichen ersetzt, so matcht der Ausdruck /^<b>*<t>/ nicht mehr und der Block wird nicht ausgeführt, sodass das Programmende erreicht und die nächste Zeile eingelesen wird.

Hier wird die Ähnlichkeit mit Assembler-Sprachen deutlich, indem mit einer Bedingung und einem Label eine Kontrollstruktur vergleichbar dem in Hochsprachen üblichen repeat-until aufgebaut wird.

Sonstige Anweisungen

Hold Space Manipulation

Eine mächtige (gleichwohl relativ unbekannte) Funktion von sed ist der sogenannte Hold Space. Das ist ein frei verfügbarer Speicherbereich, der in seiner Arbeitsweise dem in manchen Assembler-Sprachen bekannten Akkumulator ähnelt. Direkte Manipulation der Daten im Hold Space ist zwar nicht möglich, aber Daten im Pattern Space können in den Hold Space verlagert, kopiert, oder auch mit dem Inhalt desselben vertauscht werden. Auch das Anhängen des Pattern Spaces an den Hold Space oder vice versa ist möglich.

Das folgende Beispiel verdeutlicht die Funktion des Hold Space: der Text einer „Kapitelüberschrift“ wird gespeichert und jeder Zeile des jeweiligen „Kapitels“ nachgestellt, die Zeile mit der Kapitelüberschrift selbst aber unterdrückt:

sed '/^=/ {
             s/^=//
             s/^/ (/
             s/$/)/
             h
             d
          }
     G; s/\n// ' inputfile
Input Output
=Kapitel1
Zeile 1
Zeile 2
Zeile 3
=Kapitel2
Zeile A
Zeile B
Zeile C
Zeile 1 (Kapitel1)
Zeile 2 (Kapitel1)
Zeile 3 (Kapitel1)
Zeile A (Kapitel2)
Zeile B (Kapitel2)
Zeile C (Kapitel2)

Immer wenn eine Zeile mit „=“ beginnt, so wird der Anweisungsblock ausgeführt, der dieses Zeichen entfernt und dafür die restliche Zeile mit einem führenden Leerzeichen und Klammern versieht. Danach wird dieser Text in den Hold Space kopiert (h) und aus dem Pattern Space gelöscht (d), wodurch das Programm für diese Zeile beendet und die nächste Zeile gelesen wird. Da für „normale Zeilen“ die Bedingung des Eingangsblocks nicht zutrifft, wird lediglich die letzte Anweisung (G) durchgeführt, die den Inhalt des Hold Space an den Pattern Space anhängt.

Mehrzeilen-Anweisungen

Nicht alle Textmanipulationen lassen sich innerhalb einzelner Zeilen ausführen. Manchmal müssen Informationen aus anderen Zeilen in die Entscheidungsfindung miteinbezogen werden, manchmal auch zeilenübergreifende Ersetzungen durchgeführt werden. Dafür sieht die sed-Programmiersprache die Anweisungen N, P und D vor, mit denen mehrere Zeilen des Eingabetexts gleichzeitig in den Pattern Space geladen (N) und Teile davon ausgegeben (P) oder gelöscht (D) werden können. Ein typisches Anwendungsbeispiel ist der folgende Einzeiler (eigentlich zwei Einzeiler), der einen Text mit Zeilennummern versieht:

sed '=' inputfile | sed 'N; s/\n/<t>/'

Der erste sed-Aufruf druckt für jede Zeile im Eingangstext die Zeilennummer aus und danach die Zeile selbst. Der zweite sed-Aufruf verbindet diese beiden Zeilen zu einer einzigen, indem erst die jeweils nachfolgende Zeile eingelesen (N) und dann das automatisch eingefügte Zeilentrennzeichen („\n“) durch ein Tabulatorzeichen ersetzt wird.

Anwendungen, Optionen, Hinweise

Kapazitätsgrenzen

sed unterliegt keinen (realen) Beschränkungen hinsichtlich der Dateigrößen. Abgesehen vom verfügbaren Plattenplatz, der eine praktische Grenze darstellt, realisieren die meisten Implementationen den Zeilenzähler als int oder long int. Bei den heute üblichen 64-Bit-Prozessoren kann die Gefahr eines Überlaufs deshalb vernachlässigt werden.

Wie die meisten textmanipulierenden Tools in UNIX unterliegt sed allerdings einer Begrenzung hinsichtlich der Zeilenlänge (genauer: der Anzahl Bytes bis zum nachfolgenden newline-Zeichen). Die Mindestgröße ist durch den POSIX-Standard festgelegt, die tatsächliche Größe kann von System zu System variieren und kann im jeweiligen Fall in der Kernel-Headerdatei /usr/include/limits.h als Wert der Konstanten LINE_MAX nachgeschlagen werden. Die Länge wird in Bytes angegeben, nicht in Zeichen (weshalb eine Umrechnung etwa bei der Verarbeitung von UTF-codierten Dateien, die einzelne Zeichen mit mehreren Bytes darstellen, nötig ist).

Greedyness

Vorlage:Siehe auch Beim Geltungsbereich von RegExps wird zwischen greedy und non-greedy unterschieden. sed-RegExps sind immer greedy, das bedeutet, dass die RegExp immer den längstmöglichen Geltungsbereich hat:

/a.*B/; "'a', gefolgt von null oder mehr beliebigen Zeichen, gefolgt von 'B'"
axyBBBskdjfhaaBBpweruBjdfh ; längstmöglicher Geltungsbereich (greedy)
axyBBBskdjfhaaBBpweruBjdfh ; kürzestmöglicher Geltungsbereich (non-greedy)

Der Grund ist, dass sed auf Geschwindigkeit optimiert ist und non-greedy RegExps aufwendiges Backtracking erfordern würde. Will man ein Non-greedy-Verhalten erzwingen, so erreicht man dies üblicherweise durch negierte Zeichenklassen. Im obigen Beispiel etwa:

/a[^B]*B/ ; "'a', gefolgt von null oder mehr nicht-'B', gefolgt von 'B'"

Praktische Grenzen in der Shell-Programmierung

Es sollte nicht unerwähnt bleiben, dass die allerhäufigste Anwendung von sed (aber auch von awk, tr und ähnlichen Filterprogrammen) in der Praxis – die Manipulation ad hoc von Ausgaben anderer Kommandos, etwa so:

ls -l /path/to/myfile | sed 's/^\([^ ][^ ]*\) .*/\1/' # gibt Filetype und Filemode aus

genaugenommen einen Missbrauch darstellt. Da jeder Aufruf eines externen Programmes die aufwendigen Systemaufrufe fork() und exec() erfordert, sind Shell-interne Methoden, etwa die sogenannte Variablenexpansion, selbst wenn sie deutlich länger zu schreiben sind, meist dem Aufruf von externen Programmen überlegen.[1] Die Faustregel dafür lautet: wenn die Ausgabe des Filterprozesses eine Datei bzw. ein Datenstrom ist, so ist das Filterprogramm zu verwenden, ansonsten ist Variablenexpansion vorzuziehen.

In-Place-Editing

Aufgrund der Art wie sed Textmanipulationen durchführt, kann dies nicht direkt auf der Eingabedatei geschehen. Als Ausgabe wird eine von dieser getrennte Datei benötigt, die gegebenenfalls danach über die Eingangsdatei kopiert wird.

sed '…<Anweisungen>…' /path/to/inputfile > /path/to/output
mv /path/to/output /path/to/input

Dies ist auch so im POSIX-Standard vorgesehen. Die GNU-Version von sed bietet zusätzlich zum POSIX-Standard die Kommandozeilen-Option -i. Diese erlaubt es, eine Datei scheinbar ohne Umweg (in place) zu verändern, tatsächlich wird aber im Hintergrund ebenfalls eine temporäre Datei angelegt. Diese wird im Fehlerfall nicht gelöscht und die Metadaten (Besitzer, Gruppe, Inode-Nummer, …) der Originaldatei auf jeden Fall verändert.

RegExp-Notation

Es hat sich eingebürgert, regular Expressions – wie auch in den obigen Beispielen – durch Schrägstriche zu begrenzen. sed erfordert dies allerdings nicht. Jedes Zeichen, das einem Ersetzungskommando folgt, wird als Begrenzer akzeptiert und dann in der Folge erwartet. Diese beiden Anweisungen sind deshalb gleichwertig:

s/^\([^ ][^ ]*\) \([^ ][^ ]*\)/\2 \1/ ; vertauscht erstes und zweites Wort einer Zeile
s_^\([^ ][^ ]*\) \([^ ][^ ]*\)_\2 \1_ ; "_" statt "/"

Dies ist praktisch, wenn der Schrägstrich als Teil der RegExp benötigt wird, weil man sich dann das mühsame Escapen (Kenntlichmachen der Verwendung als Literal) ersparen kann. Man weicht dann einfach auf ein anderes, nicht verwendetes Zeichen aus.

Einige typische Verfahren

Löschung von Textteilen

Erfolgt durch Ersetzung durch nichts. Explizite Löschung für Teile einer Zeile ist nur vom Zeilenbeginn bis zum ersten Zeilentrennzeichen (D) vorgesehen. Der Ausdruck

/Ausdruck/d

löscht hingegen NICHT den Teil Ausdruck, sondern jede Zeile, die Ausdruck enthält! Ausdruck fungiert hier als die Adresse (siehe oben, 1-Adress-Variante des Kommandos d).

Ansprechen von mindestens einem Zeichen

Im Umfang der POSIX-BREs ist – im Unterschied zu den GNU-BREs – der Quantor \+ für ein oder mehrere des vorangegangenen Ausdrucks nicht vorgesehen. Um portable sed-Scripte zu schreiben, die nicht nur mit GNU-sed laufen, sollte der Quantor \{min,\} verwendet werden.

/xa\+y/ ; GNU-Variante für "'x' gefolgt von einem oder mehr (aber nicht null) 'a', gefolgt von 'y'"
/xa\{1,\}y/ ; dasselbe in POSIX: "'x' gefolgt von mindestens einem 'a', gefolgt von 'y'"

Ersetzung mehrerer bzw. aller Vorkommen innerhalb einer Zeile

Ohne Angabe weiterer Optionen wird immer nur das erste Vorkommen eines Suchtexts der Ersetzungsregel unterworfen:

sed 's/alt/NEU/' inputfile
Input Output
alt
alt alt
alt alt alt
alt alt alt alt
alt alt alt alt alt
NEU
NEU alt
NEU alt alt
NEU alt alt alt
NEU alt alt alt alt

Dieses Verhalten kann allerdings durch die Angabe von Kommandoptionen geändert werden: Wird eine Zahl N angegeben, dann wird lediglich das N-te Vorkommen geändert, ein g (für global) ändert alle Vorkommen:

sed 's/alt/NEU/g' inputfile
Input Output
alt
alt alt
alt alt alt
alt alt alt alt
alt alt alt alt alt
NEU
NEU NEU
NEU NEU NEU
NEU NEU NEU NEU
NEU NEU NEU NEU NEU

Filtern bestimmter Zeilen

Grundsätzlich gibt sed immer den Inhalt des Pattern Spaces nach der letzten Anweisung aus. Will man dieses Verhalten für einzelne Zeilen unterdrücken, so kann man entweder über eine Regel bestimmte Zeilen löschen (explizite Filterung), aber es ist auch mit der Kommandozeilen-Option -n möglich, dieses Verhalten insgesamt abzustellen (implizite Filterung). Ausgegeben wird dann nur noch, was mit dem ausdrücklichen Print-Kommando (p) angegeben wird. p kann dabei entweder als eigene Anweisung oder als Option für andere Anweisungen dienen. Das Beispiel gibt aus dem bereits oben verwendeten Text nur noch die „Kapitelüberschriften“ aus:

sed -n 's/^=\(.*\)$/Kapitelüberschrift: \1/p' inputfile
Input Output
=Kapitel1
Zeile 1
Zeile 2
Zeile 3
=Kapitel2
Zeile A
Zeile B
Zeile C
Kapitelüberschrift: Kapitel1
Kapitelüberschrift: Kapitel2

Debugging

Zur Fehlersuche kann es nützlich sein, sich Zwischenergebnisse ausgeben zu lassen um die Entwicklung im Pattern Space besser nachvollziehen zu können. Dazu kann die bereits oben erwähnte Option p verwendet werden. Zeilen können durchaus mehrmals hintereinander ausgegeben werden. In dem obigen Beispielprogramm etwa:

sed '/^=/ {
             s/^=//p
             s/^/ (/p
             s/$/)/p
             h
             d
          }
     p
     G' inputfile