Der Name AWK steht für die drei Autoren dieses Programms, Aho, Weinberger und Kernighan. Das Ziel dieses Tools war die Generation von einfachen Reports über textbasierte Datenbankdateien. Schnell wurde das Programm dann aber weiterentwickelt, so dass es heute eins der universellsten Hilfsmittel bei der Bearbeitung von Textdatenströmen ist.

Einfache Beispiele

Wie bei sed, so wird auch bei awk der eigentliche Programmtext (oder einfacher – die Befehle) entweder direkt auf der Kommandozeile mitgegeben, oder über eine separate Befehlsdatei. Aber was macht denn nun awk eigentlich? Beginnen wir mit einem simplen Beispiel.

Nehmen wir an, die Datei test.txt hat den folgenden Inhalt:

  Hans Maier       35     1234.56
  Peter Müller     27      987.65
  Herbert Schmidt  34     1111.99
  Leopold Lehrling 17      234.99
  Günter Geschäftsführer 56 12345.89

Diese Datei ist also eine einfache textbasierte Datenbank, die die Felder Vorname, Name, Alter, Gehalt enthält. Mit awk können wir diese Datei wunderbar bearbeiten. Schreiben wir doch einmal

  awk 'peter {print}' test.txt

Dann bekommen wir die Ausgabe der Zeile von Peter Müller. Natürlich hätte das auch grep so erreichen können, aber awk kann wesentlich mehr. Aber betrachten wir einmal den Aufbau der awk Befehlszeile (die innerhalb der Hochkommas steht) genauer.

Jeder einfache awk-Befehl hat die Struktur

  Muster { Aktion }

In unserem Beispiel war also das Muster Peter und die Aktion in geschweiften Klammern hieß print. Das bedeutet, alle Zeilen der Datei, in denen das Wort Peter steckt sollen ausgegeben werden. Die Aktion wird grundsätzlich in geschweiften Klammern geschrieben, das Muster grundsätzlich ohne Klammern. So kann – falls eines der beiden weggelassen wurde – unterschieden werden, was was ist.

Es können entweder das Muster oder die Aktion auch weggelassen werden. Wird das Muster weggelassen, so wird jede Zeile bearbeitet, wird die Aktion weggelassen, so wird print angenommen. Wir hätten also auch einfach schreiben können

  awk 'peter' test.txt

OK, das war einfach. Es geht aber auch wesentlich mehr. Sehen wir uns die Struktur der Datei noch einmal genauer an. Jede Zeile besteht aus Feldern, die durch ein oder mehrere Leerzeichen voneinander getrennt sind. Aus der Sicht von awk bekommt jedes Feld in dieser Zeile eine Nummer, bzw. ist über den Variablennamen $Nummer im Programm ansprechbar. Aus der Sicht von awk gilt also

   Hans Maier       35     1234.56
    $1   $2         $3        $4

Wollten wir also etwa nur die Ausgabe der Nachnamen und des Gehaltes, so würden wir schreiben:

  awk '{print $2,$4}' test.txt

Das Muster haben wir weggelassen, die Anweisung gilt also für alle Zeilen. Der Befehl hätte also folgende Ausgabe gebracht:

  Maier 1234.56
  Müller 987.65
  Schmidt 1111.99
  Lehrling 234.99
  Geschäftsführer 12345.89

Der Befehl print gibt uns also auf Wunsch nur bestimmte Felder aus. Somit können wir beliebige Ausgaben erzeugen, die aus beliebigen Dateiformaten das jeweils gewünschte Ergebnis hervorbringen. Eingefleischte C-Programmierer werden sich freuen, dass auch die printf-Funktion existiert, wir hätten also auch schreiben können:

  awk '{printf("Herr %s ist %d Jahre alt und verdient %.2f Euro\n",$2,$3,$4)}'

Neben der print-Anweisung gibt es natürlich noch viele weitere, die hier nicht alle beschrieben werden können. Zumindest angemerkt sei, dass es alle gängigen Rechenoperatoren und Vergleichstechniken gibt, auch die zusammengesetzten, die von C her bekannt sind. Sie können sowohl im Muster, als auch in der Aktion stehen.

Wenn wir z.B. nur diejenigen Zeilen sehen wollen, in denen Menschen beschrieben sind, die noch nicht volljährig sind, so könnten wir schreiben:

  awk '$3<18 {print}'

Das Muster besteht also aus der Bedingungsüberprüfung „Feld 3 ist kleiner als 18“, nur die Zeilen werden ausgegeben, deren drittes Feld diese Bedingung erfüllt.

BEGIN und END

awk bietet zwei spezielle Muster an, die eine Sonderbedeutung haben. Normalerweise können Muster entweder Bedingungen (wie im letzten Beispiel), einfache Suchbegriffe (wie im ersten Beispiel) oder komplexe reguläre Ausdrücke (in Slashs geklammert – /Ausdruck/ ) enthalten. Jeder dieser Muster wird auf jede Zeile angewendet. Der spezielle Musterausdruck BEGIN wird nur einmal ausgeführt, bevor die eigentlichen Zeilen der Eingabe abgearbeitet werden, das spezielle Muster END wird analog dazu abgearbeitet, nachdem alle Zeilen durchlaufen sind. Beide benötigen zwingend eine Aktion.

Machen wir ein Beispiel. Diesmal werden wir keine Datei bearbeiten, sondern awk in einer Pipe verwenden. Wir lassen also einfach den Dateinamen weg.

Das Programm df gibt uns eine Liste aller gemounteten Dateisysteme aus, inklusive der Angabe, wieviel Platz insgesamt verfügbar ist, wieviel belegt ist und wieviel noch frei. Eine typische Ausgabe von df sieht also etwa so aus:

Dateisystem          1k-Blöcke    Benutzt Verfügbar Ben% montiert auf
/dev/hdc1              4032092   2735308   1091960  72% /
/dev/hdd1              6346136   1394708   4951428  22% /usr
/dev/hda2             15472800   5422804   9264016  37% /opt
/dev/hdc3              1028124    260720    767404  26% /home
/dev/vol0/volume1       806288    154352    610976  21% /tmp
/dev/vol0/volume2       806288     16428    748900   3% /usr/local

Na das riecht doch schon nach awk. Die Ausgabe besteht ja wieder eindeutig aus Feldern, die durch ein oder mehrere Leerzeichen voneinander getrennt sind. Nur die erste Zeile stört etwas, sie enthält Überschriften, keine Werte. Wie bekommen wir sie weg?

Alle Zeilen außer der ersten Zeile beginnen mit einem Slash (/). Also benötigen wir einen regulären Ausdruck, der den Slash am Zeilenanfang(^) sucht. Schreiben wir

  df | awk '/^\// {print}' 

so bekommen wir nur die Zeilen, deren erstes Zeichen der Slash ist. Wir mussten den Slash hier mit einem Backslash maskieren, damit awk ihn nicht als Ende des regulären Ausdrucks wertet.

Wenn wir jetzt z.B. für ein Backup wissen wollen, wieviele Blöcke insgesamt benutzt sind, so können wir mit awk und den BEGIN und END Anweisungen einfach die entsprechenden Felder zusammenzuzählen:

  df | awk 'BEGIN {zahl=0}; /^\// {zahl+=$3}; \
       END {printf("Insgesamt %d KByte belegt \n",zahl)}' 

Was passiert hier? Nun im ersten Muster {Aktion} Paar ist das Muster BEGIN. Diese Anweisung wird nicht für jede Zeile ausgeführt, sondern nur einmal, vor der Abarbeitung der Zeilen. Die Aktion ist zahl=0. Wir setzen eine Variable zahl auf den Wert 0. Das zweite Anweisungspaar ist ein normales Paar, das auf alle Zeilen angewand wird, die auf das Muster passen. Das Muster ist das oben schon besprochene, also „Alle Zeilen, die mit einem Slash beginnen“. Nur geben wir diesmal die Zeilen nicht aus, sondern wir addieren das Feld 3 auf die Variable zahl. zahl+=$3 bedeutet soviel wie „zahl=zahl+$3“. Ganz zum Schluß, im END-Block geben wir dann die Zahl mit einer printf-Anweisung aus.

Mehrere awk-Anweisungen werden durch Strichpunkte voneinander getrennt. Ich habe das hier in zwei Zeilen geschrieben, um es leichter lesbar zu machen. Durch den Backslash vor dem Zeilentrenner wird der Trenner nicht gewertet. Der Befehl wäre also tatsächlich auch genau so einzugeben.

Feldtrennzeichen

Wir haben bisher immer mit den Leerzeichen als Feldtrenner gearbeitet. Das ist sicher auch die häufigste Methode, manchmal – wie etwa in /etc/passwd – benötigen wir aber auch andere Zeichen als Feldtrenner. Wir können das awk über eine Kommandooption mitteilen. Die Option -F Zeichen ermöglicht die Angabe eines alternativen Trennzeichens. Wollen wir also einfach nur wissen, wer der User mit der UserID 501 ist, geben wir ein:

  awk -F: '$3==501 {print $5}' /etc/passwd

Das ruft awk jetzt mit dem Feldtrenner Doppelpunkt auf. Wenn das dritte Feld 501 ist, geben wir das fünfte Feld (Klarname) aus.

Natürlich kann awk noch viel mehr, inklusive Schleifenbildung, if-Anweisung und vieles andere. Diese ganzen Dinge zu beschreiben würde aber den Rahmen dieser Darstellung deutlich sprengen, ein kleiner Überblick ist aber doch gegeben.