Wenn fortgeschrittene Linuxer Probleme lösen, greifen sie oft auf Skriptsprachen zurück: auf die Shell, auf Perl oder AWK. Während Shell- und Perl-Skripte einen hohen Bekanntheitsgrad haben, fragt sich der Uneingeweihte: "Was ist AWK?"
Die Abkürzung steht für die drei Autoren der Sprache: Alfred Aho, Peter Weinberger und Brian Kernighan. AWK liest und bearbeitet textbasierte Dateien. Zwar ist der Interpreter relativ flexibel einsetzbar, seine größte Stärke spielt er jedoch in einem ganz bestimmten Umfeld aus: bei Dateien, deren Zeilen Informationen enthalten, die nach einem vorgegebenen, gleichförmigen Muster aufgebaut sind. Dabei lassen sich Begriffe wie "Zeile" oder "gleichförmig" recht dehnbar auslegen: AWK arbeitet gerne auch mit ganzen Absätzen und sucht sich seine Daten aus komplexen Strukturen heraus.
Der Einstieg ist relativ einfach: Ein AWK-"Programm" besteht aus einer oder mehreren Einheiten. Jede davon enthält einen Vergleichsausdruck sowie einen Befehlsblock. Der Interpreter liest nun die Datei mit Rohdaten Zeile für Zeile (oder Datensatz für Datensatz) ein. Wenn ein Vergleichsausdruck auf eine Zeile passt, führt AWK den zugehörigen Befehlsblock aus.
Eine passende Datensammlung sollte aus purem Text bestehen, wahlweise in ASCII oder in Ihrem bevorzugten Zeichensatz. Typisch sind tabulatorgetrennte Tabellen wie in Listing 1. Doch prinzipiell ist jedes beliebige Trennzeichen zwischen den Feldern erlaubt, zum Beispiel auch ein Komma. AWK erlaubt im Gegensatz zu Perl sogar reguläre Ausdrücke als "Trennausdruck"! Wie heißt es in der Manpage perlvar(1) so schön (und frei übersetzt): "An einer Stelle muss AWK ja besser sein :-)"
Listing 1
Datenquelle
handys.tsvHandy Preis Gewicht N6310i 419 111 S45i 249 93 T68i 565 84
Am häufigsten benutzt man den Interpreter zu ganz einfachen Zwecken wie der Ausgabe der ersten Spalte aus Listing 1:
$ awk '{print $1}' handys.tsv
Handy
N6310i
S45i
T68i
Das "Programm" steht zwischen den beiden Apostrophen. Es enthält eine einzige Einheit, bestehend aus einem Vergleichsausdruck und einem Befehlsblock. Letzterer steht in geschweiften Klammern – hier ist es ein einziges Kommando, print $1, das das erste Feld der gelesenen Zeile ausgibt.
Passt eine Eingabezeile auf den Vergleichsausdruck, wird sie in Felder zerlegt, und zwar von eins an aufsteigend durchnummeriert. Die erste Zeile von Listing 1 beispielsweise zerfällt in drei Felder, $1 = "Handy", $2 = "Preis" und $3 = "Gewicht". Falls Sie sie einmal brauchen, finden Sie die gesamte, unzerteilte Eingabezeile in der Variablen $0 wieder.
Doch wo ist in unserem ersten Beispiel der Vergleichsausdruck? Er ist leer, und das bedeutet: Der zugehörige Befehlsblock wird auf jede Zeile der Datenquelle angewandt.
Weder Programm noch Daten müssen sich übrigens je auf ein einziges Argument beschränken. Sie können mehrere Programmfragmente und auch Dateien, die AWK-Befehle enthalten, in beliebiger Folge angeben. Ebenso dürfen Sie mehrere Datenfiles angeben, die dann einfach nacheinander eingelesen werden. Fehlen Angaben zu Datenquellen an der Kommandozeile, liest AWK einfach die Standardeingabe stdin.
Noch ein Beispiel gefällig? Wir könnten die ISDN-Meldungen des Kernels auswerten und darüber herausfinden, wer bei uns angerufen hat. Wenn ein Anruf eingeht (und der Computer ihn nicht entgegennimmt), produziert Linux im Syslog etwa die Meldungen aus Listing 2 (sie variieren vermutlich von Version zu Version geringfügig).
Listing 2
Unbeantwortete ISDN-Anrufe im Syslog
[…] Sep 22 11:30:21 nathan kernel: isdn_tty: call from 0 -> 9654321 ignored Sep 22 11:42:39 nathan kernel: isdn_net: call from 891234567,1,0 -> 9654321 Sep 22 11:42:39 nathan kernel: isdn_net: Service-Indicator not 7, ignored Sep 22 11:42:39 nathan kernel: isdn_tty: call from 891234567 -> 9654321 ignored […]
Dabei interessiert uns vor allem die erste und die letzte Zeile. Mit AWK suchen wir die Meldung über diese und die übrigen nicht abgenommenen Gespräche folgendermaßen heraus:
awk '/kernel.*call from.*ignored/ {print $0}' /var/log/*messages*
Als Vergleichsausdruck kommt hier ein regulärer Ausdruck (zwischen den Schrägstrichen) zum Einsatz. Er passt auf alle Zeilen, die nacheinander die Textfolgen kernel, call from und ignored enthalten; das sind Message 1 und 4 in Listing 2. Diese druckt print $0 komplett aus.
Soweit wären wir mit einer einfachen Suche mit grep wohl auch gekommen. Aber mit AWK lässt sich das beliebig verfeinern. Der nächste Befehl gibt lediglich Datum und Uhrzeit sowie die Rufnummer des Anrufers aus:
$ awk '/kernel.*call from.*ignored/ {printf "%s %02d %s 0%s\n",$1,$2,$3,$9}' /var/log/*messages*
Sep 22 11:30:21 00
Sep 22 11:42:39 0891234567
Damit es nicht langweilig wird, benutzen wir im Befehlsblock diesmal printf statt print. Während letzteres ganz ähnlich wie echo in der Shell funktioniert, bietet printf die gewohnte Funktionalität der entsprechenden C-Routine: Als erstes Argument verlangt es einen Format-String, der Platzhalter für die auszugebenden Variablen enthält und deren Formatierung festlegt.
Ein solcher Platzhalter beginnt immer mit dem Prozentzeichen %. Danach steht ein Buchstabe, der das Format der auszugebenden Variablen enthält, in unserem Beispiel z. B. s für einen String oder d für eine (ganzzahlige) Dezimalzahl. Wichtige Alternativen sind %e, %f und %g für Fließkommazahlen in verschiedenen Darstellungsweisen oder %x für eine Hexadezimalzahl. Wollen Sie tatsächlich ein Prozentzeichen ausgeben, verdoppeln Sie es: %%.
Zwischen % und der Formatangabe dürfen noch zusätzliche Zeichen stehen, die das gewünschte Format genauer bezeichnen. In unserem Beispiel haben wir %02d geschrieben. Die Zahl 2 steht für die Feldbreite, d. h., selbst wenn die Zahl (in diesem Fall der Tag) nur eine Stelle hat, sollen zwei Stellen ausgegeben werden. AWK würde normalerweise ein Leerzeichen voranstellen, aber wegen der zusätzlich angegebenen 0 füllt es die gewünschte Feldbreite mit Nullen auf. Für den 7. September erhalten wir aus $1 und $2 also Sep 07.
Wenn Sie die Feldbreite einer Fließkommazahl festlegen wollen, benutzen Sie zwei Zahlen: eine für die gesamte Feldbreite (einschließlich Punkt bzw. Komma, Exponent und/oder Nachkommastellen) und eine für den Anteil nach dem Fließkomma (oder Dezimalpunkt, je nachdem).
Die Telefonnummer des Anrufers aus $9 ergänzt der Format-String um eine führende 0, um das gewohnte Telefonbuch-Aussehen zu erhalten. Das \n am Ende sorgt für einen Zeilenumbruch, der bei print noch automatisch eingefügt wurde. Auf die Formatierungsangaben folgt mit $1,$2,$3,$9 die Auswahl der Daten.
Sie sehen den kleinen Schönheitsfehler: Wenn eine Rufnummer nicht übertragen wurde, schreibt der Kernel einfach eine 0 in die Logdatei. Zusammen mit der angehängten Null ergibt das dann den seltsamen Anrufer 00. Doch AWK erlaubt beliebig komplexe Befehle, so dass wir dieses Problemchen lösen können (Listing 3).
Listing 3
Unbekannte Anrufer benennen
$ awk '/kernel.*call from.*ignored/ {printf "%s %02d %s %s\n",$1,$2,$3,$9==0 ? "unbekannter Anrufer" : "0"$9}' /var/log/*messages*
Sep 22 11:30:21 unbekannter Anrufer
Sep 22 11:42:39 0891234567
Statt des lapidaren $9 steht diesmal ein ?:-Ausdruck. Vor dem Fragezeichen enthält dieser eine Bedingung. Ist sie erfüllt, gilt der Abschnitt zwischen Fragezeichen und Doppelpunkt, ansonsten der Abschnitt vom Doppelpunkt bis zum Ende:
$9 == 0 ? "unbekannter Anrufer" : "0"$9
Zu deutsch: "Wenn Variable $9 gleich null ist, dann drucke unbekannter Anrufer, ansonsten schreibe eine 0 plus den Inhalt von $9." Wer C kennt, weiß das alles schon, lediglich der Umgang mit Strings ist in AWK etwas einfacher.
Ein noch etwas komplexeres Demoprogramm zeigt Listing 4. Natürlich können Sie es von [1] aus dem Internet beziehen. Es liest die Datei aus Listing 1 ein. Darin zählt es die einzelnen Handys und errechnet den durchschnittlichen Preis sowie das durchschnittliche Gewicht.
Listing 4
Handy-Statistik
! /^Handy/ { # Wenn die Zeile nicht mit
# "Handy" (Überschrift) beginnt
zahl ++; # -> Handy zählen,
preis += $2; # Preis …
gewicht += $3; # … und Gewicht merken
}
END { # letzte Zeile vorbei?
# -> Durchschnittswerte ausgeben
print "Durchschnitte aus " \
zahl " Geräten: " \
"Preis " preis/zahl \
" Euro, Gewicht " \
gewicht/zahl " Gramm"
}
Zu diesem Zweck enthält das Programm zwei Einheiten mit je einem Vergleichsausdruck und einem Befehlsblock. Passt eine Eingabezeile auf den Vergleichsausdruck, wird sie in die Felder $1, $2 usw. zerlegt; diese stehen im Befehlsblock zur Verfügung.
In der ersten Einheit besteht der Vergleichswert aus dem Operator ! (NICHT) und einem regulären Ausdruck, der auf alle Zeilen passt, die mit "Handy" beginnen. Der zugehörige Befehlsblock wird also genau dann ausgeführt, wenn am Anfang einer Zeile nicht das Wort "Handy" steht, und passt somit auf alle Zeilen außer der Überschrift. In diesem Fall zählt das Programm durch den Operator ++ (genau wie in C) zur Variablen namens zahl eins hinzu. Der Preis des Handys aus Spalte 2 ($2) wird zur Variablen preis addiert, das Gewicht aus Spalte 3 ($3) zur Variablen gewicht.
AWK-Variablen müssen nicht vordefiniert werden: Sobald Sie sie benutzen, sind sie da. Jede neue Variable enthält zunächst den Wert 0 bzw. einen leeren String, so dass es in AWK keine undefinierten Variablen gibt.
Die zweite Einheit zeigt einen besonderen Vergleichsausdruck: Ein Befehlsblock mit END kommt nach Abschluss der Dateneingabe zur Ausführung. Typischerweise zeigt END Statistiken oder Ähnliches an, hier die Zahl der eingelesenen Handys und die gewünschten Durchschnittsdaten. Analog dazu existiert übrigens ein Vergleichsausdruck BEGIN, dessen Befehlsblock AWK ganz am Anfang der Bearbeitung durchläuft. BEGIN verwendet man meistens zur Initialisierung eigener Variablen oder zur Konfiguration des Interpreters.
AWK versucht zwar, Ihnen die Erstellung des Programms nach Möglichkeit zu erleichtern. Unter Unix versteht man unter "erleichtern" jedoch nicht unbedingt eine grafische Oberfläche mit bunten Bildern und vielen Mausklicks. Nein: Ein AWK-Programm schreiben Sie mit dem Editor Ihrer Wahl oder direkt an der Kommandozeile in einem Terminalfenster. Die Maus brauchen Sie höchstens für ein schnelles Spielchen zur Entspannung zwischendurch …
Haben Sie das Demo-Programm und die zugehörigen Daten eingegeben (oder von [1] heruntergeladen), starten Sie AWK an der Kommandozeile. Dazu haben Sie mehrere Möglichkeiten. Für unser Beispiel geben Sie die Dateinamen von Programm und Datensammlung direkt an der Kommandozeile an:
$ awk -f handys.awk handys.tsv Durchschnitte aus 3 Geräten: Preis 411 Euro, Gewicht 96 Gramm
Wahlweise liest AWK die Daten direkt von der Standardeingabe. Das ist in unserem Fall weniger spannend …
awk -f handys.awk < handys.tsv
…, nützlich wird diese Variante in Verbindung mit Pipes.
Schreiben Sie als erste Zeile der AWK-Datei #!/bin/awk -f, weiß die Shell, welcher Interpreter zum Zuge kommen soll (vorausgesetzt, das awk-Programm liegt tatsächlich in /bin). Dann lässt sich ein AWK-Skript auch direkt ausführen. Setzen Sie einfach das Execute-Bit mit chmod a+x handys.awk.
Das funktioniert, da Kommentare wie in der Bash immer mit einem Hash-Zeichen # beginnen, AWK ignoriert die Zeile also. Die Shell jedoch entnimmt dem Rest der Zeile den Interpreter und startet – in diesem Beispiel – AWK mit der Option -f. Voilà:
./handys.awk < handys.tsv
Wenn man AWK nur mal so eben verwendet, um Zahlen aus einem Logfile herauszusuchen, wird man das Skript kaum in einer eigenen Datei abspeichern. Am handlichsten ist es dann, wenn man die Befehle – wie in unseren ersten Beispielen – direkt an der Kommandozeile angibt und die Daten von der Standardeingabe liest:
$ awk '! /^Handy/ { zahl++; preis+=$2 } END { print "Durchschnitt " preis/zahl " EUR" }' <handys.tsv
Durchschnitt 411 EUR
Das Wichtigste dabei sind die einfachen Anführungszeichen (Apostrophe) rund um das Programm, die selbiges vor unerwarteten Interaktionen mit der Shell schützen. Gerade solche Variablennamen wie $2 verarbeitet jene sonst nämlich selbst, und das Ergebnis ist meistens unsinnig.
Kaum jemals wird ein derart aus dem Ärmel geschütteltes "Programm" auf Anhieb funktionieren, aber dafür gibt es ja die History der Shell: einmal [Pfeil nach oben] gedrückt, und die vorherige Befehlszeile steht zur Überarbeitung wieder am Prompt. So nähert man sich Schritt für Schritt dem gewünschten Resultat …
Neben den Sonderfällen BEGIN und END hatten wir bereits Regexps in Vergleichsausdrücken eines AWK-Programms besichtigt. Das ist eine sehr leistungsstarke Methode! Nur muss man sie gut beherrschen …
Leider sind logische Operatoren wie UND, ODER und NICHT in Regexps bestenfalls unübersichtlich. Daher erlaubt AWK eine wichtige Vereinfachung: Statt den logischen Operator irgendwie innerhalb des regulären Ausdrucks unterzubringen, dürfen Sie mehrere Regexps angeben und diese miteinander kombinieren.
Angenommen, Sie wollen einen bestimmten Befehlsblock nur auf solche Zeilen einer Datei anwenden, die sowohl das Wort "blassblau" als auch "Rose" enthalten. Sie möchten sich aber nicht festlegen, in welcher Reihenfolge die Wörter stehen sollen. Mit einer reinen Regexp müssten Sie beide Alternativen – "blassblau" zuerst oder "Rose" zuerst – berücksichtigen:
awk '/(blassblau.*Rose)|(Rose.*blassblau)/ { print $1 }' dateiWenn mehrere Bedingungen zusammenkommen, wird das schnell richtig kompliziert. Mit logischen Operatoren außerhalb der Regexps geht es einfacher:
awk '/blassblau/ && /Rose/ { print $1 }' dateiTabelle 1: Vergleichsausdrücke für AWK
| BEGIN | Befehlsblock wird bei Programmstart ausgeführt. |
| END | Befehlsblock wird vor Programmende abgearbeitet. |
/regexp/
|
Befehlsblock kommt nur dann zum Zuge, wenn der aktuelle Datensatz den regulären Ausdruck regexp enthält.
|
! /regexp/
|
Befehlsblock wird nur ausgeführt, wenn der aktuelle Datensatz den regulären Ausdruck regexp nicht enthält.
|
/regexp1/ && /regexp2/
|
UND-Verknüpfung der beiden regulären Ausdrücke. |
/regexp1/ || /regexp2/
|
ODER-Verknüpfung der beiden regulären Ausdrücke. |
/regexp1/,/regexp2/
|
/regexp1/ und /regexp2/ kennzeichnen einen ersten und einen letzten Datensatz. Der Befehlsblock wird für alle Datensätze innerhalb dieses Bereichs ausgeführt.
|
/regexp1/ ? /regexp2/ : /regexp3/
|
Wenn regexp1 passt, dann gilt regexp2 als Vergleichsausdruck, sonst regexp3. Beispiel: /Rose/ ? /rot/ : /blau/. Enthält ein Datensatz das Wort "Rose", muss zusätzlich rot darin vorkommen, in den übrigen Datensätzen hingegen blau. Der Befehlsausdruck wird also für alle roten Rosen und zusätzlich für alle blauen Nicht-Rosen ausgeführt.
|
(...)
|
Alle logischen Operatoren sind nicht nur auf reguläre Ausdrücke anwendbar, sondern auch auf komplexe Ausdrücke, die ihrerseits durch logische (oder andere) Operatoren gebildet wurden. Dabei empfiehlt sich zur Verdeutlichung der Rangfolge der Einsatz von runden Klammern. |
Bislang haben wir immer nur einfache Textdateien bearbeitet. Die einzelnen Datensätze waren dabei durch Zeilenenden voneinander getrennt, d. h., jede Zeile enthielt genau einen Datensatz. Zwischen den Feldern eines Datensatzes befand sich eine beliebige Menge Whitespace, also eine Abfolge von Leerzeichen und/oder Tabstopps. Dies ist die häufigste Variante und damit auch die Voreinstellung. Sie können diese Prämissen jedoch nach Belieben verändern.
Angenommen, Sie bearbeiten eine Textdatei. In bester LaTeX-Manier haben Sie nach jedem Satzende zwei Leerzeichen gelassen. Nun können Sie ganze Sätze, unabhängig von Zeilenumbrüchen, als "Datensatz" behandeln. Dazu definieren Sie die Trennung zwischen Datensätzen (den so genannten Record Separator) als Folge zweier Leerzeichen.
Den Record Separator speichert AWK – wie fast alle Konfigurationsoptionen – in einer besonderen Variable, in diesem Fall in RS. Wenn Sie diese verändern möchten, haben Sie mehrere Möglichkeiten: Zum einen lassen sich beim Aufruf des Interpreters Variablen über die Kommandozeilenoption -v (identisch mit --assign) setzen:
awk -v RS=" " intelligent.awk < mein_toller_roman.tex
Innerhalb eines AWK-Programms können Sie die Variable natürlich direkt modifizieren, ebenso wie Sie es oben bei den Zählvariablen gesehen haben. Für die meisten Anwendungen ist der BEGIN-Block die ideale Stelle, um derartige Konfigurationsoptionen einzustellen:
BEGIN{
RS = " ";
maxcount = 10000;
}
Eine zweite, häufig genutzte Konfigurationsmöglichkeit betrifft die Trennung der einzelnen Felder innerhalb des Datensatzes. Dieser Field Separator steht – Sie ahnen es – in der Variablen FS. Voreinstellung ist FS=" ", ein einzelnes Leerzeichen, das AWK als "beliebigen Whitespace" behandelt. Wenn Sie FS ein anderes Zeichen (z. B. ein Komma) zuweisen, kommt genau dieses Zeichen als Feldtrenner zum Einsatz. Mehrere Zeichen sind ebenso möglich, werden dann aber als regulärer Ausdruck betrachtet. Beim Sonderfall FS="" sortiert AWK jedes Zeichen des Datensatzes in ein eigenes Feld.
Natürlich dürfen Sie diese Variablen aus dem laufenden Programm heraus umkonfigurieren. Als praktisches Beispiel wäre die Bearbeitung einer E-Mail denkbar, bei der man zunächst den gesamten Header als ersten Datensatz behandelt. Dazu setzt man RS="", wodurch AWK (eine oder mehrere) Leerzeilen als Trenner zwischen Datensätzen behandelt. Anschließend definiert man RS="\n" und liest den Rest der Mail zeilenweise ein.
Tabelle 2: Nützliche Spezialvariablen
| RS | "Record Separator", Trennzeichen (oder regulärer Ausdruck) zwischen Datensätzen. Sonderfall: RS="" behandelt Leerzeilen als Trenner.
|
| FS | "Field Separator", Trennzeichen (oder regulärer Ausdruck) zwischen den Feldern eines Datensatzes. Sonderfälle: FS=" " steht für eine beliebige Folge von Whitespace (mindestens ein Zeichen); FS="" weist jedem Zeichen ein eigenes Feld zu.
|
| FIELDWIDTHS | Für die Bearbeitung von Dateien mit fester Feldgröße. FIELDWIDTHS enthält die Feldbreiten, getrennt durch Leerzeichen.
|
| NF | Zahl der Felder im aktuellen Datensatz. |
| NR | Nummer des aktuellen Datensatzes: Beim ersten Datensatz ist NR==1 usw.
|
| OFS und ORS | Setzen Feld- und Satztrenner für die Ausgabe mit print.
|
| IGNORECASE | Wenn diese Variable auf einen anderen Wert als 0 gesetzt ist, ignorieren alle Suchoperatoren die Groß-/Kleinschreibung.
|
Kasten 1: Die AWK-Befehlssprache
AWK enthält die üblichen Operatoren, Funktionen und Konstrukte zur Flusskontrolle, die man aus Sprachen wie C zur Genüge kennt.
Bei den Operatoren gilt die übliche Rangfolge. Auf die Klammern, die Vorrang vor allem anderen haben, folgen die mathematischen Operatoren ++ (Addition von 1), --, der Potenzoperator in den Schreibweisen ^ oder **, die unären Operatoren +, - und !; dann kommen *, / und % (Modulo) sowie + und -. Als relationale Operatoren gibt es < und <=, > und >= sowie != und ==. Für Vergleiche mit regulären Ausdrücken ist der Operator ~ (negiert !~) zuständig. Als logische Operatoren kommen &&, || und ?: zum Einsatz. Den geringsten Vorrang haben die Zuweisungsoperatoren = sowie die C-Sonderformen += ("addiere den Wert rechts des Operators zum alten Wert der linksstehenden Variable und weise dieser das Ergebnis zu"), -=, *=, /=, %= und ^=.
Auch AWK bringt die obligatorischen mathematischen Funktionen wie sin, cos usw. mit. Interessanter sind meist jedoch Routinen zur Interaktion mit Dateien und anderen Programmen:
getline <konfigurationsdatei
liest einen Datensatz aus konfigurationsdatei und zerlegt ihn gemäß den aktuellen Einstellungen in Felder. Explizit öffnen muss man die Datei in AWK nicht: Entweder sie ist bereits offen, dann wird einfach der nächste Datensatz gelesen, oder sie ist noch nicht offen, dann öffnet der Interpreter sie und liest den ersten Datensatz.
Für den umgekehrten Weg, das Schreiben in eine Datei, benötigt man keinen besonderen Befehl. Hängen Sie an ein print- oder printf-Kommando wie in der Shell ein oder zwei Größer-als-Zeichen und einen Dateinamen an. Die Datei wird überschrieben (bei >) bzw. verlängert (bei >>).
Einen Unix-Prozess starten Sie nach dem Muster
system("/usr/sbin/pppd call telekom
")
Wenn Sie Daten per Pipe (|) an ein anderes Programm schreiben möchten, kümmert sich AWK für Sie um die Erzeugung des Kind-Prozesses. Ein Beispiel zum Nichtausprobieren:
printf "%s\n%s\n%s\n\n%s\n",
"From: me@somewhere.net",
"To: "$1,
"Subject: Dies ist kein Spam",
"Wirklich nicht! Aber wenn Sie schnell zu Geld kommen wollen, folgen Sie\ndiesen einfachen Anweisungen …" \
| "/usr/lib/sendmail -t -U"
Ein paar weitere Funktionen erlauben String-Vergleiche mit und ohne reguläre Ausdrücke. Hierzu gehören gensub und gsub (Suchen und Ersetzen mit regulären Ausdrücken) oder index und match (Suchen nach Text ohne Regexps).
Bedingungen stellen Sie mit if-else-Konstrukten wie
if (NR==2) {
print "Bin jetzt im zweiten Datensatz!"
} else {
print "Bin woanders!"
}Auch bei den Schleifen dürften Sie Bekannte vorfinden, wenn Sie bereits einmal programmiert haben:
for (count=0; count<NF; count++) {
print "noch ein Wort, "
}
while (count>0) {
print "tue irgendwas"; count--
}
do {
print "tue irgendwas"; count--
} while (count>0)Vorzeitig heraus kommen Sie aus einer Schleife zum Beispiel so:
while (count>0) {
print "tue irgendwas";
if (bedingung
) break
}
do {
count--;
if (bedingung
)
continue;
print "tue irgendwas"
} while (count>0)
function max (a, b) {
return a > b ? a : b;
}angeblich richtiggehend in Bibliotheken. Da man an der Kommandozeile beliebig viele Programmdateien angeben darf, steht der Einbindung solcher Libraries in eigene Programme prinzipiell nichts im Weg:
/Ergebnis/ {
print "Der Gewinner hat " max($2, $3) " Tore geschossen."
}Aber ganz im Vertrauen gesprochen: Wenn ein Programm so kompliziert wird, dass man es in mehrere Module zerlegen muss, sollte man dann nicht irgendwann an eine größere Programmiersprache denken?
Nun fehlen in diesem Überflug über die Features von AWK eigentlich nur noch die Arrays. Das Wichtigste zu diesem Thema demonstriert Listing 5, ein kleines Programm zur CD-Verwaltung (selbstverständlich im Internet verfügbar unter [1]).
Es versteht nur ein paar Befehle: buy Gruppe CD-Titel (beide Argumente dürfen keinen Whitespace enthalten) nimmt eine CD als gekauft in die Datenbank auf, indem der entsprechende Eintrag im zweidimensionalen Array cds mit der Zahl 1 markiert wird. Umgekehrt löscht sell Gruppe CD-Titel die CD wieder aus dem Array heraus. check Gruppe CD-Titel überprüft, ob eine bestimmte CD vorhanden ist.
print gibt eine Liste aller CDs aus. Beachten Sie: Variablen werden in AWK erzeugt, sobald sie das erste Mal benutzt werden. Wenn check auf eine Array-Position einer bislang unbekannten CD zugreift, wird diese tatsächlich erzeugt, wenn auch mit dem Wert null! print muss in der for-Schleife daher zusätzlich prüfen, ob eine gespeicherte CD auch tatsächlich vorhanden ist.
Das Konstrukt for (i in cds) funktioniert leider nicht für einzelne Dimensionen eines Arrays. Bei AWK-Arrays handelt es sich nämlich immer um Hashes, die über eine Zeichenkette indiziert werden. Mehrdimensionale Arrays gibt es daher nur über einen Trick: Die beiden Schlüssel werden über die (ganz am Anfang gesetzte) Variable SUBSEP aneinandergehängt und bilden damit einen gemeinsamen Schlüssel. Nach Eingabe von buy Supertramp Autobiography wird also ein neuer Schlüssel Supertramp\tAutobiography gebildet und die Kennung 1 unter diesem Schlüssel gespeichert.
AWK verwendet normalerweise das Zeichen \034 für die Schlüsselgenerierung in mehrdimensionalen Arrays. Indem Sie SUBSEP auf einen Tabstopp umdefinieren, sorgen Sie für eine halbwegs schöne Ausgabe der CD-Liste.
Listing 5
Demo-Programm "CD-Verwaltung"
#!/bin/awk -f
BEGIN {
IGNORECASE = 1;
SUBSEP = "\t";
}
# Befehl buy Gruppe Titel
/^buy/ {
cds[$2, $3] = 1;
}
# Befehl sell Gruppe Titel
/^sell/ {
delete cds[$2, $3];
}
# Befehl check Gruppe Titel
/^check/ {
if (cds[$2, $3])
print "habe ich"
else
print "unbekannt";
}
# Befehl print
/^print/ {
for (i in cds)
if (cds[i])
print i
}
# Befehl new (lösche alles)
/^new/ {
delete cds;
}
# Befehl quit (Ende)
/^quit/ {
exit;
}
Wenn Sie AWK erst aus diesem Artikel kennengelernt haben, liegt der Eindruck nahe, diese Sprache könne nicht mehr als Texte zu finden und auszudrucken. Ein Trugschluss! Die Wahrheit lautet: Der Einfachheit – und Kürze – halber hat der Autor viele Konstrukte und Funktionen unterschlagen oder nur am Rande erwähnt.
Immerhin zeigt Kasten 1 einen kurzen Überblick über die Befehlssprache. Eine komplette Referenz enthält die – sehr gut gemachte – Manpage zu gawk(1), der GNU-Version von AWK, die auf den allermeisten Linux-Systemen installiert sein sollte. Als Standardlektüre in Buchform bietet sich immer noch das passende Werk der AWK-Autoren selbst [2] an.
Für die weitere Beschäftigung gilt wie so oft unter Unix: Probieren geht über Studieren. AWK eignet sich für fast alle Situationen, in denen es um einfache Operationen mit reinen Textdateien geht; von der Logfile-Auswertung über Finanzbuchhaltung bis hin zur wissenschaftlichen Statistik.
Glossar
reguläre Ausdrücke
Liebevoll Regexps genannt, erlauben diese Muster eine flexible Suche nach Textteilen, die nicht immer gleich sind. Neben normalen Zeichen kann man auch nach besonderen Bestandteilen fahnden, z. B. Platzhalter benutzen oder den Suchstring in Abhängigkeit von bereits gefundenen Textstücken modifizieren. Nähere Informationen gibt [3].
tsv
"Tabulator separated values" nennt man eine Datei, bei der mehrere Spalten durch Tabulatorzeichen voneinander getrennt sind. Das Äquivalent mit Kommas zwischen den Spalten heißt CSV, "comma separated values".
grep
Eines der meistgenutzten Unix-Progrämmchen; es durchsucht Dateien nach regulären Ausdrücken.
Whitespace
Bezeichnung für eine Folge von Leerzeichen, Tabstopps oder Zeilenenden, die mindestens ein Zeichen lang sein muss, aber beliebig lang sein darf. Auf manchen Systemen (einschließlich AWK) zählt zusätzlich der vertikale Tabstopp zum Whitespace. Klar: "White space" ist alles, was "weißen Platz" auf dem Papier erzeugt.
LaTeX
In Verbindung mit TeX ein Textsatz-, nicht aber DTP-System [4]. Als typografische Feinheit fügt LaTeX nach einem Punkt normalerweise einen etwas größeren Zwischenraum ein, um das Satzende für die Lesenden leichter erfassbar zu machen.
Infos
[1] Die Beispiele aus diesem Artikel: http://www.seligma.com/linux-user/awk/
[2] Alfred V. Aho, Brian W. Kernighan, Peter J. Weinberger: "The AWK Programming Language", Addison Wesley, 1988, ISBN 0-201-07981-X, ca. 54,00 USD.
[3] Marc André Selig: "Nadel im Heuhaufen", LinuxUser 08/2002, S. 74 ff.
[4] Heike Jurzik: "Gutenberg am Rechner", LinuxUser 09/2002, S. 43 ff.