computerseite-spezial.de

handverlesene Infos zu Linux, FreeBSD und OpenSolaris

  • Full Screen
  • Wide Screen
  • Narrow Screen
  • Increase font size
  • Default font size
  • Decrease font size
Sie sind hier: Startseite Besondere Programme Keine Angst vor awk - ein Schnelleinstieg

Keine Angst vor awk - ein Schnelleinstieg

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

  1. http://de.wikipedia.org/wiki/Awk

  2. http://de.wikipedia.org/wiki/Regexp

  3. http://www.ostc.de/awk.pdf

  4. http://www.catonmat.net/download/awk.cheat.sheet.pdf



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.

 

You are here: