computerseite-spezial.de

handverlesene Infos zu Linux, FreeBSD und OpenSolaris

  • Full Screen
  • Wide Screen
  • Narrow Screen
  • Increase font size
  • Default font size
  • Decrease font size
Sie sind hier: Startseite Besondere Programme Effektives automatisiertes Bearbeiten von Daten mit sed

Effektives automatisiertes Bearbeiten von Daten mit sed

Dieser Beitrag ist erschienen in freiesMagazin (Link) 03/2010 / Lizenz: GNU Free Documentation License (GNU FDL) (Link) / Autor: Christian Brabandt.

Wenn Sie diesen Artikel weiterverwenden möchten, beachten Sie bitte die Lizenzbedingungen. Vielen Dank.




Effektives automatisiertes Bearbeiten von Daten mit sed

von Christian Brabandt


D
ieser Artikel gibt einen Überblick über GNU sed [1] und seine Anwendungsmöglichkeiten. Andere sed-Versionen sollten genauso funktionieren, können sich aber in Details leider durchaus unterscheiden. Eine schöne Übersicht über Inkompatibiltäten finden sich in den sed-FAQ auf SourceForge [2].

Was ist sed?

sed ist eine Abkürzung und steht für „Stream Editor“, was bedeutet, dass es sich dabei um einen nicht-interaktiven Editor handelt, der somit für die Stapelverarbeitung sehr gut geeignet ist.

Während man bei einem interaktiven Editor typischerweise den Cursor bewegt und an bestimmten Stellen auf dem Monitor Veränderungen vornimmt, arbeitet man bei sed in Ermangelung eines Cursors mit Aktionen, die typischerweise durch Muster oder Zeilennummern spezifiziert werden. Das klingt im ersten Moment etwas seltsam, aber man kann damit sehr flexibel mit einem Einzeiler Aktionen durchführen.

Das heißt, sed liest die zu bearbeitende Datei normalerweise zeilenweise in einen Puffer (den „Pattern Space“), prüft, ob die angegebenen Bedingungen zutreffen und, falls ja, führt es die Änderungen durch und gibt diesen Puffer wieder aus.

So kann man zum Beispiel einfach in einer Eingabe alle Vorkommen von dem falschen, englischen Artikel „teh“ durch die korrekte Version „the“ ersetzen. Oder man kann alle Kommentarzeichen aus einer Konfigurationsdatei entfernen. Es gab sogar schon Leute, die mit sed Spiele programmiert, einen Debugger implementiert oder einen Webserver umgesetzt haben.

Dies alles, obwohl die Sprache keine Variablen kennt und nur eingeschränkte Kontrollstrukturen. Seine Sprachelemente erscheinen auf den ersten Blick etwas kryptisch und sehen oft auch aus wie feinster Buchstabensalat.

sed hat, wie so viele praktische Werkzeuge, seinen Ursprung in der Unix-Welt und wurde 1973 oder 1974 (Unix Version 4) als Erweiterung bzw. Nachfolger von grep und ed entwickelt. Die verwendete Syntax und Lexik beeinflusste auch maßgeblich Programme wie vi, perl, awk und vermutlich auch die ein oder andere Shell.

Da sed so weit verbreitet ist, gehört es auch zur POSIX Single Unix Specification beziehungsweise zum Standard IEEE 1003.1 (beide Standards bezeichnen dasselbe, nachzulesen auf der Seite von „The Open Group“ [3]). Beide Standards definieren u. a. Schnittstellen und Werkzeuge, die auf einem System vorhanden sein müssen, um den Markennamen UNIX tragen zu dürfen.

Damit ist der grundsätzliche Befehlssatz und die Grammatik von sed, die man erwarten kann, auch schon standardisiert. Falls wider Erwarten kein sed enthalten sein sollte, kann man es leicht über die Paketverwaltung über das gleichnamige Paket nachinstallieren.

sed und reguläre Ausdrücke

sed arbeitet wie oben erwähnt mit Mustern, die bestimmte Teile definieren, an denen Änderungen vorgenommen werden sollen. Diese Muster werden typischerweise durch reguläre Ausdrücke definiert.

GNU sed unterstützt dabei einfache reguläre Ausdrücke (Basic Regular Expressions) bzw. mit dem Schalter -r erweiterte reguläre Ausdrücke (Extended Regular Expressions), z. B. reguläre Ausdrücke, die von egrep benutzt werden.

Etwas vereinfacht ausgedrückt, ist der Unterschied zwischen einfachen regulären Ausdrücken und erweiterten regulären Ausdrücken der, dass bei den ersten der \ genutzt wird, um die Sonderbedeutung der Zeichen einzuschalten, während bei letzteren der \ die Sonderbedeutung der Zeichen ausschaltet.

Reguläre Ausdrücke kurz erklärt

Bei regulären Ausdrücken steht normalerweise jedes Zeichen für sich selbst. Besondere Bedeutung haben jedoch folgende Zeichen, die man sich einmal genauer anschauen sollte:

Anker

^
steht für den Zeilenanfang.
$
steht für das Zeilenende.
.
steht für irgendein Zeichen.

Zeichenklassen

[... ]
steht für irgendein Zeichen innerhalb der eckigen Klammer.
[a-z]
passt auf alle Kleinbuchstaben des Alphabets, [abc] entweder auf a oder auf b oder auf c.
[^... ]
passt auf irgendein Zeichen, welches nicht innerhalb der eckigen Klammer angegeben ist.
^[a-z]
passt auf alle Zeichen, die keine Kleinbuchstaben sind. (Der Bereich, der durch diese Angabe definiert ist, wird maßgeblich durch die Lokalisierung auf dem jeweiligen System bestimmt. Hierfür sind vor allem die beiden Umgebungsvariablen LC_ALL und LC_CTYPE verantwortlich.)

Quantifier

*
steht für kein bis beliebig häufiges Auftreten, dabei wird ein längerer passender Ausdruck bevorzugt (sogenannte Greediness, „Gier“). a* passt also auf „ “, „a“, „aa“, „aaa“ und so weiter, bei einer Zeichenkette „baah“ wird es aber immer auf „aa“ passen.
\?
steht für kein- oder einmaliges Auftreten (als erweiterter regulärer Ausdruck: ?).
\+
steht für genau ein- oder mehrmaliges Auftreten (bei erweiterten regulären Ausdrücken +). Längere Treffer werden dabei bevorzugt. Ursprünglich kennen die elementaren regulären Ausdrücke diesen Quantifier nicht. Mittlerweile wird aber \+ auch meist unterstützt. Falls es die Unterstützung für \+ nicht vorhanden sein sollte, so kann man diesen Quantifier auch mit * nachbilden. a\+ ist nämlich gleichbedeutend mit aa* (findet eine beliebige Anzahl a, aber mindestens 1).

Verschachtelung

\( \)
steht für Klammerung (bei erweiterten regulären Ausdrücken ohne Backslash, also ( )). Damit kann man sich Ausdrücke merken und sie später mit den Variablen \0 bis \9 referenzieren. Das heißt, man kann maximal zehn verschiedene Zeichenketten referenzieren, mehr geht nicht. Dabei zählt immer die Reihenfolge der öffnenden Klammer. \0 bezieht sich dabei auf den kompletten Match und bedeutet das gleiche wie &. \(a\)b\1 passt auf „aba“ aber nicht auf „abba“.

Sonstiges

\|
passt entweder auf den vorherigen Ausdruck oder den nachfolgenden Ausdruck (bei erweiterten regulären Ausdrücken | - ). a\|b passt auf a oder auf b.
\n
steht für den Zeilenumbruch. Hier sollte man auch beachten, dass sed seine Eingabe zeilenorientiert liest. Das heißt, normalerweise wird ein Muster mit einem \n für einen Zeilenumbruch nicht passen. Man kann zwar damit arbeiten, aber man sollte das im Hinterkopf behalten, wenn man \n benutzt.)
\$
passt auf ein $-Zeichen (denn $ steht ja für das Ende der Zeile. So kann man mit dem Backslash nachstehende Zeichen maskieren. \* passt auf einen *, \& passt auf das Kaufmanns-Und usw.

Das ist soweit das Wichtigste zu den regulären Ausdrücken, die von sed unterstützt werden. Die Syntax ist nicht so schwer, man sollte sich nur merken, welche der vielen Dialekte der regulären Ausdrücke sed beherrscht.

Darüber hinaus unterstützt GNU sed die POSIX Zeichenklassen, d. h. das Muster [[:lower:]] passt auf alle Kleinbuchstaben und [[:upper:]] auf alle Großbuchstaben. Außerdem beherrscht GNU sed noch Muster, die mit einem Backslash anfangen. \w passt zum Beispiel auf ein „Word-Character“, als Zeichenklasse ausgedrückt: [A-Za-z0-9_], \W passt auf ein „Non-Word-Character“ und ist somit die Umkehrung von \w (also [^a-zA-Z0-9_]). Die beiden Muster \< und \> bedeuten den Start beziehungsweise das Ende des Wortes. Weitere \-Muster werden auch unterstützt, dafür sei aber letzten Endes auf die Manpages bzw. Infopages verwiesen.

Sed und fortgeschrittene Eigenschaften regulärer Ausdrücke

So fortgeschrittene Sachen wie „Look-Around Assertions“, „Non-Greedy Matches“, „Non-Capturing Groups“ oder auch rekursive Ausdrücke der perlkompatiblen regulären Ausdrücke beherrscht sed leider nicht. Eine gute Übersicht über die vorhandenen Möglichkeiten gibt schließlich die Wikipedia [4].

Adressierung von Zeilen

Diese Ausdrücke definieren nun, wo etwas gemacht wird. Anstelle von Mustern kann man aber auch die Zeilennummern angeben. $ als Zeilennummer passt dabei immer auf die letzte Zeile.

Die Adressierung erfolgt entweder durch Angabe genau der Zeile, für die etwas gemacht werden soll oder alternativ durch Angabe eines Bereiches. Einen Bereich trennt man dann durch ein Komma ab. Die Angabe 1,$ ist gleichbedeutend mit der Angabe von der ersten Zeile bis zur letzten und kann alternativ durch % angegeben werden.

Zusätzlich erlaubt es GNU sed noch, Schrittfolgen zu definieren. Also zum Beispiel von Zeile 1 ausgehend, jede zweite (also 1, 3, 5, 7, ...). 1~2 passt genau auf dieses Muster, während 0~2 jede gerade Zeile ausgibt. Verallgemeinert gesagt bezeichnet x~y also ein Muster, in dem x die Startzeile angibt und y die Schrittfolge.

Kommandos

Welche Aktionen durchgeführt werden, bestimmen die Kommandos. Diese Kommandos verstehen entweder keine Adressen (z. B. Sprungangaben und Kommentare), eine oder keine Adressen oder zwei Adressen (nämlich Start- und End-Adresse).

Die folgenden Kommandos kennt dabei GNU sed:

#
Die Raute kennzeichnet einen Kommentar.
s/x/y/
ersetze x durch y.
:
Der Doppelpunkt definiert bei sed eine Sprungmarke.
=
Das Gleichheitszeichen gibt die Zeilennummer aus.
a\Text
hängt „Text“ als neue Zeile an.
i\Text
fügt „Text“ vor der aktuellen Zeile ein.
c\Text
ersetzt die aktuelle Zeile durch „Text“.
q
beendet sed.
r foobar
hängt den Inhalt der Datei foobar hinter die aktuelle Zeile an.
R foobar
fügt eine Zeile der Datei foobar hinter die aktuelle Zeile an.
b foobar
weist sed an, zur Sprungmarke foobar zu springen.
p
Gib die Treffer aus.
t foobar
Springe zur Sprungmarke foobar, wenn das letzte s-Kommando erfolgreich war.
T foobar
Springe zur Sprungmarke foobar, wenn das letzte Kommando nicht erfolgreich war.
d
Lösche die aktuelle eingelesene Zeile und fange von vorne an.
n
Lies die nächste Zeile ein.
N
Lies die nächste Zeile ein und hänge sie an die aktuelle Zeile an.
w foobar
Schreibe den aktuellen Inhalt in die Datei foobar.
W foobar
Schreibe ausschließlich die erste Zeile des aktuellen Puffers in die Datei foobar.
y/a/b/
Ersetze a durch b; funktioniert so ähnlich wie tr. Mit y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ werden alle Kleinbuchstaben durch Großbuchstaben ersetzt.
!
weist sed an, das jeweils nachfolgende Kommando für alle Zeilen außer der aktuellen Zeile auszuführen.

Normalerweise liest sed die Eingabe (bis zum Zeilenende \n) in den Arbeitspuffer (den sogenannten „Pattern Space“), führt die Kommandos dort aus und gibt anschließend die Änderungen aus. Daneben gibt es noch einen extra Puffer, den sogenannten „Hold Space“. In ihm kann man Daten zwischenspeichern und später wieder verwenden. Dafür gibt es die folgenden Kommandos:

D
Lösche bis zum ersten Zeilenumbruch im Pattern Space und fange dann wieder von vorne an.
x
Tausche die Inhalte des Pattern Spaces und Hold Spaces aus.
h
Kopiere den Pattern Space an den Hold Space Puffer.
H
Hänge den Pattern Space an den Hold Space Puffer an.
g
Kopiere den Hold Space Puffer in den Pattern Space.
G
Hänge den Hold Space Puffer an den Pattern Space an.

Adressen und Kommandos definieren die Aktionen

Durch Klammerung mit { kann man definieren, welche Kommandos logisch zusammengehören und entsprechend zusammen ausgeführt werden. Der Aufbau eines Kommandos und der Adressangabe ist dabei folgender: Adresse1,Adresse2 {Kommando1; Kommando2; Kommando3; ...; KommandoN}. Muster werden durch Slashes / getrennt angegeben: /Muster1/,/Muster2/ {Kommando1; Kommando2}

.

Durch die Angabe zweier Adressen definiert man einen Bereich, in dem die nachfolgenden Kommandos ausgeführt werden (inklusive der Start- und Endzeilen). Wenn man nur eine Adresse angibt, dann wird das Kommando nur an dieser einen Stelle ausgeführt. Wenn man nur ein Kommando ausführen will, kann man die Klammerung auch weglassen. Werden alle Adressen weggelassen, bezieht sich das Kommando immer auf deie komplette Eingabe (also von Zeile 1 bis zur letzten Zeile).

sed anhand von Beispielen erklärt

Nachdem nun die Grundlagen bekannt sind, wird sed abschließend noch anhand von Beispielen erklärt. Einige dieser Beispiele sind den „Sed 1 Liners“ [5] entnommen.

$ sed -n '3p'

Hier gibt sed die Zeile 3 und auschließlich die Zeile 3 aus.

$ sed -n '3,5p'

Und hier wird alles von Zeile 3 bis 5 ausgegeben (inklusive dieser Zeilen). Normalerweise wird bei sed der Input immer auch ausgegeben; mit -n unterdrückt man das, und nur wenn man es explizit verlangt (Kommando p) wird etwas ausgegeben.

Das einfachste Kommando ist das #-Kommando. Allein für sich macht das gar nichts und gibt einfach alles aus:

$ sed '#' foobar
Der Fuchs ist rot und rot sind auch Aepfel.

Alternativ kann man auch das leere Kommando angeben:

$ sed “ foobar
Der Fuchs ist rot und rot sind auch Aepfel.

Das Gleiche erreicht man auch mit dem „Kommando“ ;, welches eigentlich verschiedene Kommandos trennt. In diesem Fall führt es ein „Null“-Kommando aus und gibt daher alles aus, was es einliest:

$ sed ';' foobar
Der Fuchs ist rot und rot sind auch Aepfel.

Das verbreitetste Kommando ist sicherlich das s-Kommando. Damit kann man nach einem Muster suchen und dieses durch ein anderes ersetzen. Zum Beispiel ersetzt

$ sed 's/rot/gruen/' foobar

die Zeichenfolge „rot“ durch „gruen“ und gibt die Änderung aus:

$ sed 's/rot/gruen/' foobar
Der Fuchs ist gruen und rot sind auch Aepfel.

Naja, aber eigentlich ist der Fuchs ja rot und Äpfel sind grün. Daher kann man dem s-Kommando noch zusätzliche Flags mitgeben. Das bekannteste Flag ist das g-Flag. Damit werden alle gefundenen Muster ersetzt:

$ sed 's/rot/gruen/g' foobar
Der Fuchs ist gruen und gruen sind auch Aepfel.

Man kann aber auch nur bestimmte Muster ersetzen, zum Beispiel nur das 2. Muster:

$ sed 's/rot/gruen/2' foobar
Der Fuchs ist rot und gruen sind auch Aepfel.

Wenn kein Flag angegeben wird, wird implizit die 1 angenommen (also das erste gefundene Muster wird ersetzt).

Ein weiteres Flag ist das i-Flag. Hierbei wird das Muster „case-insensitive“ gesucht, d. h. Klein-/Großbuchstaben spielen keine Rolle. Es ist auch möglich, mehrere Flags miteinander zu kombinieren:

$ sed 's/ROT/blau/ig' foobar
Der Fuchs ist blau und blau sind auch Aepfel.

Manchmal möchte man aber auch Pfadangaben ersetzen. Blöd nur, dass der Slash / normalerweise das Muster vom Ersetzungstext trennt. Möchte man nun z. B. /usr/local/share/foobar durch /usr/share/foobar ersetzen, müßte man sowas schreiben:

$ sed 's/\/usr\/local\/share\/foobar/\/usr\/share\/foobar/g' script

Daher erlaubt sed es, irgendein anderes Trennzeichen zu nutzen, z. B. die Pipe |:

$ sed 's|/usr/local/share/foobar|/usr/share/foobar|g' script

Oder man entscheidet sich für den Unterstrich _:

$ sed 's_/usr/local/share/foobar_/usr/share/foobar_g' script

Oder auch irgendein anderes Zeichen, was nicht im Muster vorkommt.

Manchmal möchte man auch das gefundene Muster im Ersetzungsteil wieder verwenden. Wenn man z. B. eine Anweisung in einer Konfigurationsdatei auskommentieren möchte, die mit „foobar“ anfängt, kann man das so machen:

$ sed 's/^\(foobar\)/#\1/' config
#foobar wichtig!

Oder man benutzt einfach den Replacement Character &, der für das Muster steht, das gefunden wurde:

$ sed 's/^\(foobar\)/#&/' config
#foobar wichtig!

Vereinfachen kann man das noch so:

$ sed '/^foobar/s/^/#/' config
#foobar wichtig!

Hier benutzt man den regulären Ausdruck /^foobar/ als Adresse, und nur Zeilen, die ausschließlich auf dieses Muster passen, werden ersetzt. Vereinfacht gesagt, ersetzt man das Zeichen ^ (das ja für den Zeilenanfang steht) durch ein # und fügt somit noch eine Raute an den Anfang der Zeile ein.

Aber natürlich kann man noch mehr mit regulären Ausdrücken machen. Zum Beispiel Kleinbuchstaben durch Großbuchstaben ersetzen:

$ ls /home/ | sed 's/[[:lower:]]/\U&/g'
CB
CHRISBRA
FTP

sed sucht nach der Posix-Klasse von Kleinbuchstaben und ersetzt diese durch die entsprechenden Großbuchstaben. Das \U steht für „Uppercase“ und wandelt alle Zeichen in die passenden Großbuchstaben um. Man beachte die Nutzung von &, um das gefundene Muster wieder zu verwenden. Ohne das Flag g wäre übrigens nur der erste Buchstabe groß geschrieben worden. Das gleiche kann man übrigens mit dem Kommando y/ erreichen:

$ ls /home/ | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'

CB
CHRISBRA
FTP

Das ist etwas umständlich, denn das y-Kommando scheint keine Zeichenklassen zu unterstützen, sondern man muss explizit alle Zeichen hinschreiben.

cat -n simulieren

Ein einfaches cat -n macht nichts weiter als die Datei inklusive Zeilennummern auszugeben:

$ cat -n datei
     1  eins
     2  zwei
     3  drei

Mit sed kann man das nun wie folgt simulieren:

$ sed -e '=' datei | sed -e '/^[0-9]\+$/{N;s/\n/ /}'
1 eins
2 zwei
3 drei

Hier werden zwei sed-Prozesse benötigt, weil der erste nichts weiter macht, als in die Ausgabe die Zeilennummer mit Zeilenendezeichen \n getrennt zu schreiben. Nachfolgende sed-Befehle können diese Zeilen nicht mehr ändern. Daher wird ein zweiter sed-Prozess gestartet, der alle Zeilen auswählt, die nur mit Zahlen anfangen (der reguläre Ausdruck dazu ist ^[0-9]\+$), für diese Zeile die nächste Zeile einliest (N) und danach per Substitute-Befehl das Zeilenendezeichen \n durch ein Leerzeichen ersetzt. Es ist wichtig, dass zunächst die nachfolgende Zeile eingelesen wird, ansonsten sieht sed nicht das Zeilenendezeichen \n

.

Die Lösung ist schon nahezu perfekt. Wünschenswert wäre es vielleicht noch, dass man die Eingabe eingerückt ausgibt. Außerdem trennt cat -n die Ausgaber per Tabulatorzeichen \t, wie ein Hexdump bestätigt.

Eine vollständige Lösung samt der gewünschten Ausgabe könnte schließlich mit dem folgenden Einzeiler erreicht werden:

$ sed -e '=' datei | sed -e '/^[0-9]\+$/{N;s/\n/\t/;s/^/     /}'
     1  eins
     2  zwei
     3  drei

wc -l simulieren

Wie man vielleicht weiß, kann man mit dem Programm wc - neben einigen anderen Möglichkeiten - über den Schalter -l auch die jeweilige Anzahl der Zeilen für die aufgerufene Datei ausgeben:

$ wc -l datei
3 lines

Auch sed kann die Anzahl der Zeilen angeben:

$ sed -n '$=' datei
3

Hier wurde der Schalter -n benutzt. Normalerweise kopiert sed jede Zeile der Eingabe in die Ausgabe. Dies wird durch den Schalter -n verhindert und sed schreibt nur eine Ausgabe, wenn es explizit verlangt wird (zum Beispiel mit dem Print-Kommando). Die regulären Ausdrücke $= sind wieder eine Adressangabe ($ steht für die letzte Zeile) und die Anweisung, die Zeilennummer zu schreiben. Das heißt, wenn sed die letzte Zeile einliest, wird es die aktuelle Zeilennummer ausgeben, die zufällig mit der Anzahl der Zeilen in der Eingabe übereinstimmt.

tac emulieren

sed kennt, wie schon gesagt, neben dem Verarbeitungspuffer (Pattern Space) noch den Hold Space, in dem Daten temporär abgelegt werden können. Von diesem temporären Puffer wird hier Gebrauch gemacht:

$ sed '1!G;h;$!d' datei
drei
zwei
eins

Das h als Anweisung bedeutet jetzt, dass die aktuell bearbeitete Zeile in diesen Puffer kopiert wird.

Die erste Anweisung 1!G weist sed an, den Inhalt dieses Zwischenspeichers an die aktuell zu bearbeitende Zeile mit Zeilenendezeichen \n getrennt anzuhängen (außer in der ersten Zeile, denn zu diesem Zeitpunkt ist der Zwischenspeicher noch leer). Diese Eingabe wird dann wiederum in den temporären Puffer geschrieben.

Die letzte Anweisung $!d bedeutet „lösche jede Zeile außer der letzten“ (! entspricht einem Nicht-Operator).

Wenn sed die letzte Zeile liest, fügt es den Inhalt aller vorherigen Zeilen an und gibt diese wieder aus. Durch die Art, wie die Zeilen angehängt werden, entspricht diese Reihenfolge genau der umgekehrten Reihenfolge der Eingabe.

Eine alternative Anweisung, in der sed explizit nur die letzte Zeile ausgibt und ansonsten keine Ausgabe erzeugt, könnte auf folgende Art realisiert werden:

$ sed -n '1!G;h;\$p' datei
drei
zwei
eins

Dieser sed-Einzeiler enthält die folgenden drei Anweisungen: 1!G, h, $!p. Dabei bedeutet 1!G nun, dass der Inhalt jeder Zeile, außer der ersten Zeile, in den temporären Puffer (Hold Space) geschrieben wird.

Zeilen mit Backslash-Ende in eine Zeile schreiben

Möchte man zwei durch einen Backslash getrennte Zeilen zu einer zusammenfügen, hilft der folgende Ausdruck:

$ sed -e ':a' -e '/\\$/{N;s/\\\n//;ta}'

In der ersten Anweisung wird die Sprungmarke :a definiert. Das erlaubt es, später wieder dorthin zu springen. In der nächsten Anweisung sucht sed nach Zeilen, die mit einem Backslash aufhören, wobei mit \\$ der Backslash einmal maskiert werden muss, sonst würde sed nach dem $-Literal suchen. Für diese Zeilen liest sed die nächste Zeile ein (Kommando N) und löscht anschließend die Zeichenfolge „\“, gefolgt von einem Zeilenende.

Dadurch sind nun die ehemals zwei getrennten Zeilen wieder zu einer Zeile zusammengefügt. Nachdem dies geschehen ist, springt sed zur Sprungmarke :a und fängt wieder von vorne an (falls die neue Zeile ebenfalls mit einem \ endet). Wenn nichts gefunden wird, wird die Zeile ausgegeben.

grep emulieren

sed sucht nach Zeilen, die das Suchmuster enthalten. Alle Zeilen, die nicht auf das Muster passen, werden gelöscht und nicht ausgegeben.

$ sed '/eins/!d' datei
eins

Eine alternative Lösung besteht im folgenden Einzeiler:

$ sed -n '/eins/p' datei
eins

Hier wird sed nur dann etwas ausgeben, wenn es dazu angewiesen wird. Und in diesem Fall wird sed dazu angewiesen, wenn genau das Muster /eins/ auf die die verarbeitete Zeile passen sollte.

tail -10 emulieren

Im folgenden Fall wird sed immer maximal zehn Zeilen in seinem Puffer halten.

$ sed -e ':a' -e '$q;N;11,$D;ba' /var/log/syslog

Sobald sed die letzte Zeile erreicht, wird der komplette Puffer ausgegeben. Jede eingelesene Zeile veranlasst sed dazu, die nächste Zeile einzulesen (N) und nachdem es zehn Zeilen eingelesen hat, wird sed immer die erste eingelesene Zeile löschen (D) und mit der nächsten Zeile fortsetzen. Zum Schluss wird sed angewiesen, zur Sprungmarke :a zu springen und von vorne anzufangen. Dadurch wird sichergestellt, dass niemals mehr als zehn Zeilen im Puffer sind.

Leere Zeilen löschen

Leere Zeilen sind oft unnötiger Ballast und sollen entfernt werden. In so einem Fall kann man den nächsten Einzeiler anwenden.

$ sed '/./!d' datei

Der Punkt . passt nur auf Zeilen, die mindestens ein Zeichen enthalten. Leere Zeilen erfüllen diese Bedingung nicht und würden daher aus der Eingabe gelöscht. Alternativ kann man mit

$ sed '/^$/d' datei

das Gleiche erreichen. Hier wird sed angewiesen, explizit alle Zeilen, die nur aus Zeilenanfang und Zeilenende bestehen (also keinen Inhalt haben), zu löschen.

Alle Zeilenendezeichen entfernen

Dies ist eine interessante Aufgabenstellung. Normalerweise kann man mit sed nicht das Zeilenende suchen, weil sed seine Eingabe zeilenweise verarbeitet.

$ sed -e ':x' -e 'N;$!bx;s/\n//g' datei
einszweidrei

Man muss sed also anweisen, erst die komplette Datei in seinen Puffer zu kopieren, bevor man anfängt, das Zeilenendezeichen \n zu ersetzen. Daher wird zunächst die Sprungmarke :x definiert. Anschließend wird sed angewiesen, so lange die nächste Zeile einzulesen, bis es die letzte Zeile gelesen hat. Nachdem sed die nächste Zeile eingelesen hat, wird so lange zur Sprungmarke :x gesprungen, bis die letzte Zeile im Zwischenspeicher enthalten ist. Wenn dies der Fall ist, kann auch das Zeilenendezeichen \n gesucht und gelöscht werden.

Da in diesem Fall immer die komplette Datei in den Puffer gelesen wird, kann es bei großen Dateien zu Geschwindigkeitseinbußen kommen. Es ist daher eigentlich auch nicht empfehlenswert, diese Aufgabe durch sed erledigen zu lassen. Geeigneter wäre zum Beispiel tr:

$ tr -d '\n' <datei
einszweidrei

Fazit

Abschließend kann festgestellt werden, dass sed ein durchaus flexibles Werkzeug ist, wenn man per Stapelverarbeitung Dateien bearbeiten muss. Durch seine Eigenschaft, Dateien immer zeilenweise zu verarbeiten und durch die Verwendung von Basic/Extended Regular Expressions ist es jedoch nur mit Einschränkungen zu empfehlen, wenn man komplexere Bearbeitungen über mehrere Zeilen vornehmen möchte oder wenn man kompliziertere reguläre Ausdrücke mit „Look-Around-Assertions“ benötigt. Es lohnt sich aber auf jeden Fall, sich mit den vielfältigen Möglichkeiten von sed vertraut zu machen. Gerade für kleinere Aufgaben ist sed hervorragend geeignet und es muss ja auch nicht jedesmal gleich die Skriptsprache $foo ausgepackt werden. sed entspricht daher ganz der Unix-Tradition „Do one thing well“.

 

Links

  1. http://www.gnu.org/software/sed/

  2. http://sed.sourceforge.net/sedfaq7.html

  3. http://www.opengroup.org/onlinepubs/9699919799/nframe.html

  4. http://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines

  5. http://sed.sourceforge.net/sed1line.txt


Autoreninformation:

Christian Brabandt setzt privat fast ausschließlich auf Linux und Freie Software. Von Anfang an interessierte er sich vor allem für Shell-Scripting, den Editor vi und vim und Anwendungsmöglichkeiten der Kommandozeile im Allgemeinen.

 

 


You are here: