Blogbeitrag über den Stream Editor sed, eingesetzt zum Beispiel zur Analyse von altem Cobol-Code

Praktischer Nutzen von sed

Einleitung

Bei der Analyse von (alten) Quellcode-Dateien können Programme wie sed oder grep von großem Nutzen sein. Beispielsweise kann ein Ordner mit Cobol Quellcode-Dateien auf fehlende „Includes“ geprüft werden (ohne Compiler). Ebenso können externe Aufrufe ohne vorhandenen Quellcode ausfindig gemacht werden.
Die Syntax von sed ist zudem in anderen Programmen wieder zu finden, zum Beispiel in vi oder vim.

sed ist ein Unix-Werkzeug, ein sogenannter „Stream Editor“ und wird verwendet, um Text zu filtern. Oft wird sed für ad hoc Manipulation von Ausgaben anderer Befehle verwendet, um ungewollte Information zu entfernen oder Information zu ergänzen.

Als ein frühes Unix-Standardprogramm (Version 7 Unix, 1979) zeichnet sich sed durch Schnelligkeit und geringen Speicherbedarf aus.
Auch wenn sed auf unterschiedlichen unixoiden Betriebssystemen verfügbar ist, gibt es kleine Versionsunterschiede. So unterscheiden sich die GNU Version und BSD Version in gewissen Bereichen. Die GNU Version unterstützt beispielsweise erweiterte „Reguläre Ausdrücke“.

Blogbeitrag über den Stream Editor sed, eingesetzt zum Beispiel zur Analyse von altem Cobol-Code

Programme wie sed erleichtern die Analyse von Cobol-Dokumenten und anderen alten Quellcode-Dateien

Funktionsweise und Beispiele

Die Beispiele in diesem Abschnitt basieren auf GNU sed (4.2.2).

Eingaben werden mit sed zeilenweise eingelesen und bearbeitet, wobei pro Zeile eine oder mehrere Operationen ausgeführt werden und ein modifizierter Text zurückgegeben wird. Dabei können beispielsweise Teile ersetzt, hinzugefügt oder gelöscht werden – einmalig oder mehrfach.

Die vermutlich grundlegendste Anwendung ist eine einfache Ersetzung beziehungsweise „Substitution“.

Beispiel 1

echo "Hallo Splendit! Hallo Splendit!" | sed s/Splendit/Welt/

In diesem Beispiel wird mittels echo-Befehl eine Eingabe an sed weitergeleitet („Hallo Splendit! Hallo Splendit!“). Auf diese Eingabe wendet sed eine einmalige Substitution von „Splendit“ durch „Welt“ an. Die Ausgabe ist demnach: „Hallo Welt! Hallo Splendit!“

Eine Substitution hat das Muster „s/Suchmuster/Ersetzung/“. Nach dem letzten Schrägstrich wir spezifiziert, auf welche Vorkommnisse des Suchmusters die Ersetzung angewandt werden soll. Steht hier nichts, wird nur das erste Vorkommnis pro Zeile ersetzt.

Beispiel 2

# Substitution aller Vorkommnisse von "a" durch "b" (global):
sed s/a/b/g

# Substitution des 3. Vorkommnisses:
sed s/a/b/3

# Substitution aller Vorkommnisse nach dem 3. Vorkommnis:
sed s/a/b/3g

Interessant wird es dann, wenn „Reguläre Ausdrücke“ dazu kommen.

Beispiel: Ersetzung aller Abkürzungen

Dieser Abschnitt soll einige interessante Konzepte von sed anhand eines Beispiels näher bringen.

Ein Eingabetext beinhaltet diverse (zum Teil unverständliche) Abkürzungen. Alle Abkürzungen sollen anhand einer Liste durch die entsprechenden ausgeschriebenen Wörter ersetzt werden.

Eingabetext

Der Eingabetext wird für dieses Beispiel in der Textdatei „eingabe.txt“ gespeichert. Der beispielhafte Inhalt ist wie folgt:

Sg. Damen und Herren!

Bei der Wohnungssuche gibt es ggf. sehr viele Abkürzungen, die nicht unbedingt verständlich sind, z.B. KM, NK, HZK oder KT.

Wäre es nicht besser, wenn diese Abkürzungen ausgeschrieben wären?

Mfg,

Splendit

Liste der Abkürzungen

Die Abkürzungen werden in diesem Beispiel in einer Textdatei namens „abkuerzungen.txt“ mit dem folgenden Inhalt gespeichert:

Sg.=Sehr geehrte
ggf.=gegebenenfalls
z.B.=zum Beispiel
KM=Kaltmiete
NK=Nebenkosten
HZK=Heizkosten
KT=Kaution
Mfg=Mit freundlichen Grüßen

Schritt 1

Die Liste der Abkürzungen muss in ein für sed übliches Format gebracht werden, also in das bekannte „s/a/b/“-Format. Dies wird natürlich auch mit sed bewerkstelligt.

Es muss vorne, in der Mitte und hinten ein Schrägstrich („/“) hinzufügt werden.

Hier gibt es mehrere Dinge anzumerken:

  • Der Schrägstrich hat standardmäßig eine besondere Bedeutung, weil er ein Steuerzeichen ist. Diese Bedeutung kann mit einem vorgestellten Backslash aufgehoben („escaped“) werden: „\/
    Das Verhalten kann jedoch vereinfacht werden, indem wir „/“ als Steuerzeichen durch ein anderes ersetzen, denn sed erlaubt das auf einfache Weise. So kann einfach „sed s_a_b_“ statt „sed s/a/b/“ geschrieben werden. Das erste Steuerzeichen im Muster bestimmt hier das gültige Steuerzeichen für den Rest des Musters. Es kann aber nicht innerhalb eines Musters gewechselt werden. 
Typische alternative Steuerzeichen sind hier: „_“, „#“ oder „,
  • Auf den Anfang und das Ende einer Zeichenkette kann – regex konform – mit „^“ und „$“ zugegriffen werden.
  • Mehrere Suchmuster können mittels „(a|b|c)“ auf einmal angewandt werden. Jedoch müssen standardmäßig sowohl Klammern („(“ und „)“), als auch das Verkettungszeichen („|“) escaped werden. 
Auch das kann vereinfacht werden, indem der Parameter „-r“ übergeben wird (erfordert GNU sed). Dadurch wird erweiterte regex-Unterstützung aktiviert, was dazu führt, dass die Zeichen nicht mehr escaped werden müssen.

Diese Punkte kombiniert ergeben den folgenden Befehl:

sed -r 's_(^|=|$)_/_g' abkuerzungen.txt

Die Ausgabe davon:

/Sg./Sehr geehrte/
/ggf./gegebenenfalls/
/z.B./zum Beispiel/
/KM/Kaltmiete/
/NK/Nebenkosten/
/HZK/Heizkosten/
/KT/Kaution/
/MfG/Mit freundlichen Grüßen/

Die einfachen Anführungszeichen müssen in diesem Fall vorhanden sein, um das Muster zu begrenzen.

Dieser sed Befehl ersetzt den Anfang (vor dem ersten Zeichen), das „=“ und das Ende (nach dem letzten Zeichen) durch einen Schrägstrich. Das „g“ ist hier natürlich notwendig, sonst würde nur der erste Treffer ersetzt werden – in diesem Fall wäre das also immer nur der Anfang.

Schritt 2

Weiters ist es notwendig, ein „s“ an den Anfang und ein „g“ an das Ende zu setzen.

sed erlaubt es, mehrere Muster hintereinander auszuführen. Die einzelnen Muster müssen hierbei durch einen Strichpunkt getrennt werden.

Beispiel

sed 's/a/b/g; s/c/d/g'

Somit kann der sed Befehl von Schritt 1 einfach erweitert werden:

sed -r 's_(^|=|$)_/_g;s/^/s/;  s#$#g#' abkuerzungen.txt

Wie hier zu sehen ist, kann das Steuerzeichen für jedes Muster gewechselt werden, falls erwünscht. Außerdem spielt es keine Rolle, wie viele Leerzeichen vor oder nach dem Strichpunkt sind.

Hier die Ausgabe:

s/Sg./Sehr geehrte/g
s/ggf./gegebenenfalls/g
s/z.B./zum Beispiel/g
s/KM/Kaltmiete/g
s/NK/Nebenkosten/g
s/HZK/Heizkosten/g
s/KT/Kaution/g
s/Mfg/Mit freundlichen Grüßen/g

Schritt 3

Der nächste Schritt besteht darin, alle vorher erstellten Muster auf den Eingabetext anzuwenden.

sed bietet hier zwei interessante Möglichkeiten:

1. Muster können aus einer Datei ausgelesen werden.
2. Muster können aus einem Script ausgelesen werden.

Beide Varianten sind sehr ähnlich, sollen jedoch hier separat erklärt werden.

Variante 1: Muster wird aus einer Datei ausgelesen

Hierfür wird die Ausgabe von Schritt 2 in einer Datei benötigt. Selbstverständlich können wir einerseits einfach eine gewöhnliche Weiterleitung der Ausgabe vornehmen:

sed -r 's_(^|=|$)_/_g; s/^/s/; s/$/g/' abkuerzungen.txt > abkuerzungen.sed

Andererseits ist es mit sed auch möglich, gleich „in-place“ zu editieren, also in der Eingabedatei Änderungen vorzunehmen:

sed -i.bak -r 's_(^|=|$)_/_g; s/^/s/; s/$/g/' abkuerzungen.txt

Anmerkung

-i.bak erstellt eine Sicherheitskopie der Originaldatei mit der (frei wählbaren) Endung „.bak“. Wird .bak weggelassen und nur -i geschrieben, wird diese Sicherheitskopie nicht erstellt.

Heißt die Muster-Datei nun „abkuerzungen.sed“, sieht der komplette Befehl wie folgt aus:

sed -f abkuerzungen.sed eingabe.txt

-f bewirkt hier, dass sed eine Datei mit Mustern als Eingabe verwendet.

Variante 2: Muster wird als Script angegeben

Der grundlegende Unterschied ist hier, dass keine Muster-Datei erstellt werden muss, sondern, dass die Ausgabe des sed Befehls von Schritt 2 direkt als Muster verwendet wird. Es werden also zwei geschachtelte sed Befehle ausgeführt.

Um den inneren sed Befehl zu begrenzen, muss sich der Befehl zwischen "$( und )" befinden. Zudem muss der äußere sed Befehl den Parameter -e verwenden, der ein Script als Eingabe spezifiziert.

Ergebnis von Variante 2:

sed -e "$(sed 's_\(^\|=\|$\)_/_g; s/^/s/; s/$/g/' abkuerzungen.txt)" eingabe.txt

Die Ausgabe von Variante 1 und Variante 2 ist jeweils wie folgt:

Sehr geehrte Damen und Herren!

Bei der Wohnungssuche gibt es gegebenenfalls sehr viele Abkürzungen, die nicht unbedingt verständlich sind, zum Beispiel: Kaltmiete, Nebenkosten, Heizkosten oder Kaution.

Wäre es nicht besser, wenn diese Abkürzungen ausgeschrieben wären?

Mit freundlichen Grüßen,
Splendit

Schlusswort

sed ist ein mächtiges Tool, um Streams zu manipulieren. Das gezeigte Beispiel sollte hier einen kleinen Einblick in die Möglichkeiten von sed vermittelt haben.
Natürlich könnten bei diesem Beispiel noch diverse Verbesserungen gemacht werden. So wird beispielsweise nicht nur nach ganzen Worten gesucht, Klein- und Großschreibung wird nicht beachtet und die korrekte Adjektivdeklination wird schon gar nicht verwendet (Beispiel: „Sg. Herr Müller“ wird zu „Sehr geehrte Herr Müller“).
Es sollte sich dennoch anhand des Beispiels erahnen lassen, wie hilfreich sed sein kann. Vor allem in Anbetracht der Erweiterbarkeit des gezeigten Beispiels.

Alle Beispiele sind auf GitHub zu finden:
https://github.com/Splendit/sed-example

Für Interessierte empfiehlt sich zudem die folgende Seite:
http://www.grymoire.com/Unix/Sed.html

Außerdem sind die Wikipedia Einträge sowohl auf Deutsch, als auch auf Englisch sehr empfehlenswert:
https://de.wikipedia.org/wiki/Sed_(Unix)
https://en.wikipedia.org/wiki/Sed