Wer bis hierher gekommen ist, sollte wirklich verstanden haben was Adressen und was Kommandos sind. Das ist wichtig, denn ab jetzt werden diese in einem sed-Script hintereinandergehängt, und das kann sonst schon für einige Verwirrung sorgen.
Hin und wieder trifft man in Scripten nicht die gewohnte Form '/' einer RE vor - die
Slashs '/' scheinen zu fehlen. Das hat den Grund, dass es manchmal nötig ist in einer RE den
Slash selber anzugeben.
Damit dieser aber nicht fälschlicherweise interpretiert wird, muss er mit dem Backslash
gequotet werden, also 'r/\/'. sed gibt einem die Möglichkeit, ein anderes
Zeichen als den Slash als RE-Begrenzer zu verwenden. Man kann also '/\/bin\/ls/'
oder beispielsweise '\@/bin/ls@' verwenden.
In gleicher Weise kann das mit dem s- oder y-Kommando geschehen:
's//' ist gleichwertig zu 's@@' Hat man deshalb nicht genau verstanden,
was Adresse was Kommando und was RE ist, kommt man da leicht ins Schleudern.
Reguläre Ausdrücke finden immer den längsten passenden String. Das kann manchmal unerwünscht sein. Will man zum Beispiel eine HTML-Seite in Text umwandeln, dann könnte man in Versuchung kommen folgendes Script zu verwenden:
sed -e 's/<.*>//g' text.html
Das liefert aber nicht den gewünschten Effekt, denn eine Zeile
Das <b>ist</b> ein <i>Beispiel</i>.
wird zu
Das.
verkrüppelt. Man muss also nur jene Zeichen bis zum ersten '>' löschen:
sed -e 's/<[^>]*>//g' text.html
Muss man einen Text nicht bis zum ersten Vorkommen eines Zeichens sondern einer Zeichenkette bearbeiten, wird die RE ein bisschen komplizierter. Im Kapitel mit den Beispielen findet sich dazu ein Lösungsansatz (Löschen von Kommentaren).
Das s/// Kommando kann nicht nur fixe Strings einsetzen, sondern auch den gefundenen String oder
Substrings davon. Der Ampersand '&' steht dabei für den gesamten gefundenen String.
In meiner Kindheit hatten wir die elleff-Sprache, unsere Geheimsprache, bei der man jeden Vokal
(oder Gruppe von Vokalen) in einem Wort mit <VOKAL>l<VOKAL>f<VOKAL> ersetzen muss.
Kompliziert? Da ist die sed-Schreibweise prägnanter:
sed -e 's/[aeiou][aeiou]*/&l&f&/g'
Die Mächtigen der Welt, als 'Bilifill Clilifintolofon' oder 'Boloforilifis Jelefelzilifin' ausgesprochen, gewinnen damit in meinen Augen sofort an Sympathie. Meine Hochachtung jedem, der ein verellefftes 'ukulele' aussprechen kann ohne es vom Bildschirm zu lesen.
Unter GNU-sed kann man folgende Zeile schreiben:
sed -e 's/[aeiou]\+/&l&f&/g'
Bitte den Backslash '\' vor dem Plus beachten, da dieses Zeichen - weil
GNU-Erweiterung - zuerst als normaler Charakter angesehen wird und seine Bedeutung die er bei
REs inne hat, erst durch den Backslash gewinnt.
Gleiches gilt auch für das Fragezeichen (Questionmark) '?',
nicht aber für den Asterisken '*'.
Hier weise ich noch einmal auf die Grenzen von Regulären Ausdrücken hin.
Es ist nicht möglich, die Rücktransformation aus der elleff-Sprache mit REs auszudrücken.
Ein [aeiou]l[aeiou]f[aeiou] kann man wohl angeben, nicht aber die Bedingung dass alle drei
Vokale gleich sein müssen.
Ob dies hinreichend ist um die elleff-Sprache als sichere Verschlüsselungsmethode zu bezeichnen,
müssen wohl findigere Kryptologen entscheiden.
Mit sed ist es auch möglich, Teile von Strings heraus zu picken um diese später zu verwenden.
Diese Teile werden mit '\(' und '\)' markiert, und man kann auf diese Strings mit
'\1', '\2' usw. zugreifen.
Nehmen wir einmal an wir hätten ein File, in dem verschiedene Namen eingetragen sind:
John Fitzgerald Kennedy Franz Josef Strauss Ernst Theodor Amadeus Hoffmann Theo Lingen
die in die Form <VORNAME> [<INITIAL ZWEITER NAME>.] <NACHNAME> gebracht werden soll.
Dazu muss man erst die Regionen definieren:
sed -e 's/^[^ ][^ ]* [[:alpha:]]..* [^ ][^ ]*$//'
Nun gibt man um die gewünschten Zonen die Klammern und stellt sich das Ergebnis mit '\1' und '\2' und '\3' zusammen:
sed -e 's/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^ ]*\)$/\1 \2. \3/'
und voilà das Ergebnis:
John F. Kennedy Franz J. Strauss Ernst T. Hoffmann Theo Lingen
Will man das Ergebnis noch in eine Adressdatenbank importieren, dann muss man einen Feldbezeichner vor die Namen setzen. Ein erster Versuch wäre der, das gleich in einem Rutsch mit dem Script
sed -e 's/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^ ]*\)$/name: \1 \2. \3/'
zu bewerkstelligen, das liefert aber genau da ein falsches Ergebnis, wenn der zweite Vorname fehlt.
name: John F. Kennedy name: Franz J. Strauss name: Ernst T. Hoffmann Theo Lingen
Einem solchen nur teilweise formatierten Datenschwulst ist nur schwer beizukommen. Deshalb den Output ungetesteter Scripte immer zuerst auf eine temporäre Datei umleiten, diese auf Korrektheit prüfen und dann die Zieldatei ersetzen. Wie man die Namen nun richtig formatiert, wird im nächsten Kapitel beschrieben. Warum hat das Script aber nicht richtig gearbeitet? Damit die RE auf eine Zeile zutrifft, muss diese mindestens 3 Felder, durch Leerzeichen getrennt, enthalten. Das ist bei Herrn Lingen nicht der Fall, deshalb wird auch das Kommando nicht ausgeführt und der pattern space wird unberührt gelassen.
Ein sed-Script kann mehrere Kommandos enthalten, die nach einander abgearbeitet werden.
Man trennt diese mit einem Semicolon (;) oder man gibt diese mit mehreren -e
Optionen an der Kommandozeile an oder man speichert die Kommandos in eine Datei die dann mit der Option
-f abgearbeitet wird.datei
Eine mögliche Lösung des obigen Problems benutzt zwei Kommandos: das erste kürzt den Namen, ein zweites setzt vor alle Zeilen den String name:.
sed -e 's/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^ ]*\)$/\1 \2. \3/' \
-e 's/../name: &/'
oder man trennt die zwei Anweisungen durch einen Strichpunkt (;).
Zu beachten ist in der zweiten Anweisung die RE '..*'; würde man nur einen Punkt schreiben, passte dieser Ausdruck auch auf leere Zeilen.
Das wird mit zwei Punkten vermieden.
Dieser Einzeiler in eine Datei geschrieben schaut so aus:
s/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^]*\)$/\1 \2. \3/ s/..*/name: &/
Und wieder eine Bemerkung die nichts mit sed zu tun hat:
Die Shell gibt einem die Möglichkeit Scripte wie normale Programme zu behandeln.
Dazu muss man nur an den Anfang des Scriptes die Zeile
'#!/pfad/zum/interpreter <eventuelle Optionen>' setzen und die Scriptdatei als
ausführbar markieren. Wenn diese Datei nun gestartet wird, ruft die Shell den angegebenen
Interpreter mit dem Scriptnamen als Parameter auf. Auf das vorhergehende Beispiel angewandt sieht das so aus:
#!/bin/sed -f s/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^]*\)$/\1 \2. \3/ s/..*/name: &/
Die Option -f weist sed an, den nachfolgenden Dateinamen
(den die Shell hinzufügt) als Script zu nehmen.
Dieser Trick funktioniert nur mit Scriptsprachen, bei denen das Zeichen '#' einen Kommentar einleitet,
da sonst auch die erste Zeile als Programmcode interpretiert wird.