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 hintereinander gehä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
Slashes '/' 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
escaped werden, also 'r
/\/
'. sed gibt einem die Möglichkeit, ein anderes
Zeichen als den Slash als RE-Begrenzer zu verwenden. Man kann als Adresse '/\/bin\/ls/
'
oder beispielsweise '\@/bin/ls@
' verwenden.
In ähnlicher Weise kann das mit dem s
- oder y
-Kommando geschehen:
's//
' ist gleichwertig zu 's@@
' Hat man nicht genau verstanden,
was Adresse, was Kommando und was RE ist, kommt man da leicht ins Schleudern.
Reguläre Ausdrücke sind greedy, finden also 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 einfacher:
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.
Mit 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 innehat, 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 herauszupicken, 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 eine Datei, in der verschiedene Namen eingetragen sind:
Alan Mathison Turing Claude Elwood Shannon Grace Murray Hopper John von Neumann Ada Lovelace
die in die Form <VORNAME> [<INITIAL ZWEITER NAME>.] <NACHNAME>
gebracht werden soll.
Dazu muss man erst die Bereiche 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:
Alan M. Turing Claude E. Shannon Grace M. Hopper John v. Neumann Ada Lovelace
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 dann ein falsches Ergebnis, wenn der zweite Vorname fehlt.
name: Alan M. Turing name: Claude E. Shannon name: Grace M. Hopper name: John v. Neumann Ada Lovelace
Einem solchen nur teilweise formatierten Datenhaufen 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 Frau Lovelace 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 nacheinander abgearbeitet werden.
Das kann man auf mehrere Wege erreichen:
Man kann zwei Kommandos im selben Script durch einen Semicolon (;) trennen
oder man gibt mehrere Scripts mit der Option -e
an.
Für längere Scripte empfiehlt es sich, diese in eine Datei zu schreiben und diese Scriptdatei mit der Option
-f
aufzurufen.
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.
Dieses Script, in eine Datei geschrieben, schaut so aus:
s/\(^[^ ][^ ]*\) \([[:alpha:]]\)..* \([^ ][^]*\)$/\1 \2. \3/ s/..*/name: &/
#!/pfad/zum/programm
'
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.
Die Zeichenkombination '#!
' nennt man shebang.
Wie weiter oben beschrieben, kann man die geschwungenen Klammern '{}
' verwenden, um mehrere Kommandos
auf eine Adresse anzuwenden.
Dies lässt sich auch für einen kleinen Trick missbrauchen. Will man zum Beispiel das shebang
('#!
') in der ersten Zeile einer Datei entfernen, kann man das so machen:
sed -e '1{/^#!/d;}'
Dieses Script löscht die erste Zeile, aber nur wenn sie mit '#!
' beginnt.
Es ist ein schönes Beispiel für die Kombination von mehreren Adressen.
Gelegentlich will ein Script einfach nicht funktionieren, und man verbringt Stunden damit, zu rätseln,
warum eine RE partout nicht auf eine Zeile passen will.
Manchmal liegt das an der Eingabe-Datei, nämlich wenn sie Zeichen enthält, die man nicht erwartet.
In solchen Fällen ist das Kommando 'l
' nützlich.
Es gibt den pattern space in einer eindeutigen Schreibweise (welche an ANSI C angelehnt ist)
auf den Bildschirm:
echo "versuch mich zu haschen! " | sed -ne 'l' versuch\tmich zu haschen\307\203 $
Jetzt wird klar, warum die Adresse '/^versuch mich zu haschen!$/
' nicht zur Eingabe passt:
erstens ist das Zeichen zwischen den ersten beiden Wörtern kein Leerzeichen sondern ein Tabulator,
zweitens ist das vermeintliche Rufezeichen in Wahrheit das Unicode Zeichen "latin letter retroflex click",
und schließlich hat sich ein Leerzeichen am Ende der Zeile eingeschlichen.
echo dmVyc3VjaAltaWNoIHp1IGhhc2NoZW7HgyAK | base64 -d | sed -ne 'l'