AWK
Baustelle: Dieser Artikel ist eine Baustelle. Das heißt, jemand hat sich dieses Artikels angenommen und überarbeitet ihn gerade. |
Vereinfachte Erklärung zu awk. Der Text soll nur als schnelle Einstiegshilfe dienen. Genaueres findet man in der awk Manpage, in diversen Büchern, Tutorials und zu gawk auf https://www.gnu.org/software/gawk/manual/gawk.html
Achtung, es gibt in Debian einige unterschiedliche awk Versionen, die sich leider auch mehr oder weniger unterschiedlich verhalten. Hier benutzt ist gawk.
apt-get install gawk
Grundsätzliches
awk kannst Du in zwei Modi betreiben: als "one-liner" und mit "Programmcode". Der zweite Fall ist für den Anfang interessanter.
Eine einfache Programmdatei (hier "code.awk") hat folgende Struktur:
{ print $0 }
Du kannst den Code natürlich auch in eine Zeile quetschen und direkt ausführen, nur ist das bei umfangreichen Programmen nicht mehr lesbar. Speicher daher den Code anfangs in extra Dateien und fang mit der getrennten Form an:
awk -f code.awk daten.txt
Die {} und den daran enthaltenen Code nennt man Block. Awk liest zeilenweise (genauer recordweise, Erklärung folgt weiter unten) aus der Eingabedatei (hier "daten.txt") und führt den Programmcode blockweise darauf aus.
Ein einfacher Block ohne vorangestellte Bedingung wird immer ausgeführt.
Eine vorangestellte Bedingung sähe so aus:
/1/{ print $0 }
Hier werden nur die Zeilen ausgegeben, die eine 1 enthalten. Diese Bedingungen werden "Pattern" genannt.
In ihnen kann man auch auf komplexere Reguläre Ausdrücke, Variableninhalte und ähnliches prüfen. Das ist aus mehreren Gründen interessant, denn awk bringt unter anderem eine Handvoll vordefinierter Variablen mit.
Die wahrscheinlich meist verwendeten vordefinierten Variablen sind NR und NF. NR bedeutet "number of records", NF steht für "number of fields". Merk Dir die Begriffe einfach schonmal, Erklärung folgt sogleich. Erstmal ein Beispiel:
NR==1{ print $0 }
Hier wird nur etwas ausgegeben, wenn "number of records" 1 ist. Ein Record ist das, was awk mit einem Mal gelesen hat. Anfangs hatte ich behauptet, awk liest Zeilenweise. Das ist nicht ganz richtig, awk liest immer dann zeilenweise, wenn als "Recordtrenner" der Zeilenumbruch definiert ist. Und das ist der Normalfall. Mit jedem gelesenen Record wird NR um eins erhöht. In dem Beispiel wird also nur die erste Zeile ausgegeben.
Man kann den Recordtrenner auch ändern, dazu aber später mehr.
Du kannst übrigens beliebig auch viele Blöcke zusammenbauen:
NR==1{ print $0 }
NR==1{ print $0 }
Hier würde (auch wenn hier nicht besonders sinnvoll) die erste Zeile mehrfach ausgeben. Alle anderen Zeilen der Eingabedatei werden hier ignoriert: die Bedingung (NR==1) trifft ja nur auf die erste Zeile zu.
Nun gibt es noch spezielle Pattern namens "BEGIN" und "END":
BEGIN{ print "hallo" }
{ print $0 }
END{ print "ende" }
BEGIN wird ausgeführt, bevor die Eingabe gelesen wird. END wird ausgeführt, nachdem alles abgearbeitet wurde. Apropos Eingabe: awk kann die Eingabe von stdin lesen, kann eine Datei genannt bekommen oder mehrere, die es dann nacheinander abarbeitet.
Nun fragst Du Dich vielleicht, was das $0 im Code bedeuten soll. $0 ist der aktuell in Bearbeitung befindliche Record. Jeder Record besteht aus mehreren Feldern ("fields"). Auch hier gibts wieder einen Trenner, wie erwartet, diesmal heißt er FS. Auf die einzelnen Felder kann man mit $1, $2, usw zugreifen.
Am Beispiel:
awk -F":" '{print $1}' /etc/passwd
Hier wird mit -F der FS geändert und dann $1 ausgegeben. Alternativ kann man das auch im BEGIN Block machen:
awk 'BEGIN{FS=":"} {print $1}' /etc/passwd
Blockinhalte
IF
If funktioniert wie in den meisten Sprachen:
awk -F ":" '{if ($3 >= 1000) {$3 = " - "}; print $3 " " $1}' /etc/passwd
awk '{printf $1; if ($1 ~ "101") {print " (wird nur im Winter angeboten)"} else {print ""}}' /usr/share/doc/gawk/examples/data/class_data1
Das erste Beispiel gibt zuerst die User-ID und, durch ein Leerzeichen getrennt, den Usernamen aus, dabei werden alle User-IDs ab 1000 durch ein Minus ersetzt.
Im zweiten Beispiel sind sowohl print als auch printf benutzt. Print gibt hier nach der Ausgabe noch den Zeilenumbruch dazu, printf nicht. Einige der printf üblichen Formatierungszeichen gibt es auch in der gawk Version, die manpage gibt im Abschnitt "The printf Statement" ausführlicher Auskunft.
Die Datei class_data1 stammt übrigens aus dem Paket gawk und sollte bei der Installation von gawk ebenfalls angelegt worden sein, sie enthält folgende kleine Beispieldatensätze:
Biology_101 sum average data: 87.0 92.4 78.5 94.9 Chemistry_305 sum average data: 75.2 98.3 94.7 88.2 English_401 sum average data: 100.0 95.6 87.1 93.4
Durch den oben genannten awk Befehl wird folgendes ausgeben:
Biology_101 (wird nur im Winter angeboten) Chemistry_305 English_401
einfache Schleifen
AWK kennt die gleiche For-Schleifenstruktur wie C:
awk 'x = 0; {for (i=5; i <= NF; i++) {x=x+$i}; print $1 ": " x}' /usr/share/doc/gawk/examples/data/class_data1
In diesem Beispiel werden, ab der fünften Spalte, alle Felder einer Zeile addiert:
Biology_101: 352.8 Chemistry_305: 356.4 English_401: 376.1
Textersetzungen
Für Textsubtitionen nimmt man z.B. sub und gsub:
awk -F ":" '{gsub(".","-",$1); print $3 " " $1}' /etc/passwd
awk -F ":" '{gsub(".","-"); print $3 " " $1}' /etc/passwd
Gibt man bei den dritten Parameter nicht mit an, wird die Ersetzung auf $0 vorgenommen. Das g in gsub steht für global, es wird hier also nicht nur das erste Vorkommen ersetzt.
Sonstiges
TODO:
Zu den einzelnen Funktionen von der Sprache AWK könnte man auch noch mehr (siehe awk manpage) schreiben. Wer weiterschreiben mag, einfach loslegen!
Es fehlt noch etwas zu: next ORS / OFS