Vorlesung "UNIX"

von Prof. Jürgen Plate

9 Die Skriptsprache awk

9.1 Überblick

awk ist eine interpretierende Programmiersprache, die für das Manipulieren und Durchsuchen von Textdateien konzipiert wurde. Der Name awk steht dabei nicht etwa für eine bestimmte Aufgabe, Funktion oder Eigenschaft sondern setzt sich einfach aus den Anfangsbuchstaben der Nachnamen der Erfinder dieser Sprache, Aho, Kernighan und Weinberger, zusammen.

Und damit es da gar kein Mißverständnis gibt: Sprechen Sie awk nicht wie das englische "awkward" oder "awful" aus. Sagen Sie einfach "a w k"!

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

Sie sollen zunächst einen Überblick der Sprache erhalten. In den folgenden Abschnitten werden dann die einzelnen Sprachkonstrukte etwas detaillierter behandelt.
Die Sprache awk ist recht einfach. Man kann Sie ohne weiteres aus der ziemlich umfangreichen Man-Page erlernen. Da es sich um einen Interpreter handelt, sind awk-Programme nicht besonders schnell. Man sollte also keine zu großen und komplexen Anwendungen in awk formulieren. Für Prototypen und kleine Toos ist awk aber bestens geeignet.

9.2 Aufruf und Optionen

Im einfachsten Fall ist das auszuführende Skript 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}
Das eigentliche awk-Programm muss vor der Auswertung durch die Shell geschützt werden, deshalb die Einbettung in einfache Hochkommata. 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-Skript lieber in eine Datei. Der Programmaufruf sieht dann so aus:

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

Üblicherweise wird das Ergebnis eines awk-Aufrufs auf die Standardausgabe (was meistens der Bildschirm ist) geschrieben. Ist das nicht gewünscht, so muß die Ausgabe auf eine Ergebnisdatei umgeleitet oder in ein weiteres Programm gepipt werden.

Zur Demonstration wird im folgenden Programm das erste Feld der Passwortdatei ausgeben (der Feldseparator ist der Doppelpunkt), wobei die Zeilen zusätzlich numeriert werden:

awk -F : '{print NR,$1}' /etc/passwd

1 root
2 bin
3 daemon
4 lp
5 news
...

9.3 Grundzüge der Sprache

9.3.1 Programmablauf

Im Prinzip sieht jedes awk-Programm folgendermaßen aus:

Bedingung1 { Aktion1 }
Bedingung2 { Aktion2 }
Bedingung3 { Aktion3 }
...
Bedingungn { Aktionn }

Als Bedingung wird meist ein Suchmuster (pattern) oder regulärer Ausdruck angegeben.

Aktionen sind immer in geschweifte Klammern eingeschlossen, wobei die öffnende Klammer immer in der gleichen Zeile stehen muß wie das Pattern. Mehrere Anweisungen werden durch Strichpunkt getrennt. Die Aktion wird in einer C-ähnlichen Sprache formuliert. Der Aktionsteil darf sich auch auch über mehrere Zeilen erstrecken. Im übrigen sind awk-Programme frei formatierbar (unter Berücksichtigung der obigen Einschränkungen).

In einer Programmzeile kann entweder die Bedingung oder der Aktionsteil weggelassen werden. Fehlt die Bedingung, wird die Aktion immer ausgeführt. Fehlt die Aktion, wird die Zeile komplett ausgegeben.

Außerdem gibt es die Möglichkeit, Funktionen zu definieren:

function Name(Parameterliste) { Anweisungen }

$1, $2, $3 usw. sind Zeichenketten und können dementsprechend im Aktionsteil manipuliert und verwendet werden.

Mit diesen Vorkenntnissen sollte die Funktionsweise des ersten Programms leicht nachvollziehbar sein; es zählt einfach nur die Zeilen der Eingabe und gibt das Resultat aus:

BEGIN {
  print "Zählen von Eingabezeilen";
  zaehler = 0
  }
 
{ zaehler++ }

END { print "Ergebnis: " zaehler }

Ein weiteres einfaches Beispiel: Gegeben sei eine Datei, die Zeilen der Art

    ...
    Emil Hofer 23.08.1960 1234 175 cm 
    Karl Müller 29.02.1957 1236 160 cm
    ...

mit den Angaben "Vorname", "Nachname", "Geburtsdatum", "Nebenstellen-Telefonnummer" und "Gewicht" enthält. Es soll eine Ausgabedatei generiert werden, die nur noch Geburtsdatum und Vorname (in dieser Reihenfolge) enthält. awk kennt folgenden AUsgabefunktionen:

Damit kann das erste awk-Programm formuliert werden, was das Gewünschte leistet:
     {print $3, $1}
Da ein Bedingungsteil nicht vorhanden ist, wird die aufgeführte Aktion unabhängig von Bedingungen für alle Eingabezeilen ausgeführt: Mitttels der awk-Standard-Funktion print wird der Inhalt der Felder $3 und $1 in einem Standard-Format ausgegeben.

Das folgende Beispiel druckt eine etwas schöner formatierte Liste aus der /etc/passwd, wobei die C-ähnliche awk-Standard-Funktion printf verwendet wird:

BEGIN {
  FS=":";
  print "-------------------------------------------------------------------";
  printf "%-15s %-30s %s\n", "user-id", "Name", "Home";
  print "-------------------------------------------------------------------";
  }
  {printf "%-15s %-30s %s\n", $1, $5, $6}

In obigen Beispielen ist die Aktion von keiner Bedingung abhängig. Als Bedingung können jedoch ohne weiteres Vergleiche und logische Ausdrücke angegeben werden, wie im folgenden Beispiel gezeigt wird:

     $4>170 && $6<=80   {print $0}
Angewendet auf die obige Eingabedatei bedeutet das, daß nur noch diejenigen Eingabezeilen ($0) aufgelistet werden, für die Größe>170 ($4>170) und (&&) Gewicht<=80 ($6<=80) gilt.

Bedingungen (Muster, Patterns)

Als häufigste Bedingung werden in awk wohl die von anderen UNIX-Programmen her bekannten Zeichenkettenmuster in Form regulärer Ausdrücke verwendet. Dazu zwei Beispiele:
     /M[ea][iy]e?r/   {print $2, $3}
Als Bedingung ist hier ein regulärer Ausdruck aufgeführt, mit dem alle Eingabezeilen erkannt werden, die den Namen "Meier" (mit möglichen Varianten wie "Mayer", "Maier", "Meyr", "Meir", "Mayr" usw.) enthalten (Das Fragezeichen im regulären Ausdruck deutet an, daß das unmittelbar davor aufgeführte Element auch entfallen kann). Für die betreffenden Zeilen wird dann der Inhalt der Felder $2 und $3 ausgegeben.

$5 !~ /^[0-9][0-9][0-9]$/   {print $1, $2, $5}
Mit Hilfe der hier verwendeten Bedingung kann überprüft werden, ob das Eingabefeld $5 (Körpergröße der Datei aus dem ersten Beispiel) einer Eingabezeile eine für ein bestimmtes Problem korrekt gebildete Zahl enthält. Genauer gesagt, es wird getestett, ob ein angegebenes Muster nicht (!~) auf das Eingabefeld $5 "paßt".
Ist das der Fall, werden die Felder $1, $2 und $5 der betreffenden Zeile ausgegeben. Das Muster selbst beschreibt eine Zeichenkette, die aus drei Ziffern besteht.

In den bisherigen Programmbeispielen wurden die awk-Standard-Funktionen print und printf verwendet. Eine fast ebenso häufig eingesetzte Funktion ist gsub, mit der Zeichenketten in einer Zeile gegen andere ausgetauscht werden können. Dazu ein Beispiel:

     /ä/ {gsub(/ä/,"&auml;")}
     /Ä/ {gsub(/Ä/,"&Auml;")}
     /ö/ {gsub(/ö/,"&ouml;")}
     /Ö/ {gsub(/Ö/,"&Ouml;")}
     /ü/ {gsub(/ü/,"&uuml;")}
     /Ü/ {gsub(/Ü/,"&Uuml;")}
     /ß/ {gsub(/ß/,"&szlig;")}
     {print}
Mit Hilfe der in den Bedingungen angegebenen Muster für die deutschen Umlaute und das scharfe S und der awk-Funktion gsub werden in den betreffenden Zeilen diese Zeichen gegen die entsprechenden HTML-Umschreibungen ausgetauscht. Die letzte Programmzeile ist notwendig, damit das Ergebnis der vorgenommenen Umsetzungen auch die Standardausgabe ausgegeben wird.

Das folgende Beispiel zeigt ein Mini-Programm in der Kommandozeile:

awk ' {printf("%s-%4d %s\n", FILENAME, FNR, $0) }' eingabe*
Es werden die Zeilen aller Dateien, deren Name mit "eingabe" beginnt, numeriert und mit dem Dateinamen auf der Standardausgabe aufgelistet.

Umbenennen von Dateien (append ".new" to "files_list"):

ls files_list | awk '{print "mv "$1" "$1".new"}' | sh

9.3.2 Konstante, Variable, Sätze und Felder

awk kennt keine echten Datentypen, es wird nur mit Strings gearbeitet. Diese werden allerdings als Zahlen aufgefaßt, wenn das im Zusammenhang sinnvoll ist.

Konstante

Zwei Arten von Konstanten können auftreten:

Variable

awk verwendet dynamische Variable. Diese brauchen nicht deklariert zu werden und können Strings oder Gleitkommawerte enthalten. Variablen können sowohl Strings, als auch numerische Werte beinhalten. Es muß ihnen kein expliziter Datentyp zugeordnet werden. Während eines Programmablaufs kann dieselbe Variable sowohl Strings, als auch numerische Werte speichern. Der Typ einer Variablen wird immer aus dem Kontext bestimmt. Hierbei werden ggf. Umwandlungen gemäß OFMT vorgenommen. Jede Variable wird mit einem Nullstring bzw. mit Null initialisiert. In Ausdrücken können 3 Arten von Variablen verwendet werden:

Sätze

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
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.

Arrays (Vektoren)

awk verfügt über ein- und mehrdimensionale Vektoren. Diese weisen zwei Besonderheiten auf:

Ein Vektorelement wird durch vektor [index] angesprochen oder initialisiert.

Beispiel: Zählen von Worten

	{
	gsub(/[.,:;!?(){}-+]/,"")   # Interpunktionszeichen weg 
	for (i=1 ; i<=NF ; i++)
		feld[i]=feld[i]+1 
	}
END {
     for (i in feld)
       print i, feld[i]
     }
Mehrdimensionale Vektoren verhalten sich wie eindimensionale Vektoren. Intern werden sie zu solchen mit Hilfe von SUBSEP umgeformt. Die Indizes werden durch Kommata voneinander getrennt. Im Zusammenhang mit Operatoren sollten die Tupel mit runden Klammern eingeschlossen werden, z. B.:
	if ((i,j,k) in vektor)

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

9.3.3 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 Skript 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.
FILENAME
Name der aktuellen Eingabedatei.
FNR
Die Nummer des aktuellen Eingabesatzes. Ein awk '{print FNR, $0}' liefert ein Listing mit Zeilennummern.
FS
legt die Trennzeichen für die Felder in einer Eingabezeile (= Record, siehe RS) fest; Voreinstellung: " ". FS kann ein Einzelzeichen, ein regulärer Ausdruck oder ein leerer String sein. Im letzten Fall ist jedes Einzelzeichen ein eigenes Feld.
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.
NR
Anzahl der bisher gelesenen Eingabezeilen.
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}'
RLENGTH
Länge der Zeichenkette, die durch den letzten Aufruf der Funktion match gefunden wurde.
RS
Trennzeichen zwischen einzelnen Eingabezeilen; Voreinstellung: "\n" RS kann ein Einzelzeichen, ein regulärer Ausdruck oder ein leerer String sein. Im letzten Fall werden die einzelnen Sätze durch Leerzeilen getrennt.
RSTART
Anfangsposition der Zeichenkette, die durch den letzten Aufruf der Funktion match gefunden wurde
SUBSEP
Trennzeichen für die Indizes "mehrdimensionaler" Felder; Voreinstellung: "\034"

9.3.4 Sonderzeichen in regulären Ausdrücken

Zur Wiederholung und damit man nicht blättern muß, hier nochmals die Zeichen mit besonderer Bedeutung in regulären Ausdrücken.

^ Beginn einer Zeichenfolge.
$ Ende einer Zeichenfolge.
. Jedes beliebige Zeichen.
* Das vorangehende Muster beliebig oft (auch keinmal).
+ Das vorangehende Muster einmal oder mehrmals.
? Das vorangehende Muster keinmal oder einmal.
\ Sonderbedeutung des nachfolgenden Metazeichens aufheben. Dazu gehören auch folgende Escape-Sequenzen:
\b
\f
\n
\r
\t
\ooo Zeichen, das dem Oktalwert ooo entspricht
| Logisches oder.
[ab] Zeichenmenge (a und b).
[a-z] Zeichenmenge (alle Zeichen von a bis z).
[^ab] Verneinte Zeichenmenge (nicht a oder b).
( ) Klammerung für Teile komplexerer Muster

9.3.5 Zusammengesetzte reguläre Ausdrücke

Seien A und B reguläre Ausdrücke:

A | B Alternation (A oder B)
A B Konkatenation (A gefolgt von B)

9.4 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 Skript 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.

9.4.1 Suchmuster

Man kann folgende Suchmuster verwenden:

Beispiel 1: Der Selektor

    ((NF > 2 ) || (NF < 8)) && ($0 !~ /^[^#]/ )
wählt alle Zeilen aus, die zwischen 3 und 7 Feldern besitzen und keine Kommentarzeilen sind (also mit "#" beginnen).

Mit "Zeichenkette ~ /Muster/" lassen sich beliebige Zeichenketten vergleichen. So extrahiert nachfolgendes Awk-Programm alle Benutzernamen aus der Datei /etc/passwd, deren Heimatverzeichnis unterhalb von /home liegt:

awk -F ':' '$6 ~ /^\/home/ {print $1}' /etc/passwd
Die zu vergleichende Zeichenkette ist hier das 6. Feld ($6) der Passwortdatei (Heimatverzeichnis).

Zeichenklassen

Guter Programmierstil ist die Verwendung sogenannter "Zeichenklassen". Ihre Syntax entspricht nicht nur dem POSIX-Standard, sondern berücksichtigt auch länderspezifischen Unterschiede. Ihr Programm wird somit portabel und ist nicht mehr von einem bestimmten Zeichensatz abhängig. Dazu gleich ein Beispiel:
#!/usr/bin/awk -f

BEGIN {
  String = ÄÖÜäöü
  if (String ~ /[A-Za-z]/)    print "A-Z funktioniert.";
if (String ~ /[[:alpha:]]/) print "Alpha funktioniert.";
}

[:alnum:] Alphanumerische Zeichen
[:alpha:] Alphabetische Zeichen
[:blank:] Leerzeichen und Tabulatoren
[:cntrl:] Steuerzeichen
[:digit:]Numerische Zeichen
[:graph:] Druck- und sichtbare Zeichen (Ein Leerzeichen ist druckbar aber nicht sichtbar, wogegen ein "a" beides ist)
[:print:]Druckbare Zeichen (also keine Steuerzeichen)
[:punct:] Punktierungszeichen Punctuation characters (Zeichen die keine Buchstaben, Zahlen, Steuerzeichen oder Leerzeichen sind, z. B. ".", "," ":")
[:space:]Druckbare aber nicht sichtbaren Zeichen (Leerzeichen, Tabulatoren, Zeichenende, etc.)
[:lower:] Kleinbuchstaben
[:upper:] Großbuchstaben
[:xdigit:] Hexadezimale Zeichen (0-9,A-F,a-f)

Um zu testen, ob in einer Variablen eine gültige Zahl (ganzzahlig) gespeichert ist, bietet sich folgendes Konstrukt an:

echo "4711" | awk '/^[[:digit:]]+$/ {print "eine Zahl"}'

9.4.2 Aktionen

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

9.4.3 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; rechter String im linken enthalten/nicht enthalten
in Test auf Enthaltensein in einem Array
&& Logisches Und
|| Logisches Oder
? : Bedingter Ausdruck, wie in C (A1 ? A2 : A3)
=    +=    -=    *=
/=    %=    ^=
Zuweisung, wie in C

9.4.4 Aktionen (Steueranweisungen)

Während Sie mit Mustern die Zeilen adressieren, legen Sie mit Aktion fest, was für diese Zeile zu tun ist. Eine Aktion kann sich aus vielen Anweisungen zusammensetzen. Dieses sind dann durch Semikolon oder neue Zeile voneinander zu trennen. Die meisten Steueranweisungen haben eine direkte Entsprechung in C. Anweisungen in einer Aktion können sein:

9.4.5 Ein- und Ausgabe-Anweisungen

Die beiden Funktionen print und printf wurden schon am Anfang behandelt. Für die Eingabe gibt es noch die Funktion getline. Die folgende Tabelle faßt die Ein- und Ausgabemöglichkeiten zusammen:

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 Skriptes bearbeiten.
nextfileAktuelle Datei schließen und mit nächster fortfahren.
printGibt den aktuellen Satz aus.
print AusdruckslisteGibt Ergebnis der Ausdrücke aus. Auch: print (...).
print Ausdrucksliste > DateiSchreibt Ergebnis der Ausdrücke in Datei.
print Ausdrucksliste | ProgrammSchreibt Ergebnis der Ausdrücke in eine Pipe.
printf Format, AusdruckslisteGibt Ergebnisse formatiert aus. Auch: printf (...).
printf Format, Ausdrucksliste > Dateiformatierte Ausgabe in Datei.
printf Ausdrucksliste | ProgrammSchreibt Ergebnis der Ausdrücke in eine Pipe.
fflush([Datei])Erzwingt das Schreiben der Puffer

Bei der Ausgabe werden die Variablen $0, OFS und ORS berücksichtigt.

Zur Ausgabe in Dateien werden die Zeichen der Ein- und Ausgabeumleitung von UNIX verwendet. Dateinamen müssen in Anführungszeichen (" ") eingeschlossen werden!
Beispiel: (Entsprechend des Wertes der Zahl in der ersten Spalte der Datei soll die Datei in zwei Dateien zerlegt werden.

awk '$1 > 100 { print > "klein"};
     $1 <= 100 {print > "gross"}'
Die Ausgabe einer print- bzw. printf-Anweisung kann sogar in eine Pipe geschrieben werden. Das folgende Programm sortiert alle Zeilen einer Datei die den String "test" enthalten:
awk '/test/ { print | "sort" }'

Dateien, die Sie öffnen, sollten Sie auch wieder schließen. Zum einen dürfen Sie meistens nur eine endliche Zahl von Dateien gleichzeitig geöffnet haben, zum anderen kann es Probleme geben, wenn die Dateien auch von anderen Prozessen verwendet werden sollen (Logfiles, Infodateien etc.). Auch Pipes sollten wieder geschlossen werden! Sie erreichen dies mit:

close(dateiname)
close(kommando)

Hierbei muß der komplette Name mit Pfad - wie beim Öffnen - wieder angegeben werden.

Das Format für printf stimmt im wesentlichen mit dem für die entsprechende C-Funktion überein. Die printf-Kontrollzeichen (werden immer mit einem Prozentzeichen eingeleitet):

Zeichendrucke den entsprechenden Ausdruck als
cASCII-Zeichen
dIntegerwert (dezimal)
e[-]d.ddddddE[+-}dd
f[-]ddd.dddddd
ge oder f Format (wählt das kürzere)
oOktalwert (ohne Vorzeichen)
sZeichenkette
xHexadezimalwert (ohne Vorzeichen)
%Verwendet kein Argument! gibt ein % aus!

Eingabe mit getline

Mit dieser Funktion kann explizit der nächste Eingabe-Rekord gelesen werden. Das Programm fährt dabei mit der Abarbeitung fort und wird nicht neu gestartet. getline kann drei Rückgabewerte annehmen: 1, wenn der Rekord gelesen werden konnte; 0, wenn das Dateiende erreicht ist und -1, wenn beim Lesen ein Fehler auftrat. getline kann auf folgende Weisen eingesetzt werden:

Ausdrucksetzt folgende Variablen
getline$0, NF, NR, FNR
getline variablevariable, NR, FNR
getline <dateiname$0, NF
getline variable <dateinamevariable
kommando | getline$0, NF
kommando | getline variablevariable

9.4.6 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.
toupper(s)
Konvertierung in Großbuchstaben
tolower(s)
Konvertierung in Kleinbuchstaben

9.4.7 System-Funktionen

Die Funktion system(ausdruck) führt das durch ausdruck spezifizierte Kommando in der Shell aus. Der Rückgabewert der Funktion ist der Exit-Status des Kommandos.

Die Funktion systime() liefert die Systemzeit in Sekunden seit dem 1.1.1970 0.00 Uhr.

Die Funktion strftime(Format [, Zeit]) liefert eine formatierte Zeitausgabe. Ausgabe der Systemzeit, wenn nur der Formatstring angegeben wird.

Beispiel:

BEGIN { 
  start = systime }

  { for(i = 1; i > 100000; i++)}

END { 
  end = systime;
  print "Gesamtzeit betrug " end - start " Sekunden." }

9.4.8 Arithmetische Funktionen

FunktionBeschreibung
atan2(x,y) Arcustangens von x/y (Im Bereich von -Pi bis +Pi)
cos(x) Cosinus von x
exp(x) Exponentialfunktion von x (e hoch x)
int(x) ganzzahliger Anteil von x
log(x) natürlicher Logarithmus von x
rand() Zufahlszahl im Bereich 0 <= r < 1
sin(x) Sinus von x
sqrt(x) Quadratwurzel von x
srand([x]) Ausgangswert für rand ändern

9.5 Benutzerdefinierte Funktionen

In awk (genauer nawk, gawk) können auch Funktionen definiert werden. Die Definitionen werden üblicherweise am Ende des Programms notiert. Deklarationen sind nicht nötig. Eine Funktionsdefinition genügt der Form
function name(par1,par2, ...) 
  {
  Anweisungen
  }
Die öffnende Klammer muß direkt auf den Funktionsnamen folgen. Der Funktionsname muß eindeutig gewählt werden. Die Parameterliste inst eine Folge von Variablennamen, die durch Komma getrennt sind. Parameterübergabe erfolgt bei skalaren Variablen als call-by-value, bei Arrays hingegen als call-by-reference. Daher lassen sich die Array-Inhalte innerhalb der Funktion ändern. Parameter sind lokale Variablen, globale Variable gleichen Namnes sind daher in der Funktion nicht zugreifbar.

Benutzerdefinierte Funktionen sind typlos, die gleiche Funktion kann ganze Zahlen, Gleitpunktzahlen oder Strings zurückgeben. Die Funktione wird beendet, wenn die letzte Anweisung abgearbeitet wurde. Üblicherweise wir eine Funktion mit der Anweisung

   return [Ausdruck]
beendet. Ist ein Ausdruck angegeben, wird der Wert dieses Ausdrucks an das aufrufende Programm zurückgegeben.

Innerhalb der Funktion neu eingeführte Variablen sind global. Die Funktionsargumente sind nur im Funktionsrumpf gültig. Neue Variablen, die im Rumpf definiert werden, sind global gültig. Werden beim Aufruf weniger Parameter als vorgeschrieben angegeben, so werden die fehlenden Parameter mit "" initialisiert.

9.6 awk-Aufruf

Der Aufruf von awk wurde schon am Anfang (9.2) kurz behandelt. Hier noch einige Ergänzungen.

9.6.1 Variablen in der Kommandozeile setzen

awk '...' flag=1 datei flag=0 datei

Wertzuweisung erfolgt zu dem Zeitpunkt, zu dem auf eine durch den Parameter angegebene Datei zugegriffen würde

9.6.2 Zugriff auf Shell-Parameter

  1. Möglichkeit:
    Sei var ein Shell-Parameter.
  2. Möglichkeit:
    Wird das awk-Programm mit "..." geklammert, so bezeichnen $0, $1 etc. die Shell-Parameter. Die awk-Feldvariablen müssen dann über \$1 oder \\$1 (je nach Shell) referenziert.
  3. Möglichkeit
    Shell-Parameter in der awk-Kommandozeile an Variablen zuweisen (siehe 9.6.1).

9.6.3 Die Kommandozeilenoptionen

Einige Optionen wurden schon verwendet, ohne Bedeutung näher zu erläutern. Dies soll nun nachgeholt werden:

-F Feldtrenner
awk arbeitet auf Feldern einer Eingabezeile. Normalerweise dient das Leerzeichen/Tabulator zur Trennung einzelner Felder. Mit der Option -F wird der Wert der internen Variable FS (field separator) verändert.
-v Variable=Wert
Eine im Programm verwendete Variable kann somit "von außen" initialisiert werden (eine interne Initialisierung wird damit nicht überschrieben).
-f Programmdatei
awk liest den Quellcode aus der angegebenen Datei.
-W compat
GNU awk verhält sich wie UNIX awk, d.h. die GNU-Erweiterungen werden nicht akzeptiert.
-W help
Eine Kurzanleitung wird ausgegeben.
-W posix

GNU awk hält sich exakt an den POSIX-Standard.

9.7 Beispiele

9.7.1 Notendurchschnitt

Gegeben ist eine Datei mit Studentennamen und Noten, wobei jeder Student beliebig oft auftauchen darf. Zum Beispiel:
Maier   3.3
Mueller 1.3
Huber   2.3
Maier   4.3
Huber   2.6
Maier   4.6
Das folgende Programm berechnet die Durchschnittsnote für jeden Studenten:
    { sum[$1] += $2 ; oft[$1] += 1 } 
END { for (Student in sum)
         print Student, sum[Student]/oft[Student] 
    }
Die Programmausgabe für das Beispiel lautet:
Huber 2.45
Mueller 1.3
Maier 4.06667

9.7.2 Wortliste

Wie schon bei der Erläuterung der Arrays, wird hier die Anzahl jedes Wortes einer Datei ermittelt. Nur wird hier mit Hilfe eines regulären Ausdrucks festgelegt, daß die awk-Variable FS (in der die Feldtrennzeichen für die Eingabezeilen festgelegt werden) bestimmte Zeichen nicht annimmt. Alle anderen nicht aufgeführten Zeichen sind dann Trennzeichen.
BEGIN {FS="[^a-zA-Z0-9äÄöÖüÜáàâéèêíìîóòôúùû]"}
       {for (j=1; j<=NF; j++)
          {anzahl [$j] ++}
       }
END   {for (j in anzahl)
          {printf ("%-40s %4d\n", j, anzahl [j] ) }
       }

9.7.3 Spielen mit dem Mailspool

Beispielsweise werden von Zeit zu Zeit werden einige spezielle Nachrichten (z. B. monatliche Berichte) in einem speziellen Format (Subject: '[Monatsbericht] Monat , Abteilung') geschickt. Plötzlich, am Ende des Jahres entscheiden wir uns all diese Nachrichten zusammen, getrennt von den anderen, zu speichern.
  BEGIN {  # Variablen setzen
  BEGIN_MSG  = "From"
  BEGIN_BDY  = "Precedence:"
  MAIN_KEY   = "Subject:"
  VALIDATION = "[MONATSBERICHT]"
 
  HEAD = "NO"; BODY = "NO"; PRINT="NO"
  OUT_FILE = "Month_Reports"
  }
 
  { # keine Bedingung, also alle Zeilen bearbeiten
  if ( $1 == BEGIN_MSG ) {
    HEAD = "YES"; BODY = "NO"; PRINT="NO"
    }
 
  if ( $1 == MAIN_KEY ) {
    if ( $2 == VALIDATION ) {
      PRINT = "YES"
      $1 = ""; $2 = ""
      print "\n\n"$0"\n" >> OUT_FILE
      }
    }
 
  if ( $1 == BEGIN_BDY ) {
    getline
    if ( $0 == "" ) {
      HEAD = "NO"; BODY = "YES"
    } else {
      HEAD = "NO"; BODY = "NO"; PRINT="NO"
      }
    }
 
  if ( BODY == "YES" && PRINT == "YES" ) {
    print $0 >> OUT_FILE
    }
  }

9.7.4 Rechner

Das folgende Beispiel beschreibt einen Taschenrechner, der Eingaben in der Infixnotation akzeptiert. Das Programm muß folgende Grammatik nachbilden, die einen Infixausdruck beschreibt:
        
        expr    ->      term
                        expr + term
                        expr - term
        term    ->      factor
                        term * factor
                        term / factor
        factor  ->      number
                        ( expr )
Die obige Grammatik beinhaltet auch die unterschiedliche Wertigkeit und die Assoziativität der Operatoren. Das zugehörige Programm sieht folgendermaßen aus:
# Infix Rechner
# Das Hauptprogramm wird nur auf die aktuelle Eingabezeile
# angewendet, wenn sie mehr als ein Feld beinhaltet. D. h. 
# in jeder Zeile darf nur ein Ausdruck stehen und seine 
# Operanden muessen von Leerzeichen eingeschlossen werden.
# z. B. ( 5 + 2 ) * 3

NF > 0 {
        f = 1
        e = expr()
        if (f <= NF) printf("error at %s\n", $f)
        else printf("\t%.8g\n", e)
}

# Unterprogramme, die zur Erkennung der Struktur des
# Ausdrucks verwendet werden.

function expr( e) {             # term | term [+-] term
        e = term()
        while ($f == "+" || $f == "-")
                e = $(f++) == "+" ? e + term() : e - term()
        return e
}

function term( e) {             # factor | factor [*/] factor
        e = factor()
        while ($f == "*" || $f == "/")
                e = $(f++) == "*" ? e * factor() : e / factor()
        return e
}

function factor( e) {           # number | (expr)
        if ($f ~ /^[+-]?([0-9]+[.]?[0-9]*|[.][0-9]+)$/) {
                return $(f++)
        } else if ($f == "(") {
                f++
                e = expr()
                if ($(f++) != ")")
                        printf("error: missing ) at %s\n", $f)
                return e
        } else {
                printf("error: expected number or ( at %s\n", $f)
                return 0
        }
}
Zum Inhaltsverzeichnis Zum nächsten Abschnitt
Copyright © FH München, FB 04, Prof. Jürgen Plate