Dieser Beitrag ist erschienen in freiesMagazin (Link) 06/2009 / Lizenz: GNU Free Documentation License (GNU FDL) (Link) / Autor: Marcel Jakobs .
Wenn Sie diesen Artikel weiterverwenden möchten, beachten Sie bitte die Lizenzbedingungen. Vielen Dank.
Keine Angst vor awk - ein Schnelleinstieg
von Marcel Jakobs
Der Befehl awk (Link) gilt bei vielen Linux-Anwendern als eines der kompliziertesten Kommandos. Um mit awk zurecht zu kommen, ist es jedoch einfacher es als sehr leichte Programmiersprache zu sehen, die vor allem bei der Verarbeitung von Textdateien nützlich ist, und nicht als kompliziertes Kommando. Sie eignet sich auch hervorragend für die Standardeingabe STDIN. Aber auch wer nicht programmieren kann, wird vielleicht an den einfachen Konstrukten gefallen finden. Dies hier wird jedoch keine Programmieranleitung.
Im Artikel wird auf nawk („new awk“) eingegangen, das Standard auf den meisten Linux-Distributionen ist. Es gibt noch eine weitere Version namens gawk (GNU awk), mit der die Beispiele aber genauso funktionieren.
Erste Schritte
Wer irgendeine Programmiersprache (am besten C, Java oder Perl) beherrscht, wird mit awk recht schnell zurecht kommen. awk liest meist eine Textdatei (oder andere Eingabe) ein und zerstückelt sie direkt in Zeilen und diese wiederrum in Felder (Wörter). Dadurch hat man sich schon etwas Programmieraufwand gespart. Nun geht awk den Text Zeile für Zeile durch und führt das Programm auf jeder einzelnen Zeile aus. Es gibt einige Standardvariablen, die man verwenden kann:
$0 ist die aktuelle Zeile
$1 ... $n sind die Wörter der Zeile
NF (Number of Fields) ist die Anzahl der Felder der aktuellen Zeile
NR (Number of Records) ist die Nummer der aktuellen Zeile
Mit diesen Variablen kann man schon recht praktische Programme schreiben. Dabei wird für jede Zeile nach folgendem Schema vorgegangen:
Muster { Aktion } Wobei das Muster meist ein regulärer Ausdruck (Link) ist, bei dessen Gültigkeit (für die Zeile) die Aktion auf die Zeile ausgeführt wird. Es kann jedoch eine beliebige Bedingung sein. Oft wird das Muster weggelassen, was dazu führt, dass die Aktion auf jede Zeile ausgeführt wird.
Ein paar einfache Beispiele:
$ awk '{print $2}' datei.txt gibt von jeder Zeile das zweite Wort aus.
$ awk '{print NF}' datei.txt gibt von jeder Zeile die Anzahl der Wörter aus.
$ awk '{print $NF}' datei.txt gibt von jeder Zeile das letzte Wort aus.
$ awk '{print NR ": " $0}' datei.txt gibt jede Zeile mit Zeilennummer davor aus.
Wie man sieht, können Strings einfach verkettet werden, indem man sie aneinander schreibt (ein Leerzeichen dazwischen erhöht die Lesbarkeit). Variablen werden hingegen mit einem Komma konkateniert. Ein Semikolon trennt (wie bei vielen Programmiersprachen) zwei Anweisungen.
$ awk 'END{print NR}' datei.txt gibt die Anzahl der Zeilen aus. Das END ist ein Muster, das besagt, dass die Aktion erst ausgeführt werden soll, wenn alle Zeilen bearbeitet wurden. Entsprechend gibt es ein Muster BEGIN, dessen Aktion vor dem Einlesen der Datei ausgeführt wird.
Die Hochkommata umschließen das eigentliche Programm und verhindern, dass die Shell Zeichen daraus interpretiert. Entsprechend müssen Strings innerhalb des Programms mit doppelten Anführungszeichen gekennzeichnet werden.
Variablen
Es gibt nur zwei Typen von Variablen: Zahlenwerte und Strings. Variablen brauchen nicht deklariert zu werden und werden standardmäßig mit 0 bzw. initialisiert. Wie bei Perl wird der Typ automatisch bestimmt.
Ein einfaches Beispiel könnte so aussehen:
$ awk '{sum+=$NF} END{print sum}' datei.txt Dieses Programm addiert das letzte Wort jeder Zeile zu der Variablen sum hinzu und gibt am Ende den Inhalt von sum aus. Dies ist recht praktisch, wenn man sich die einzelnen Posten einer Bestellung in eine Datei schreibt, wobei als letztes Wort jeweils der Preis steht. So kann einfach die Gesamtsumme berechnet werden ohne jeden Betrag einzeln zur Berechnung herauszukopieren.
Ein weiteres schönes Beispiel ist die Berechnung eines Durchschnitts:
$ awk '{sum+=$NF} END{print sum/NR}' datei.txt Hier wird das Ergebnis noch durch die Anzahl der Zeilen geteilt. Dies ist praktisch z. B. für einen Notendurchschnitt.
So kann man mit wenig Kenntnissen (Variablen, Print-Anweisung und Arithmetik) schon so viele praktische Dinge erledigen. Nimmt man nun noch einfache reguläre Ausdrücke hinzu, hat man bereits einige praktische Möglichkeiten.
Reguläre Ausdrücke als Muster
Die folgenden Beispiele ließen sich so oder ähnlich auch mit anderen Kommandos realisieren, doch hier geht es ja um awk.
$ ls | awk '/png$/{print}' gibt alle png-Dateien in einem Verzeichnis aus. Statt print $0 reicht auch ein einfaches print, da ohne ein Argument standardmäßig die aktuelle Zeile ausgegeben wird. Dies ist also die gleiche Funktionalität wie ein simples grep. Genauso kann man auch wie mit grep Dateien durchsuchen:
$ awk '/foo/{print}' datei.txt gibt (wie grep) alle Zeilen aus, die „foo“ enthalten. Um auch die Zeilennummern mit auszugeben, braucht man nur zusätzlich noch NR:
$ awk '/foo/{print NR": "$0}' datei.txt Hier noch einige Beispiele mit dem Kommando ls:
$ ls -l | awk '/png$/{sum+=$5} END{print sum}' gibt die Gesamtgröße aller png-Dateien in einem Verzeichnis aus.
$ ls -l | awk '/png$/{sum+=$5; print} END{ print sum/(1024*1024)" MB"}' zeigt die Ausgabe von ls -l für alle png-Dateien an und gibt am Ende die Gesamtgröße in Megabytes aus.
$ ls -l | awk '/png$/{sum+=$5; anz++; print} END{print "Anzahl der PNG-Dateien: "anz; print "Gesamtgroesse der PNG-Dateien: "sum/1024" kB ( "sum" Bytes )"; print "Durchschnittliche Groesse einer PNG-Datei: "(sum/anz)/1024" kB"}' gibt alle PNG-Dateien mit ls -l aus und darunter, wieviele Dateien es waren, wie viel Speicher von allen png-Dateien im Verzeichnis verbraucht werden und wie groß eine Datei im Durchschnitt ist.
awk kennt auch Arrays, die beliebig dimensional sein können (mehr- und gemischtdimensional) und deren Länge vorher nicht festgelegt werden braucht. Auch assoziative Arrays sind möglich. Beispiele:
arr[5]=7; # setzt den 6. Wert des Arrays auf 7 arr[5,3]="hallo"; # setzt den 4. Wert des 6. Arrays auf "hallo". arr["first"]=8; # assoziatives Array
Die kürzeren Programme überlegt man sich in der Regel jedes mal neu und verwirft sie nach Gebrauch wieder. Bei längeren Programmen lohnt es sich jedoch sie zu speichern. Dafür wird der Teil zwischen den Hochkommata in eine Datei geschrieben, die nun mit awk -f aufgerufen werden kann. Statt dessen kann man auch in die erste Zeile
#!/usr/bin/awk -f
schreiben und die Datei ausführbar machen. Dann kann man sie jederzeit aufrufen.
Vordefinierte Funktionen
Bevor nun weiter auf die Kontrollstrukturen eingegangen wird, sollen kurz einige vordefinierte Funktionen vorgestellt werden, mit denen sich wieder viele Probleme lösen lassen. Am praktischsten ist wohl getline, mit dem die nächste Zeile in $0 geladen wird. So kann man sich mittels
$ ifconfig | awk '/eth0/{getline; print $2}' die IP-Adresse von eth0 anzeigen lassen. Doch es steht noch ein „Adresse:“ davor. Um dieses Wort noch weg zu bekommen (um die IP-Adresse z. B. in Skripten verwenden zu können) kann man die Funktionen substr(s,i[,n]) und index(s,t) nutzen. substr gibt einen Teilstring von s ab der Position i aus. Das Optionale n gibt an wieviele Zeichen ausgegeben werden sollen. index gibt die Position des ersten Vorkommens von t in s aus. Daraus kann man das Programm verfeinern, so dass wirklich nur die IP-Adresse ausgegeben wird:
$ ifconfig | awk '/eth0/{getline; print substr($2,index($2,":")+1)}' Ein substr($2,9) hätte es in diesem Fall zwar auch getan, das hätte bei einer englischen Ausgabe jedoch nicht mehr funktioniert (und auf die Art wurden direkt zwei Funktionen erklärt ;-) ).
Einige weitere vordefinierte Funktionen sind:
sin(x),cos(x) - Sinus und Cosinus von x (mit x in Radiant)
int(x) - gibt Ganzzahl zurück, indem Nachkommastellen von x abgeschnitten werden
sqrt(x) - Wurzel von x
rand() - Zufallswert zwischen 0 und 1
and(a,b), or(a,b), xor(a,b), compl(a) - logische Operationen
gsub(r,s[,t]) - ersetzt jedes Vorkommen von r durch s in der Variablen t (bzw. $0, falls t nicht gesetzt)
length(s) - gibt Länge von s zurück (ohne Argument Länge von $0)
Das folgende Beispiel gibt die längste Zeile eines Textes mit Zeilennummer aus:
$ awk 'length($0) > longest {line=$0; longest=length($0); num=NR} END{print num": "line}' Hier wurde als Muster eine Bedingung gewählt, deren Aktion ausgeführt wird, wenn sie wahr ist.
Kontrollstrukturen
Die Kontrollstrukturen von awk sind denen von C, Perl und Java sehr ähnlich. Die if-, while- und for-Konstrukte sind syntaktisch identisch. Daher wird hier nur ein Beispiel angegeben. Wegen der Lesbarkeit wurde das Programm auf mehrere Zeilen aufgeteilt. Man kann jedoch auch einfach alles in eine Zeile schreiben:
$ awk '{ for(i=1;i<=NF;i++){ if(length($i)>length(biggest)){ biggest=$i; } } } END{print biggest}' datei.txt gibt das längste Wort einer Datei aus. Für jede Zeile wird eine Variable i von 1 bis „Anzahl der Felder“ hochgezählt und die Länge des entsprechenden Feldes mit dem bisher längsten Feld (biggest) verglichen. Ist das aktuell verglichene Feld größer als das bisher größte, so wird das größte Feld auf das aktuelle gesetzt. Am Ende wird das längste Feld ausgegeben. Als Einzeiler sieht das Programm so aus:
$ awk '{for(i=1;i<=NF;i++){if(length($i)>length(biggest)){biggest=$i;}}}END{print biggest}' datei.txt Operatoren
Die Operatoren sind im Prinzip auch die gleichen wie in den bereits genannten Programmiersprachen und einige davon wurden ja auch schon benutzt. Interessant sind hier die Operatoren ~ und !~, die prüfen, ob ein regulärer Ausdruck passt (bzw. nicht passt).
Das folgende Beispiel bestimmt die IP-Adresse von eth0. Hier wird zusätzlich zu den oben angegebenen Programmen noch geprüft, ob das entsprechende Wort eine Folge von Ziffern mit abschließendem Punkt beinhaltet. Wenn nicht, wird ein leerer String ausgegeben:
$ ifconfig | awk '/eth0/{getline; if($2 ~ /[0-9]+\./){print substr($2,index($2,":")+1)} else {print }}' Funktionen
Natürlich lassen sich in awk auch eigene Funktionen schreiben. Diese sind einfach nach der Syntax
function name(arg1, arg2){ ... } zu schreiben und können wie gewohnt aufgerufen werden. Das folgende Beispiel definiert eine Funktion, die prüft, ob das übergebene Argument eine Primzahl ist. Das Programm nimmt nun jede Zeile der übergebenen Datei, die nur aus Ziffern besteht und gibt sie aus. Dahinter wird geschrieben, ob es eine Primzahl ist oder nicht.
#!/usr/bin/awk -f function isprim(number){ if(number==1) { return 0; } else if(number<4) { return 1; } else if(number%2==0) { return 0; } else if(number<9) { return 1; } else if(number%3==0) { return 0; } else { r=int(sqrt(number)+0.5); f=5; while(f<=r) { if(number%f==0) { return 0; } if(number%(f+2)==0) { return 0; } f=f+6; } } return 1; } /^[0-9]+$/{ if(isprim($0)){ prim=": prim"; }else{ prim=; } print $0,prim; } Ändern der Wort- und Zeilentrenner
Welche Zeichen zwei Wörter bzw. Zeilen voneinander trennen, kann auch festgelegt werden. Dafür sind die beiden Variablen FS und RS zuständig. Setzt man z. B. FS=',', so kann man recht gut CSV-Dateien (CSV=Comma Separated Values) verarbeiten. Dabei dürfen die Variablen mit regulären Ausdrücken belegt werden, so dass man mit RS='\. - \.\n - \! - \!\n - \? - \?\n' mit Sätzen statt Zeilen arbeitet.
Ein awk-Programm, welches jeden Satz in einer eigenen Zeile ausgibt wäre z. B.
$ awk 'BEGIN{RS="\. |\.\n|\! |\!\n|\? |\?\n"} {print}' datei.txt da print ohne Parameter automatisch $0 ausgibt.
Die Variablen FS und RS können jederzeit und überall im Programm geändert werden.
Schlussbemerkung
Es gibt natürlich noch zahlreiche weitere Funktionen, vordefinierte (oder definierbare) Variablen, Aufrufoptionen, Kontrollstrukturen, Operatoren etc. Aber mit diesem Wissen kann man schon recht schöne kleine awk-Programme schreiben, um sich das Leben zu erleichtern. Auch für das Commandline-Fu sollten die hier vorgestellten Aspekte zumindest eine Zeit lang ausreichen.
Eine sehr schöne und ausführliche deutsche Anleitung zu awk gibt es bei OSTC (Link). Ein schönes Cheat-Sheet bekommt man bei catonmat.net (Link). Ansonsten hält wie immer die Manpage weitere Informationen bereit.
Links
Autoreninformation:
Marcel Jakobs ist Autor bei zInformatik. Obwohl er seit Jahren Linux- und Kommandozeilennutzer ist, hat er sich kürzlich erst näher mit awk beschäftigt. Dabei kamen ihm seine Kenntnisse anderer Programmiersprachen zugute.
| < Zurück | Weiter > |
|---|





