Kernel (de) Kernel (it) Books Shell Sed/awk RAM/swap P35

sed, awk und reguläre Ausdrücke

Autor und Copyright: Dr. Thomas Hertweck
Version: 0.3; Stand: 06. März 2011

Zu diesem Dokument

Die neuste Version dieses Dokuments kann stets unter http://www.thomashertweck.de/sedawk.html gefunden werden.

Diese kleine Einführung zu den Programmen sed und awk entstand (in etwas anderer Form) für eine Vorlesung am Geophysikalischen Institut der Universität Karlsruhe. Da sowohl sed als auch awk bei Anwendungen oft mit regulären Ausdrücken zu tun haben, wird auch auf dieses Thema etwas eingegangen. Dieser Text erhebt natürlich keinen Anspruch auf Richtigkeit und Vollständigkeit und es wird keine Gewähr für alle Angaben oder daraus entstehender Konsequenzen übernommen. Ich hoffe aber, er wird der ein oder anderen Person hilfreich sein.

sed - der Stream Editor

Der Programmname sed leitet sich von "stream editor" ab. Es handelt sich hierbei um keinen Editor wie man es üblicherweise kennt (z.B. vi, joe, emacs, nedit), sondern um einen skript-basierten Editor, der grundlegende Textmanipulationen auf jede Zeile eines Eingabestroms (aus einer Datei oder einer Pipe, also stdin) anwendet. Der Editor sed ist dementsprechend nicht interaktiv! Er macht nur einen Durchlauf über den zu bearbeitenden Eingabestrom, was u.a. zur Folge hat, dass man sich nicht rückwärts bewegen kann zum Editieren. Allerdings ist genau aus diesem Grund sed auch sehr effizient bei der Bearbeitung, insbesondere bei großen Datenmengen. Zur Benutzung von sed muss der Anwender aber im Vorhinein genau wissen, welche Textmanipulationen am Eingabestrom vorgenommen werden sollen.

Wie bereits erwähnt, wendet sed die Textmanipulationen zeilenweise an. Es stellt sich also die Frage, wie nun zu ändernde Abschnitte des Eingabestroms adressiert werden können. Ein sed Kommando akzeptiert keine, eine oder zwei Adressen, die entweder durch Zeilennummern oder durch Suchmuster gegeben sind. Die Zeilen, die auf das Suchmuster passen bzw. die richtige Zeilennummer haben, sind dabei immer mit eingeschlossen. Ein folgendes Ausrufezeichen ! invertiert dabei die Auswahl. Hier nun einige Beispiele zur Adressierung:

Wie man sieht, ist das Ansprechen von gewissen Zeilen bei der Verwendung von GNU sed kein großes Problem. Weitere Möglichkeiten lassen sich in man sed nachlesen. Werfen wir nun einen Blick auf die wichtigsten Kommandozeilenoptionen: die Option -n unterdrückt die Standardausgabe von sed. Normalerweise gibt sed jede Zeile des Eingabestroms wieder aus. Mit dieser Kommandozeilenoption lässt sich das Verhalten derart ändern, dass sed nur noch auf explizite Anforderung durch den Anwender eine Zeile ausgibt. Die Option -e sagt sed, dass nun das nächste Argument ein Editierkommando ist (falls es eindeutig ist, braucht diese Option nicht verwendet zu werden). Die Option -f weist sed an, die Editierkommandos aus dem nächsten Argument, was ein Dateiname ist, zu lesen. Die Optionen -e und -f können mehrfach verwendet oder auch gemischt werden. Die Syntax eines Editierkommandos sieht dabei wie folgt aus:

[adresse1[,adresse2]][!] {
  kommando1 [argumente]
  kommando2 [argumente]
  ...
}

Die geschweiften Klammern können weggelassen werden, wenn nur ein einziges Kommando folgt. Hier nun zwei einfache Beispiele, wie ein kompletter Aufruf des Editors sed aussehen könnte.

Nehmen wir an, wir brauchen alle Zeilen aus der Datei demo.txt, die das Wort Beispiel enthalten. Das lässt sich mit sed wie folgt lösen:

sed -n '/Beispiel/ p' demo.txt

Es sei nicht unerwähnt, dass ein einfaches grep hier auch einen guten Dienst leisten würde. Wollen wir wissen, in welcher Zeile das Wort Beispiel auftaucht, so würde der sed Befehl wie folgt aussehen:

sed -n '/Beispiel/ =' demo.txt

Um diese beiden Beispiele nun komplett zu verstehen, müssen wir einen Blick auf sed-interne Kommandos werfen:

Es gibt einige weitere Kommandos (siehe man sed), diese hier dürften aber zu den Wichtigsten zählen. Die genaue Syntax (hier sind nur die eigentlichen Kommandos wiedergegeben, nicht die komplett nötige Eingabesequenz) kann man ebenfalls der Manual-Seite entnehmen. Hier einige weitere einfache Beispiele:

sed '/Beispiel/ a\Beispiel2' demo.txt

Hier würde nach jeder Zeile, die das Muster Beispiel enthält, eine Zeile mit dem Text Beispiel2 eingefügt werden. Da die Option -n fehlt, wird also die komplette Datei demo.txt ausgegeben und das Resultat (d.h. die Standardausgabe des sed Befehls) ist um genau so viele Zeilen länger wie es Zeilen mit dem Wort Beispiel in demo.txt gibt. Durch

sed 's/oo/uu/' demo.txt

würden in der Datei demo.txt alle Zeilen abgearbeitet (es ist keine Adressierung angegeben) und die erste vorkommende Buchstabenfolge oo pro Zeile durch uu ersetzt werden. Man beachte, dass nur der erste Treffer (pro Zeile) ersetzt wird, selbst wenn weitere Buchstabenfolgen oo in der Zeile vorkommen. Will man alle Treffer ersetzen (also "global" in der gesamten Datei), so ist das Kommando

sed 's/oo/uu/g' demo.txt

zu verwenden. Die Ausgabe erfolgt hier wieder auf die Standardausgabe, die man über den Operator > in eine Datei mit z.B. dem Namen demo2.txt umleiten könnte. Ein

diff demo.txt demo2.txt

würde dann genau die gemachten Veränderungen zu Tage befördern. Mit Hilfe von

sed -n '/Beispiel/ w ausgabe.txt' demo.txt

kann man sich alle Zeilen, die das Muster Beispiel enthalten, in die Datei ausgabe.txt schreiben lassen. Ein äquivalenter Befehl dazu wäre:

grep Beispiel demo.txt > ausgabe.txt

Wie man sieht, gibt es bei einem Unix/Linux System oft viele Möglichkeiten, eine Aufgabe anzugehen. Nicht alle sind gleich elegant, gleich schnell oder gleich geschickt, aber primär erfüllen sie alle ihren Zweck.

Reguläre Ausdrücke (Regular Expressions)

Bei der Verwendung von sed kommt man früher oder später nicht um die Verwendung von sog. regulären Ausdrücken (regular expression, regex, siehe u.a. man 7 regex) herum. Neben sed akzeptieren viele weitere Programme Regular Expressions, z.B. awk, grep, xemacs oder perl. Was ist eigentlich eine Regular Expression? Im Prinzip ist es nichts anderes als eine Formel, um beliebig komplizierte Zeichenketten anhand von Mustern passgenau zu treffen. Oft sehen die Konstrukte verwirrend und kompliziert aus und unglücklicherweise wird dieser Text nichts daran ändern können. Auch oder gerade wegen ihrer Syntax sind Regular Expressions aber extrem mächtig und vielseitig. Und zum Glück sind Regular Expressions oft viel leichter selbst zu schreiben als fremde Konstrukte zu verstehen - dazu wird dieser Text hoffentlich etwas beitragen. Die Idee hinter Regular Expressions besteht in der Verwendung von normalen Zeichen in Verbindung mit sog. Meta-Zeichen bzw. Wildcards, die eine Sonderbedeutung haben. Einige Meta-Zeichen sind:

Wie bereits angedeutet, können Regular Expressions sehr komplex werden und hier nicht in aller Ausführlichkeit dargestellt werden. Eine gute Seite zum Stöbern stellt http://www.regular-expressions.info/ und das dortige Tutorial dar. Hier noch ein paar Beispiele in Verbindung mit sed:

sed 's/^$/d' demo.txt

Mit diesem Kommando werden alle Leerzeilen aus der Datei demo.txt entfernt und das Ergebnis wird auf die Standardausgabe ausgegeben.

sed 's/^[ \t]*$/d' demo.txt

Bei diesem Beispiel werden alle Zeilen aus der Datei demo.txt entfernt, die lediglich Leerzeichen (oder Tabulatorsprünge) enthalten. Das Ergebnis erscheint wiederum auf der Standardausgabe.

sed 's/"//g' demo.txt

Bei diesem Beispiel werden alle Anführungszeichen aus der Datei demo.txt entfernt. Auch hier erscheint das Ergebnis wieder auf der Standardausgabe.

Die Programmiersprache awk

Das Akronym awk steht nicht etwa für eine kurze Schreibweise von "awkward" (engl.: ungeschickt, unangenehm, schwierig), sondern für die Erfinder dieser Programmiersprache, Alfred V. Aho, Peter J. Weinberger und Brian W. Kernighan. Zusätzlich zu den bereits bei sed aufgeführten Eigenschaften kennt awk Variablen, erweiterte logische Ausdrücke, numerische Berechnungen und formatierte Ausgaben. Ähnlich wie sed, so wendet awk ein oder mehrere Skripte auf einen Eingabestrom aus einer Datei oder einer Pipe an. Im Unterschied zu sed wird jedoch nicht jede Zeile aus dem Eingabestrom auch automatisch wieder ausgegeben - hier muss die Ausgabe immer explizit durch den Anwender angefordert werden. Die wichtigsten Kommandozeilenoptionen sind:

Mehrere Instanzen der Option -f und -v sind möglich. Die Struktur eines awk Kommandos sieht wie folgt aus:

muster { aktion }

Wird kein Muster muster angegeben, so wird jede Zeile des Eingabestroms bearbeitet. Wird keine Aktion aktion angegeben, so wird die jeweilig treffende Zeile des Eingabestroms schlicht ausgegeben. Generell wird natürlich jede Aktion aktion nur auf Zeilen angewendet, die durch das Muster muster adressiert werden.

awk kennt einige interne Variablen, die sehr häufig gebraucht werden. Das sind:

Für weitere interne Variablen, siehe man awk.

Ebenso gibt es die Blöcke BEGIN und END, die jeweils einmalig zu Beginn der awk Kommandos oder am Ende der awk Kommandos abgearbeitet werden.

Hier nun einige sehr einfache Beispiele. Das Konstrukt

awk '$2 > $1' input.txt

gibt alle Zeilen der Datei input.txt aus, bei denen der Wert im zweiten Feld den Wert im ersten Feld übertrifft. Die Felder sind dabei durch den Standard-Feldtrenner voneinander getrennt. Man beachte, dass hier keine Aktion angegeben ist und so automatisch eine treffende Zeile ausgegeben wird. Es sollte nun leicht verständlich sein, dass

awk '{ print $3 + $4 }' input.txt

auf alle Zeilen der Datei input.txt angewendet wird und die jeweilige Ausgabe (also pro Zeile) die Summe aus den Spalten drei und vier ist. Bei diesem Beispiel fehlt das Muster und es ist nur eine Aktion gegeben. Bei dem folgenden Beispiel ist sowohl ein Muster als auch eine Aktion angegeben:

awk 'NF == 5 { print NR, $NF }' input.txt

Hier werden alle Zeilen der Datei input.txt angesprochen, die fünf Felder besitzen, die durch den Standard-Feldtrenner getrennt sind. Ausgegeben wird die jeweilige Zeilennummer (NR) sowie das jeweils letzte Feld in einer Zeile ($NF), getrennt durch ein Leerzeichen.

awk beherrscht sehr viele Kommandos, die natürlich nicht alle hier aufgeführt werden können. Nicht umsonst wird awk auch als vollständige Programmiersprache betrachtet. So kennt awk z.B. auch verschiedene Arten von Schleifen, Bedingungen, arithmetische Funktionen, Zeichenkettenmanipulationen, u.v.m. Schon alleine das einfache Kommando print erlaubt aber viele komplexe Anwendungen zu realisieren.

Hier nun ein etwas kompliziertes Beispiel mit einer Regular Expression:

awk -F: '$1 ~ /root|man/ { print }' /etc/passwd

Falls das erste Feld (Felder sind dabei durch einen Doppelpunkt getrennt) der Datei /etc/passwd entweder root oder man enthält, wird die gesamte treffende Zeile ausgegeben. Der Operator ~ (Tilde) dient dabei zum Vergleich eines konstanten Ausdrucks mit einer Regular Expression.

Ist nun klar, was das folgende Beispiel machen wird?

awk -F: '$3 ~ /\<50[0-4]/ { print "User:",$1,"; Shell:",$7 }' /etc/passwd

Die Lösung sollte jeder mit Hilfe dieser Anleitung finden können.

Zu guter letzt nun noch eine Beispielserie, die auf folgender Datei bundeliga.txt basiert:

Muenchen       - Nuernberg            3 : 2     34000 Zuschauer
Kaiserslautern - Moenchengladbach     2 : 1     28260 Zuschauer
Uerdingen      - Homburg              3 : 0     10000 Zuschauer
St.Pauli       - Bremen               0 : 0     20600 Zuschauer
Leverkusen     - Dortmund             1 : 0     22000 Zuschauer
Stuttgart      - Karlsruhe            2 : 0     34000 Zuschauer
Bochum         - Koeln                0 : 1     21000 Zuschauer
Frankfurt      - Mannheim             3 : 1     20000 Zuschauer
Duesseldorf    - Hamburg              1 : 1     35000 Zuschauer

(Hinweis: Mannschaften und Ergebnisse wurden zufällig ausgewählt).

Zu lösen sind nun drei Aufgaben:

  1. Gib alle Spiele aus, die vor mehr als 30000 Zuschauern stattgefunden haben oder bei denen es einen Auswärtssieg gab.
  2. Ermittle, in welcher Stadt die meisten Zuschauer zugegen waren und gib das Ergebnis wie folgt aus:
    Die meisten Zuschauer waren in STADT (ANZAHL).
    
  3. Erstelle eine Statistik für den Spieltag, die wie folgt aussehen sollte:
    Es gab insgesamt NUM Spiele.
    Zuschauer (gesamt): ANZAHL
    Zuschauer pro Spiel (Duchschnitt): ANZAHL2
    Tore (gesamt): TORE
    Tore pro Spiel (Duchschnitt): TORE2
    

Überleg Dir nun, wie diese Aufgaben zu lösen sind. Am Besten natürlich, ohne vorerst in die nun folgende Lösung zu schauen.

  1. Die Lösung von Aufgabe 1 sollte relativ leicht fallen. Es müssen hier zwei Bedingungen überprüft werden, die nicht unbedingt gleichzeitig zutreffen müssen, d.h. es handelt sich um eine ODER-Verknüpfung.
    awk '$4 < $6 || $7 > 30000'  bundesliga.txt
    
    Eine Aktion muss hier nicht angegeben werden, da im Falle, dass eine der Bedingungen zutrifft, automatisch eine Ausgabe der treffenden Zeile erfolgt (siehe oben).
  2. Die Lösung der Aufgabe 2 ist ein klein wenig umfangreicher. Hier schreiben wir daher ein kleines Skript (eine Textdatei) namens aufgabe2.awk:
    max < $7 { max = $7 ; stadt = $1 } 
    END { print "Die meisten Zuschauer waren in",stadt,"("max")." }
    
    Beim Aufruf von
    awk -v max=0 -f aufgabe2.awk bundesliga.txt
    
    erhalten wir dann die gewüschte Ausgabe. Es lässt sich natürlich leicht nachprüfen, ob unser Konstrukt das macht, was es machen soll. Hier sieht man die Verwendung eines END Blocks, der nur einmal am Ende abgearbeitet wird, sowie die Verwendung von Variablen. Man beachte, dass eigene Variablen beim Referenzieren nicht mit einem Dollar-Operator eingeleitet werden, wie man es vielleicht von der Shell her gewohnt ist. Ansonsten sollte die Befehlssequenz und die enthaltene Bedingung leicht zu verstehen sein.
  3. Auch die Lösung zu Aufgabe 3 ist etwas umfangreicher, weswegen wir hier ebenfalls ein kleines Skript (eine Textdatei) anlegen, und zwar mit dem Namen aufgabe3.awk:
    { 
    zusch += $7 
    tore += ($4+$6) 
    } 
    END { 
    print "Es gab insgesamt",NR,"Spiele." 
    print "Zuschauer (gesamt):",zusch 
    print "Zuschauer pro Spiel (Durchschnitt):",zusch/NR 
    print "Tore (gesamt):",tore 
    print "Tore pro Spiel (Durchschnitt):",tore/NR 
    }
    
    Beim Aufruf von
    awk -v zusch=0 -v tore=0 -f aufgabe3.awk bundesliga.txt
    
    erhalten wir die gewüschte Ausgabe, die hoffentlich richtig sein sollte. Das Initialisieren der Variablen zu Null an der Kommandozeile könnte man sich übrigens auch sparen, das wird implizit durchgeführt - schaden tut es aber jedenfalls nicht.

Damit endet nun dieser Text zur Einführung in sed, awk und reguläre Ausdrücke. Verbesserungsvorschläge, Korrekturhinweise, Lob und Kritik sind stets erwünscht und willkommen.

März 2011: Ein Dank geht an Thomas Sattler, der einige Korrekturen für dieses Dokument eingeschickt hat.