Die Scriptsprache awk

Überblick

awk ist eine Sprache zur Verarbeitung von Textdateien. Die Bezeichnung awk setzt sich einfach aus den Anfangsbuchstaben der Nachnamen der Erfinder dieser Sprache, Aho, Kernighan und Weinberger, zusammen.

Unter Linux wird die GNU Implementierung gawk benutzt, die außer allen im POSIX-Standard vorgesehenen Features auch die Erweiterungen aus SVR4 unterstützt.

Der awk zählt zu den grundsätzlichen Tools, die auf jedem UNIX-System verfügbar sein sollten. Er wird recht oft von anderen Programmen benutzt und sollte deshalb auf jeden Fall mit installiert werden.

Die Sprache awk ist recht einfach. Man kann Sie ohne weiteres aus der ziemlich umfangreichen Man-Page erlernen.

Aufruf und Optionen

Der awk ist ein Interpreter. Im einfachsten Fall ist das auszuführende Script so kurz, daß man es gleich mit auf der Kommandozeile angeben kann:

awk [Optionen] [--] Programmtext Datei ...

Folgendes Beispiel gibt Namen der Benutzer und das jeweilige Home-Verzeichnis aus:

awk -F: '{print $5, $6}' /etc/passwd
Dabei gibt die Option
-F:
an, daß der Doppelpunkt als Trennzeichen zwischen Feldern aufgefaßt werden soll. Das eigentliche awk-Programm besteht aus
{print $1, $6}
Die Hochkommas verstecken die im Script enthaltenen Sonderzeichen vor der Shell. Die geschweiften Klammern gehören zum Programm und sind auch wichtig, wie wir später noch sehen werden.

Im awk werden Anweisungen, ähnlich der Shell, durch Semikolon getrennt. Damit kann man auch mehrere Anweisungen auf der Kommandozeile unterbringen. Trotzdem ist die Grenze der Übersichtlichkeit schnell erreicht und man schreibt das awk-Script lieber in eine Datei. Der Programmaufruf sieht dann so aus:

awk [Optionen] -f Programmdatei [--] Datei ...

Die Programmdatei wird dabei entsprechend der Pfadangaben in der Umgebungsvariable AWKPATH gesucht. Ist diese Variable nicht gesetzt, so wird das aktuelle Verzeichnis und anschließend /usr/local/share/awk durchsucht.

Grundzüge der Sprache

Programmablauf

Ein awk-Programm besteht aus einer Folge von pattern-action statements:
Suchmuster { action statements }
Außerdem gibt es die Möglichkeit, Funktionen zu definieren:
function name(Parameterliste) { statements }
awk liest zunächst das komplette Programm ein und überführt es in eine interne Darstellung, die sich schneller ausführen läßt. Dann wird zunächst ein eventuell vorhandener BEGIN-Block abgearbeitet. Dieser Block umfaßt die Statements, die mit dem speziellen Suchmuster BEGIN angegeben wurden. Hier kann man z.B. Variable initialisieren.

Dann werden alle Dateien in der angegebenen Reihenfolge zeilenweise gelesen. Für jede Zeile wird geprüft, mit welchen Suchmustern diese übereinstimmt. Die für diese Suchmuster angegebenen Aktionen werden ausgeführt.

Wenn alle Eingabezeilen verarbeitet sind, werden die Bestimmungen im END-Block abgearbeitet. Der END-Block ist durch das spezielle Suchmuster END gekennzeichnet. Hier werden häufig Summen ausgegeben.

Das folgende Beispiel druckt eine etwas schöner formatierte Liste aus der /etc/passwd, wobei nur Benutzer aufgeführt werden, die die bash als Login-Shell benutzen:

BEGIN {
  FS=":";
  print "Bash-Benutzer:"
  print "-------------------------------------------------------------------";
  printf "%-15s %-30s %s\n", "user-id", "Name", "Home";
  print "-------------------------------------------------------------------";
}
/\/bin\/bash/ {printf "%-15s %-30s %s\n", $1, $5, $6}
bsp1.awk bsp2.csv

Sätze, Felder und Variable

awk verwendet dynamische Variable. Diese brauchen nicht deklariert zu werden und können Strings oder Gleitkommawerte enthalten.

Sätze entsprechen normalerweise Zeilen, sie werden also durch das LF voneinander getrennt. Man kann aber auch durch Setzen der speziellen Variable RS einen anderen Satztrenner festlegen. Wird RS mit einer leeren Zeichenkette belegt, so werden Sätze durch Leerzeilen getrennt.

Beim Lesen eines Satzes wird dieser von awk in Felder zerlegt. Die Trennung erfolgt normalerweise durch Whitespace (Leerzeichen oder Tabulatoren), kann aber durch Setzen der Variable FS anders geregelt werden. Wird FS mit einer leeren Zeichenkette belegt, so wird aus jedem einzelnen Zeichen ein Feld.

Enthalten RS und FS mehr als ein Zeichen, so wird ihr Inhalt als regulärer Ausdruck aufgefaßt. Oft findet man auch awk-Aufrufe folgender Gestalt:

awk -v 'FS=^\"?|\"?,\"?|\"?$' '{print $3, $2}' datei.csv
Hier wird die Zuweisung der Variable FS auf der Kommandozeile vorgenommen. Der oben angegebene Ausdruck zerlegt CSV-Dateien, wie sie von Excel oder dBase exportiert werden, in einzelne Felder.

Felder werden im Programm durch das Dollarzeichen mit nachgestellter Position im Eingabesatz bezeichnet. Dabei werden die Positionen mit 1 beginnend gezaehlt. $7 bezeichnet also das siebte Feld. Mit $0 erreicht man den gesamten Eingabesatz. Man kann auch Werte an Felder zuweisen. Auch ist es möglich, die Feldposition einer Variable zu entnehmen:

n=5
print $n
gibt das fünfte Feld aus.

Eingebaute Variable

awk verfügt über einige eingebaute Variable. Die wichtigsten davon sind:

ARGC
Anzahl der Befehlszeilenparameter
ARGV
Array der Befehlszeilenparameter. Die Indizes laufen von 0 bis ARGC-1. Durch ändern von ARGV kann man vom Script aus weitere Dateien öffnen.
CONVFMT
Das voreingestellte Format für Zahlen. Standardwert ist "%.6g".
ENVIRON
Stellt die Umgebungsvariablen als assoziatives Array zur Verfügung. Z.B. liefert ENVIRON["HOME"] unser Homerverzeichnis.
ERRNO
Text zum letzten aufgetretenen Fehler bei einer Dateioperation
FIELDWIDTHS
Wenn man diese Variable mit einer durch Leerzeichen getrennten Liste von Zahlen füllt, so werden die Felder nicht durch die in FS angegebenen Trennzeichen, sondern an den entsprechenden festen Positionen getrennt. Ich verwende das oft, um vom Host per FTP übertragene Dateien in Felder zu zerlegen und weiterzuverarbeiten.
FNR
Die Nummer des aktuellen Eingabesatzes. Ein awk '{print FNR, $0}' liefert ein Listing mit Zeilennummern.
IGNORECASE
Hat diese Variable einen von Null verschiedenen Wert, so werden alle Stringvergleiche, das Trennen der Eingabe mit FS bzw. RS und die Auswertung regulärer Ausdrücke unabhängig von Groß- bzw. Kleinschreibung vorgenommen.
NF
Liefert die Anzahl Felder im aktuellen Eingabesatz.
OFMT
Das Standard-Ausgabeformat für Zahlen. Voreingestellt ist "%.6g"
OFS
Das Feldtrennzeichen für die Ausgabe. Voreingestellt ist ein Leerzeichen.
ORS
Das Satztrennzeichen für die Ausgabe. Voreingestellt ist LF. Braucht man Zeilenenden im DOS-Format (CR+LF), kann man das (unter anderem) mit dem awk erledigen: awk -v 'ORS=\r\n' '{print $0}'

Es existieren weitere spezielle Variable, auf die hier nicht weiter eingegangen werden soll.

Typen

awk kennt keine echten Variablentypen, es wird nur mit Strings hantiert. Diese werden allerdings als Zahlen aufgefaßt, wenn das im Zusammenhang sinnvoll ist. Soll die Auswertung als Zahl erzwungen werden, so addiert man eine 0.

Neben den einfachen Strings kennt awk noch assoziative Arrays. Die Indizes eines solchen Arrays sind (im Gegensatz zu C) Strings. Man kann auch mehrdimensionale Arrays verwenden.

Suchmuster und Aktionen

Die Anweisungen des awk bestehen aus dem Suchmuster und den in geschweifte Klammern eingeschlossenen Aktionen. Fehlt das Suchmuster, so werden die Aktionen für alle Eingabezeilen durchgeführt. Fehlt die Aktion, so wird die Eingabezeile unverändert ausgegeben. Der folgende awk-Aufruf entspricht einem einfachen grep:

awk '/reg. Ausdruck/' Datei

Das Script kann Kommentare enthalten. Diese beginnen mit dem Zeichen # und erstrecken sich bis zum Zeilenende. Leerzeilen sind zulässig. Ein Statement endet normalerweise am Zeilenende. Um Statements auf der nächsten Zeile fortzusetzen, maskiert man das Zeilenende mit einem Backslash.

Suchmuster

Man kann folgende Suchmuster verwenden:

BEGIN und END wurden schon erläutert. Die regulären Ausdrücke entsprechen denen von egrep. Vergleichende Ausdrücke können umfangreicher sein. Man kann z.B. testen, ob bestimmte Felder einem regulären Ausdruck entsprechen. Die Operatoren &&, ||, ! und ? werden in Analogie zu C angewandt (logisches Und, Oder, Negation und wahlweise Ausführung). Durch Klammern kann man die Reihenfolge der Ausführung bestimmen. Zwei durch Komma getrennte Suchmuster ermitteln einen Bereich von Zeilen, wobei die erste vom ersten, die letzte vom zweiten Suchmuster bestimmt wird.

Aktionen

Die Aktionen werden in geschweifte Klammern eingeschlossen. Aktionen können Zuweisungen, Verzweigungen und Schleifen enthalten. Die Syntax ist dabei ähnlich C.

Operatoren

OperatorBedeutung
(...)Gruppierung
$Feldreferenz
++ --Inkrement und Dekrement, in postfix- oder präfix-Notation
^Potenzieren
+ - !unäres plus, minus und logische Negation
* / %Multiplikation, Division, Modulo-Funktion
+ -Addition, Subtraktion
spaceStringverkettung
< > <= >= == !=Vergleiche
~ !~Vergleich mit regulären Ausdrücken
inTest auf Enthaltensein in einem Array
&&Logisches Und
||Logisches Oder
?:Bedingter Ausdruck, wie in C
= += -= *= /= %= ^=Zuweisung, wie in C

Steueranweisungen

Die meisten Steueranweisungen haben eine direkte Entsprechung in C:

Ein/Ausgabe-Anweisungen

AnweisungBedeutung
close(Datei)Datei (oder Pipe) schließen
getlinenächste Zeile in $0 laden
getline <DateiNächste Zeile aus bestimmter Datei lesen
getline VariableNächste Zeile in Variable, statt $0, laden
getline Variable <DateiZeile aus Datei in Variable laden
nextNächste Zeile lesen und ab Anfang des Scriptes bearbeiten
nextfileAktuelle Datei schließen und mit nächster fortfahren
printGibt den aktuellen Satz aus
print AusdruckslisteGibt Ergebnis der Ausdrücke aus.
print Ausdrucksliste >DateiSchreibt Ergebnis der Ausdrücke in Datei
printf Format, AusdruckslisteGibt Ergebnisse formatiert aus
printf Format, Ausdrucksliste >Dateiformatierte Ausgabe in Datei
system(Befehlszeile)Führt einen Befehl aus
fflush([Datei])Erzwingt das Schreiben der Puffer

Andere Dateiumleitungen als > sind ebenfalls erlaubt. >> hängt Daten an eine bestehende Datei an, | schreibt die Daten in eine Pipe.

Das Format für printf stimmt im wesentlichen mit dem für die entsprechende C-Funktion überein.

Stringfunktionen

Hier erläutere ich nur einige der Stringfunktionen, genaueres erfährt man in der Manual Page.
gensub(r, s, h [, t])
Ersetzt einige oder alle Vorkommen eines einem reg. Ausdruck entsprechenden Teilstrings. Das entspricht dem substitute im sed bzw. vi.
gsub(r, s [, t])
Macht dasselbe wie gensub, nimmt die Ersetzung aber direkt im entsprechenden Feld vor und liefert die Anzahl vorgenommener Ersetzungen.
index(s, t)
Liefert die Position der Zeichenkette s in t, bzw. 0, wenn s nicht in t enthalten ist.
length([s])
Liefert die Länge von s. Ohne Angabe von s erhält man die Länge des Eingabesatzes $0
match(s,r)
Liefert die Position in s, ab der der reguläre Ausdruck r paßt, anderenfalls 0. Diese Funktion wird häufig dazu genommen, in einem Suchmuster zu bestimmen, ob ein Feld einem reg. Ausdruck entspricht. Beispiel:
awk -F: 'match($7,"/bin/bash")' /etc/passwd
split(s,a,[, r])
Zerlegt den String s in ein Array a und liefert die Anzahl Teilstrings. Die Zerlegung erfolgt an Hand des reg. Ausdrucks r. Falls dieser weggelassen wird, verwendet awk den Inhalt der Variable FS
sprintf(Format, Ausdrucksliste)
Liefert formatierte Ergebnisse der Ausdrücke als String.
sub(r, s [, t])
wie gsub, nimmt aber nur die erste Ersetzung vor.
substr(s, i [, n])
Liefert den n Zeichen langen Teilstring von s ab Position i. Wird n weggelassen, so erhält man den Rest von s ab Position i.