Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
Shell-Programmierung von Jürgen Wolf
Einführung, Praxis, Referenz
Buch: Shell-Programmierung

Shell-Programmierung
782 S., mit CD, 44,90 Euro
Rheinwerk Computing
ISBN 3-89842-683-1
gp Kapitel 13 awk-Programmierung
  gp 13.1 Einführung und Grundlagen von awk
    gp 13.1.1 History und Versionen von awk
    gp 13.1.2 Die Funktionsweise von awk
  gp 13.2 Aufruf von awk-Programmen
    gp 13.2.1 Grundlegender Aufbau eines awk-Kommandos
    gp 13.2.2 Die Kommandozeilen-Optionen von awk
    gp 13.2.3 awk aus der Kommandozeile aufrufen
    gp 13.2.4 awk in Shellscripts aufrufen
    gp 13.2.5 awk als eigenes Script ausführen
  gp 13.3 Grundlegende awk-Programme und -Elemente
    gp 13.3.1 Ausgabe von Zeilen und Zeilennummern
    gp 13.3.2 Felder
  gp 13.4 Muster (bzw. Adressen) von awk-Scripts
    gp 13.4.1 Zeichenkettenvergleiche
    gp 13.4.2 Vergleichsausdrücke
    gp 13.4.3 Reguläre Ausdrücke
    gp 13.4.4 Zusammengesetzte Ausdrücke
    gp 13.4.5 BEGIN und END
  gp 13.5 Die Komponenten von awk-Scripts
    gp 13.5.1 Variablen
    gp 13.5.2 Arrays
    gp 13.5.3 Operatoren
    gp 13.5.4 Kontrollstrukturen
  gp 13.6 Funktionen
    gp 13.6.1 Mathematische Funktionen
    gp 13.6.2 Funktionen für Zeichenketten
    gp 13.6.3 Funktionen für die Zeit
    gp 13.6.4 Systemfunktionen
    gp 13.6.5 Ausgabefunktionen
    gp 13.6.6 Eingabefunktion
    gp 13.6.7 Benutzerdefinierte Funktionen
  gp 13.7 Empfehlung


Rheinwerk Computing

13.5 Die Komponenten von awk-Scriptdowntop

Die Verwendung von awk wurde bisher vorwiegend mit Einzeilern demonstriert, was häufig für den Hausgebrauch in Shellscripts ausreicht. So haben Sie die Mächtigkeit von awk schon näher kennen gelernt. Allerdings wurde bereits erwähnt, dass awk eher eine Programmiersprache als ein Tool ist, weshalb hier nun näher auf die Eigenheiten von awk als Programmiersprache eingegangen werden soll. Natürlich können Sie die awk-Scripts auch in Ihren Shellscripts einsetzen, womit Sie sich bedeutendes Know-how aneignen und sich gern als Guru bezeichnen dürfen.

Gewöhnlich verwendet man beim Erstellen eigener awk-Scripts das Verfahren mit der She-Bang-Zeile (#!) in der ersten Zeile:

#!/usr/bin/awk -f

Kommentare können Sie in gleicher Weise – wie schon bei den Shellscripts – mit # kennzeichnen.

#!/usr/bin/awk -f
#
# Programmname: programmname.awk
# Erstellt    : J.Wolf
# Datum       : ...
# ...

Eine Dateiendung ist genauso unnötig wie in Shellscripts. Trotzdem wird hierbei relativ häufig die Endung ».awk« verwendet, damit der Anwender weiß, worum es sich bei diesem Script handelt. Man kann die Aktionen in einem awk-Script in einer Zeile schreiben, wobei hier dann jede Aktion durch ein Semikolon getrennt werden muss:

# Anfang Haupt-Aktionsteil eines awk-Scripts
{
   aktion ; aktion ; aktion
}
# Ende Haupt-Aktionsteil eines awk-Scripts

Oder aber, was empfehlenswerter ist, man schreibt jede Aktion in eine extra Zeile:

# Anfang Haupt-Aktionsteil eines awk-Scripts
{
   aktion
   aktion
   aktion
}
# Ende Haupt-Aktionsteil eines awk-Scripts

Rheinwerk Computing

13.5.1 Variablen  downtop

Neben den dynamisch angelegten Feldvariablen stehen Ihnen in awk auch benutzerdefinierte Variablen zur Verfügung. Die Variablen werden mit derselben Syntax wie schon in der Shell definiert und behandelt. Bei der Verwendung wird jedoch vor die Variable kein Dollarzeichen ($) gestellt, weil dieses Zeichen für die einzelnen Felder (bzw. Wörter) reserviert ist. Zahlen können Sie ohne weitere Vorkehrungen übergeben:

# val1 mit dem Wert 1234 definieren
val1=1234
# val2 mit dem Wert 1234.1234 definieren
val2=1234.1234

Zeichenketten müssen Sie allerdings zwischen doppelte Hochkommata schreiben:

# string1 mit einer Zeichenkette belegen
string1="Ich bin ein String"

Machen Sie dies nicht wie im folgenden Beispiel

string2=teststring

weist awk der Variablen »string2« nicht die Zeichenkette »teststring« zu, sondern die Variable »teststring« – was allerdings hier keinen Fehler auslöst, da auch hier, wie schon in der Shell, nicht definierte Werte automatisch mit 0 bzw. "" vorbelegt sind. Ein einfaches Beispiel, in dem die Anzahl der Zeilen einer Datei hochgezählt wird, die Sie als Argumente in der Kommandozeile mit angeben:

#!/usr/bin/awk -f
#
# Programmname: countline.awk
BEGIN {
   count=0
}
# Haupt-Aktionsteil
{
  count++
}
END {
   printf "Anzahl Zeilen : %d\n" count
}

Das Script bei der Ausführung:

you@host > chmod u+x countline.awk
you@host > ./countline.awk countline.awk
Anzahl Zeilen : 15

Zwar lässt sich dieses Beispiel einfacher mit der Variablen NR ausführen, aber hier geht es um die Demonstration von Variablen. Zuerst wird der BEGIN-Block ausgeführt, in dem die Variable »count« mit dem Wert 0 definiert wurde. Variablen, die Sie mit dem Wert 0 vorbelegen, könnten Sie sich sparen, da diese bei der ersten Verwendung automatisch mit 0 (bzw. ""; abhängig vom Kontext) definiert würden. Allerdings ist es hilfreich, eine solche Variable trotzdem im BEGIN-Block zu definieren – der Übersichtlichkeit zuliebe.

Im Haupt-Aktionsteil wird nur der Wert der Variablen »count« um 1 inkrementiert (erhöht). Hierzu wird der Inkrement-Operator (++) in der Postfix-Schreibweise verwendet. Wurden alle Zeilen der als erstes Argument angegebenen Datei durchlaufen (wo jeweils pro Zeile der Haupt-Aktionsteil aktiv war), wird der END-Block ausgeführt und gibt die Anzahl der Zeilen aus. Hier kann übrigens durchaus mehr als nur eine Datei in der Kommandozeile angegeben werden:

you@host > ./countline.awk countline.awk mrolympia.dat
Anzahl Zeilen : 25

Das awk-Script können Sie übrigens auch wie ein wc –l benutzen. Beispiel:

you@host > ls -l | wc -l
26
you@host > ls -l | ./counterline.awk
Anzahl Zeilen : 26

Aber wie bereits erwähnt, Sie können dieses Script mit der Variablen NR erheblich abkürzen, und zwar bis auf den END-Block:

#!/usr/bin/awk -f
#
# Programmname: countline2.awk
END {
   printf "Anzahl Zeilen : %d\n", NR
}

Kommandozeilen-Argumente

Für die Argumente aus der Kommandozeile stehen Ihnen ähnlich wie in C die beiden Variablen ARGC und ARGV zur Verfügung. ARGC (ARGument Counter) enthält immer die Anzahl der Argumente in der Kommandozeile (inklusive) dem Programmnamen awk (!) und ARGV (ARGumenten Vector) ist ein Array, worin sich die einzelnen Argumente der Kommandozeile befinden (allerdings ohne Optionen von awk). Hierzu nochmals ein Script, welches die Anzahl der Argumente und jedes einzelne davon ausgibt (im Beispiel wurde die for-Schleife vorgezogen, die gleich näher beschrieben wird).

#!/usr/bin/awk -f
#
# Programmname: countarg.awk
BEGIN {
   print "Anzahl Argumente in ARGC : " , ARGC
   # einzelne Argumente durchlaufen
   for(i=0; i < ARGC; i++)
      printf "ARGV[%d] = %s\n", i, ARGV[i]
}

Das Script bei der Ausführung:

you@host > ./countarg.awk eine Zeile mit vielen Argumenten
Anzahl Argumente in ARGC :  6
ARGV[0] = awk
ARGV[1] = eine
ARGV[2] = Zeile
ARGV[3] = mit
ARGV[4] = vielen
ARGV[5] = Argumenten

Vordefinierte Variablen

In awk existiert eine interessante Auswahl von vordefinierten Variablen, wovon Sie ja bereits mit NR, NF, ARGC, ARGV, FS, $0, $1 ... schon einige kennen gelernt haben. In der folgenden Tabelle finden Sie einen kurzen Überblick zu den vordefinierte Variablen sowie ein kurze Beschreibung ihrer Funktionen.


Tabelle 13.5   Vordefinierte Variablen in awk

Variable Bedeutung
ARGC Anzahl der Argumente aus der Kommanodzeile (+1)
ARGV Array mit dem Inhalt der Kommandozeilen-Argumente
ENVIRON Enthält ein Array mit allen momentanen Umgebungsvariablen
FILENAME Name der aktuellen Eingabedatei. Bei einer Eingabe von der Tastatur steht hier der Wert '–'.
FNR Zeilennummer der Eingabe aus der aktuellen Datei
FS Das oder die Trennzeichen für die Zerlegung der Eingabezeile. Standardmäßig befindet sich hier das Leerzeichen.
NF Anzahl der Felder der aktuellen Zeile. Felder werden anhand von FS getrennt.
NR Anzahl der bisher eingelesenen Zeilen
OFMT Ausgabeformat für Fließkommazahlen
OFS Ausgabetrennzeichen für einzelne Felder; auch hier standardmäßig das Leerzeichen
ORS Das Ausgabetrennzeichen für neue Datensätze (Zeilen). Standardmäßig wird hierbei das Newline-Zeichen verwendet.
RLENGTH Gibt im Zusammenhang mit der Funktion match die Länge der übereinstimmenden Teilzeichenkette an
RS Das Eingabetrennzeichen für neue Datensätze (Zeilen), standardmäßig das Newline-Zeichen
RSTART Gibt im Zusammenhang mit der Zeichenkettenfunktion match den Index des Beginns der übereinstimmenden Teilzeichenkette an
SUBSEP Das Zeichen, das in Arrays die einzelnen Elemente trennt (\034)
$0 Enthält immer den kompletten Datensatz (Eingabezeile)
$1, $2, ... Die einzelnen Felder (Worte) der Eingabezeile, die nach dem Trennzeichen in der Variablen FS getrennt wurden

Hierzu ein einfaches Beispiel, das einige der vordefinierten Variablen demonstrieren soll:

#!/usr/bin/awk -f
#
# Programmname: vars.awk
BEGIN {
   count=0
   # Ausgabetrennzeichen: Minuszeichen
   OFS="-"
}
/USA/ {
 print $1, $2, $3
 count++
}
END {
   printf "%d Ergebnisse (von %d Zeilen) gefunden in %s\n",
      count, NR, FILENAME
   printf "Datei %s befindet sich in %s\n",
      FILENAME, ENVIRON["PWD"]
}

Das Script bei der Ausführung:

you@host > ./vars.awk mrolympia.dat
Larry-Scott-USA
Sergio-Oliva-USA
Chris-Dickerson-USA
Lee-Haney-USA
Ronnie-Coleman-USA
5 Ergebnisse (von 9 Zeilen) gefunden in mrolympia.dat
Datei mrolympia.dat befindet sich in /home/you

Rheinwerk Computing

13.5.2 Arrays  downtop

Arrays stehen Ihnen in awk gleich in zweierlei Formen zur Verfügung, zum einen die »gewöhnlichen« Arrays und zum anderen assoziative Arrays. Die »normalen« Arrays werden genauso verwendet, wie Sie dies schon in Abschnitt 2.5 bei der Shell-Programmierung gesehen haben. Verwenden können Sie die Arrays wie gewöhnliche Variablen, nur benötigen Sie hier den Indexwert (eine Ganzzahl), um auf die einzelnen Elemente zuzugreifen. Ein einfaches Script zur Demonstration:

#!/usr/bin/awk -f
#
# Programmname: playarray.awk
{
   # komplette Zeile in das Array mit dem Index NR ablegen
   line[NR]=$0
   # Anzahl der Felder (Wörter) in das Array
   # fields mit dem Index NR ablegen
   fields[NR]=NF
}
END {
   for(i=1; i <=NR; i++) {
      printf "Zeile %2d hat %2d Felder:\n", i, fields[i]
      print line[i]
   }
}

Das Script bei der Ausführung:

you@host > ./playarray.awk mrolympia.dat
Zeile  1 hat  5 Felder:
Larry Scott USA 1965 1966
Zeile  2 hat  6 Felder:
Sergio Oliva USA 1967 1968 1969
...
Zeile  9 hat 10 Felder:
Ronnie Coleman USA 1998 1999 2000 2001 2002 2003 2004

Hier wurden im Haupt-Aktionsteil die einzelnen Zeilen im Array »line« gespeichert. Als Indexnummer wird hier die vordefinierte Variable NR verwendet. Ebenfalls wurde die Anzahl von Feldern (Wörtern) dieser Zeile in einem extra Array (»fields«) gespeichert.

Neben den »gewöhnlichen« Arrays kennt awk auch noch assoziative Arrays. Dies sind Arrays, die neben Zahlen als Indexwert auch Zeichenketten zulassen. Somit sind folgende Werteübergaben möglich:

array["Wolf"]=10
array["ION"]=1234
array["WION"] = array["Wolf"]
array["GESAMT"] = array["Wolf"] + array["ION"]

Diese Variante der Arrays zu verwenden, ist wirklich außergewöhnlich. Allerdings stellen Sie sich sicherlich zunächst die Frage, wie kommt man wieder an die einzelnen Werte, also welchen Index verwendet man hierzu? Zum einen haben Sie natürlich die Möglichkeit, auf die einzelnen Werte zuzugreifen, wenn Sie das ["Wort"] des Indexwertes kennen. Aber wenn Sie nicht vorhersehen können, welche Worte hier stehen, hilft Ihnen eine spezielle, zum Arbeiten mit assoziativen Arrays gedachte Variante der for-Schleife:

for (indexwort in array)

Mit diesem Konstrukt wird das komplette assoziative Array durchlaufen. Und was eignet sich besser zur Demonstration von assoziativen Arrays als das Zählen von Wörtern einer Datei bzw. Eingabe?

#!/usr/bin/awk -f
#
# Programmname: countwords.awk
{
   # Durchläuft alle Felder einer Zeile
   for(f=1; f <= NF; ++f)
     field[$f]++
}
END {
   for(word in field)
      print word, field[word]
}

Das Script bei der Ausführung:

you@host > ./countwords.awk mrolympia.dat
Dorian 1
2000 1
2001 1
USA 5
...

Im Beispiel durchlaufen wir mit einer for-Schleife die komplette Zeile anhand der einzelnen Felder (NF). Jedes Wort wird hier als Wortindex für ein Array benutzt und inkrementiert (um eins erhöht). Existiert ein solcher Index noch nicht, wird dieser neu erzeugt und um den Wert eins erhöht. Existiert bereits ein entsprechender »Wortindex«, so wird nur die Speicherstelle zum zugehörigen Wort um eins erhöht. Im END-Block wird dann das spezielle for-Konstrukt der assoziativen Arrays für die Ausgabe verwendet. Dabei wird zuerst der Indexname (also das Wort selber) und dann dessen Häufigkeit ausgegeben.

Die Zuweisung von Werten eines Arrays ist etwas umständlich, da die Werte häufig einzeln übergeben werden müssen. Einfacher gelingt dies mit der Funktion split. split zerlegt ein Array automatisch in die einzelnen Bestandteile (anhand der Variablen FS). Ein Beispiel:

#!/usr/bin/awk -f
#
# Programmname: splitting.awk
/Coleman/ {
   # Durchläuft alle Felder einer Zeile
   split($0, field)
}
END {
   for(word in field)
      print field[word]
}

Das Script bei der Ausführung:

you@host > ./splitting.awk mrolympia.dat
1998
1999
2000
2001
2002
2003
2004
Ronnie
Coleman
USA

Hier wird die Zeile mit der Textfolge »Coleman« in die einzelnen Bestandteile zerlegt und an das assoziative Array »field« übergeben. Sofern Sie ein anderes Zeichen zur Trennung verwenden wollen, müssen Sie die vordefinierte Variable FS entsprechend anpassen.


Rheinwerk Computing

13.5.3 Operatoren  downtop

Im Großen und Ganzen werden Sie in diesem Kapitel nicht viel Neues erfahren, da Operatoren meistens in allen Programmiersprachen dieselbe Bedeutung haben. Trotzdem stellt sich hierbei zunächst die Frage, wie awk einen Wert einer Variablen oder Konstante typisiert. In der Regel versucht awk, bei einer Berechnung beispielsweise die Typen aneinander anzupassen. Somit hängt der Typ eines Ausdrucks vom Kontext der Benutzung ab. So kann ein String "100" einmal als String verwendet werden und ein anderes Mal als die Zahl 100. Zum Beispiel stellt

print "100"

eine Ausgabe einer Zeichenkette dar. Hingegen wird

print "100"+0

als Zahl präsentiert – auch wenn jeweils immer »nur« 100 ausgegeben wird. Man spricht hierbei vom Erzwingen eines numerischen bzw. String-Kontexts.

Einen numerischen Kontext können Sie erzwingen, wenn der Typ des Ergebnisses eine Zahl ist. Befindet sich ein String in der Berechnung, wird dieser in eine Zahl umgewandelt. Ein Beispiel:

print "100Wert"+10

Hier ist das Ergebnis der Ausgabe 110 – das Suffix »Wert« wird ignoriert. Würde sich hier ein ungültiger Wert befinden, so wird dieser in 0 umgewandelt:

print "Wert"+10

Als Ergebnis würden Sie den Wert 10 zurückbekommen.

Einen String-Kontext erzwingen Sie, wenn der Typ des Ergebnisses ein String ist. Somit könnten Sie z. B. eine Zahl in einen String umwandeln, wenn Sie etwa eine »Konkatenation« (Anhängen mehrere Zeichenketten) vornehmen oder Vergleichsoperatoren einen String erwarten.

Leider ist es nicht immer so eindeutig, ob ein String als fester String oder als eine Zahl repräsentiert wird. So würde beispielsweise folgender Vergleich fehlschlagen:

if( 100=="00100" )
      ...

Und zwar deshalb, weil hier die Regel in Kraft tritt: Sobald eines der Argumente ein String ist, wird auch das andere Argument als String behandelt. Daher würden Sie hier folgenden Vergleich durchführen:

if ( "100"=="00100" )
...

Anders hingegen sieht dies aus, wenn Sie z. B. eine Feldvariable (bspw. $1) mit dem Wert »00100« vergleichen würden:

if (100==$1)
...

Hier würde der Vergleich »wahr« zurückgeben. Wie dem auch sei, Sie sehen, dass es hier zu einigen Ungereimtheiten kommen kann. Zwar könnte ich Ihnen jetzt eine Liste anführen, in welchem Fall ein String als numerischer und wann als nicht numerischer String behandelt wird. Allerdings ist es mühsam, sich dies zu merken, weshalb man – sofern man Strings mit Zahlen vermischt – hier auf Nummer sicher gehen und die Umwandlung in eine Zahl bzw. in einen String selbst vornehmen sollte. Eine erzwungene Umwandlung eines Strings können Sie wie folgt durchführen:

variable = variable ""

Jetzt können Sie sicher sein, dass »variable« ein String ist. Wollen Sie hingegen, dass »variable« eine Zahl ist, so können Sie diese Umwandlung wie folgt erzwingen:

variable = variable + 0

Hiermit haben Sie in »variable« immer eine Zahl. Befindet sich in Variable ein sinnloser Wert, so wird durch die erzwungene Umwandlung eben der Wert 0 aus der Variablen. Zugegeben, viel Theorie, aber sie ist erforderlich, um auch zu den gewünschten Ergebnissen zu kommen.

Arithmetische Operatoren

Hier finden Sie ebenfalls wieder die üblichen arithmetischen Operatoren, wie in anderen Programmiersprachen auch. Ebenso existieren hier die kombinierten Zuweisungen, mit denen Sie Berechnungen wie »var=var+5« durch »var+=5« abkürzen können. Hier eine Tabelle mit allen arithmetischen Operatoren in awk:


Tabelle 13.6   Arithmetische Operatoren

Operator Bedeutung
+ Addition
Subtraktion
* Multiplikation
/ Division
% Modulo (Rest einer Division)
^ Exponentation bspw. x^y ist x hoch y
+= Abkürzung für var=var+x gleichwertig zu var+=x
–= Abkürzung für var=var–x gleichwertig zu var–=x
*= Abkürzung für var=var*x gleichwertig zu var*=x
/= Abkürzung für var=var/x gleichwertig zu var/=x
%= Abkürzung für var=var%x gleichwertig zu var%=x
^= Abkürzung für var=var^x gleichwertig zu var^=x

Ein einfaches Beispiel:

#!/usr/bin/awk -f
#
# Programmname: countfields.awk
{
   field[FILENAME]+=NF
}
END {
   for(word in field)
      print "Anzahl Felder in " word " : " field[word]
}

Das Script bei der Ausführung:

you@ghost > ./countfields.awk mrolympia.dat USA.dat Argentinien
Anzahl Felder in Argentinien : 5
Anzahl Felder in USA.dat : 15
Anzahl Felder in mrolympia.dat : 64

In diesem Beispiel werden alle Felder einer Datei gezählt. Da wir nicht wissen können, wie viele Dateien der Anwender in der Kommandozeile eingibt, verwenden wir gleich ein assoziatives Array mit dem entsprechenden Dateinamen als Feldindex.

Logische Operatoren

Die logischen Operatoren wurden bereits verwendet. Hier haben Sie die Möglichkeit einer logischen UND- und einer logischen ODER-Verknüpfung. Das Prinzip der beiden Operatoren wurde schon bei der Shell-Programmierung erklärt. Auch hier gilt: Verknüpfen Sie zwei Ausdrücke mit einem logischen UND, wird wahr zurückgegeben, wenn beide Ausdrücke zutreffen. Bei einer logischen ODER-Verknüpfung hingegen genügt es, wenn einer der Ausdrücke wahr zurückgibt. Und natürlich steht Ihnen in awk auch der Negationsoperator (!) zur Verfügung, womit Sie alles Wahre unwahr und alles Unwahre wahr machen können.


Tabelle 13.7   Logische Operatoren in awk

Operator Bedeutung
|| Logisches ODER
&& Logisches UND
! Logische Verneinung

Das folgende Script gibt alle Gewinner der 80er-Jahre der Datei mrolympia.dat zurück:

#!/usr/bin/awk -f
#
# Programmname: winner80er.awk
{
   for(i=4; i<=NF; i++) {
      if( $i >= 1980 && $i < 1990 ) {
         print $0
         # gefunden -> Abbruchbedingung für for
         i=NF;
      }
   }
}

Das Script bei der Ausführung:

you@host > ./winner80er.awk mrolympia.dat
Arnold Schwarzenegger Österreich 1970 1971 1972 1973 1974 1980
Franco Columbu Argentinien 1976 1981
Chris Dickerson USA 1982
Samir Bannout Libanon 1983
Lee Haney USA 1984 1985 1986 1987 1988 1989 1990 1991

Bedingungsoperator

Auch awk unterstützt den ternären Bedingungsoperator ?: von C. Dieser Operator stellt eine verkürzte if-Anweisung dar. So können Sie statt

if ( Bedingung )
   anweisung1
else
   anweisung2

den ternären Operator ?: wie folgt einsetzen:

Bedingung ?anweisung1 :anweisung2

Auch hier gilt wie in der if-Anweisung: Ist die Bedingung wahr, wird »anweisung1« ausgeführt, ansonsten wird »anweisung2« verwendet. Zwar mag dieser ternäre Operator eine Verkürzung des Programmcodes bedeuten, doch aus Übersichtlichkeitsgründen verwende ich persönlich immer noch lieber das if-else-Konstrukt (auch in C) – aber dies ist letztendlich Geschmackssache.

Inkrement- und Dekrementoperator

Den Inkrementoperator ++ haben Sie bereits verwendet. Dabei handelt es sich um einen Zähloperator, der den Inhalt einer Variablen um 1 erhöht – also eine Abkürzung für »var=var+1« ist »var++«. Gleiches erreichen Sie mit dem Dekrementoperator ––. Damit reduzieren Sie den Wert einer Variablen um 1. Allerdings ist es entscheidend, ob Sie den Operator vor oder nach der Variablen schreiben. In Verbindung mit einer Variablen kann dabei folgender (Neben-)Effekt auftreten:

var=1
wert=var++
print wert i # wert=1, i=2

Hier wurde die Postfixschreibweise mit »var++« verwendet. In diesem Beispiel wird zuerst der Variablen »wert« der Inhalt von »var« zugewiesen und anschließend inkrementiert. Somit bekommt »wert« den Inhalt 1. Wollen Sie, dass »wert« hier 2 erhält, müssen Sie »var« in der Präfixschreibweise erhöhen.

var=1
wert=++var
print wert i # wert=2, i=2

Jetzt wird der Inhalt von »var« zuerst inkrementiert und dann an »wert« übergeben. Gleiches gilt natürlich auch bei dem Dekrementoperator. Relativ häufig werden diese Operatoren in Zählschleifen verwendet.

Leerzeichen- und Feldoperator

Zwar würde man das Leerzeichen nicht als Operator bezeichnen, aber zwischen zwei Strings dient es dazu, diese miteinander zu verbinden – sprich zu einer neuen Zeichenkette zu verbinden. Folgendes Script als Beispiel:

#!/usr/bin/awk -f
#
# Programmname: names.awk
{
   string = string $2 " "
}
END {
   print string
}

Das Script bei der Ausführung:

you@host > ./names.awk mrolympia.dat
Scott Oliva Schwarzenegger Columbu Dickerson Bannout Haney Yates

In diesem Script wurden alle Nachnamen (zweite Spalte – $2) zu einem String verbunden, ohne das Leerzeichen wäre dies nicht möglich.

Des Weiteren steht Ihnen noch der Feldzugriffsoperator $ zur Verfügung. Sie können ihn, im Gegensatz zum Positionsparameter der Shell, relativ flexibel einsetzen:

#!/usr/bin/awk -f
#
# Programmname: names2.awk
BEGIN {
   i=2
}
{
   string = string $i " "
}
END {
   print string
}

Das Script macht dasselbe wie schon das Script zuvor, es verwendet alle Felder der zweiten Spalte und fügt Sie zu einem String zusammen. Allerdings wurde hier der Feldzugriffsoperator mit $i verwendet. »i« wurde im BEGIN-Block mit dem Wert 2 belegt. Dies macht den Zugriff auf die einzelnen Felder erheblich flexibler, besonders in Verbindung mit einer Schleife:

#!/usr/bin/awk -f
#
# Programmname: winner.awk
{
   for(i=4; i<=NF; i++)
      string[$2]= string[$2] $i "-"
}
END {
   for(word in string)
      print "Titel von " word " in den Jahren : " string[word]
}

Das Script bei der Ausführung:

you@host > ./winner.awk mrolympia.dat
Titel von Bannout in den Jahren : 1983-
Titel von Columbu in den Jahren : 1976–1981-
...
Titel von Yates in den Jahren : 1992–1993–1994–1995–1996–1997-
Titel von Dickerson in den Jahren : 1982-

Hier wurden alle Jahre, in denen ein Wettkämpfer gesiegt hat, in ein assoziatives Array gespeichert. Die einzelnen Jahre wurden mit dem Zugriffsoperator auf Felder ($) dem Array zugewiesen.


Rheinwerk Computing

13.5.4 Kontrollstrukturen  toptop

Mit den Kontrollstrukturen lernen Sie eigentlich nichts mehr Neues, da Sie hier auf alle alten Bekannten wie if, for, while und Co. stoßen werden, die Sie bereits in Kapitel 4, Kontrollstrukturen, zur Shell-Programmierung kennen gelernt haben. Leider kann ich das Thema nicht ganz so schnell überfliegen, weil hier die Syntax im Gegensatz zur Shell-Programmierung ein wenig anders ist. Derjenige, der bereits C-erprobt ist (oder sich mit den C-Shells befasst hat), ist fein raus, weil die Syntax der Kontrollstrukturen in awk exakt derjenigen von C entspricht.

if-Verzweigungen

Mit der if-Verzweigung können Sie (wie gewohnt) eine bestimmte Bedingung testen. Trifft die Bedingung zu, wird wahr (TRUE), ansonsten falsch (FALSE) zurückgegeben. Optional können Sie auch hier einer if-Verzweigung ein else-Konstrukt folgen lassen. Die Syntax:

if( Bedingung )
   # Bedingung-ist-wahr-Zweig
else
   # Bedingung-ist-falsch-Zweig (optional)

Besteht die Anweisung einer Verzweigung aus mehr als nur einer Anweisung, müssen Sie diese in einem Anweisungsblock zwischen geschweiften Klammern zusammenfassen:

if( Bedingung ) {
   # Bedingung-ist-wahr-Zweig
   anweisung1
   anweisung2
}
else {
   # Bedingung-ist-falsch-Zweig (optional)
   anweisung1
   anweisung2
}

Und selbstverständlich gibt es in awk auch das else if-Konstrukt (gleich mehr zu elif in der Shell-Programmierung):

if( Bedingung1 ) {
   # Bedingung-ist-wahr-Zweig
   anweisung1
   anweisung2
}
else if( Bedingung2 ) {
   # Bedingung2-ist-wahr-Zweig (optional)
   anweisung1
   anweisung2
}
else {
   # Bedingung1-und-Bedingung2-ist-falsch-Zweig (optional)
   anweisung1
   anweisung2
}

Ein einfaches Bespiel:

#!/usr/bin/awk -f
#
# Programmname: findusa.awk
{
   if( $0 ~ /USA/ )
      print "USA gefunden in Zeile " NR
   else
      print "USA nicht gefunden in Zeile "   NR
}

Das Script bei der Ausführung:

you@host > ./findusa.awk mrolympia.dat
USA gefunden in Zeile 1
USA gefunden in Zeile 2
USA nicht gefunden in Zeile 3
...

for-Schleifen

for-Schleifen wurden auf den vorangegangenen Seiten recht häufig eingesetzt. Die Syntax zur klassischen for-Schleife sieht wie folgt aus:

for( Initialisierung; Bedingung; Zähler )
   anweisung

Folgen auch hierbei mehrere Anweisungen, müssen Sie diese in einem Anweisungsblock zwischen geschweiften Klammern zusammenfassen:

for( Initialisierung; Bedingung; Zähler ) {
   anweisung1
   anweisung2
}

Der erste Ausdruck (Initialisierung) der for-Schleife wird nur ein einziges Mal beim Starten der for-Schleife ausgeführt. Hier wird häufig eine Variable mit Startwerten initialisiert. Anschließend folgt ein Semikolon, gefolgt von einer Überprüfung der Bedingung. Ist diese wahr, so werden die Anweisungen ausgeführt. Ist die Bedingung nicht wahr, wird die Schleife beendet und das Script fährt mit seiner Ausführung hinter der Schleife fort. Sofern die Bedingung wahr ist und die Anweisungen ausgeführt wurden, wird der dritte Ausdruck in der for-Schleife – hier der Zähler – aktiv. Hierbei wird häufig ein bestimmter Wert, der entscheidend für die Abbruchbedingung des zweiten Ausdrucks der for-Schleife ist, erhöht bzw. erniedrigt. Anschließend wird wieder die Bedingung überprüft usw.

Auch die zweite Form der for-Schleife haben Sie schon des Öfteren verwendet. Sie wird in Verbindung mit assoziativen Arrays eingesetzt (siehe Abschnitt 13.5.2). Die Syntax:

for(indexname in array)
   anweisung

oder bei mehreren Anweisungen:

for(indexname in array) {
   anweisung1
   anweisung2
}

Mit dieser for-Schleife wird das assoziative Array Element für Element durchlaufen. Das Indexwort steht Ihnen hierbei in »indexname« zur Verfügung, sodass es im Schleifenkörper weiterverwendet werden kann.

while- und do-while-Schleifen

Die while-Schleife kennen Sie ebenfalls aus der Shell-Programmierung. Sie führt die Anweisungen in einem Befehlsblock so lange aus, wie die Bedingung zwischen den runden Klammern erfüllt wird. Die Syntax:

while( Bedingung )
  anweisung

Oder, wenn mehrere Anweisungen folgen:

while( Bedingung ) {
   anweisung1
   anweisung2
}

Ein einfaches Anwendungsbeispiel zur while-Schleife:

#!/usr/bin/awk -f
#
# Programmname: countfl.awk
{
   while( i <=NF  ) {
      fields++
      i++
   }
   i=0
   lines++
}
END {
   print "Anzahl Felder in " FILENAME ": " fields
   print "Anzahl Zeilen in " FILENAME ": " lines
}

Das Script bei der Ausführung:

you@host > ./countfl.awk mrolympia.dat
Anzahl Felder in mrolympia.dat: 73
Anzahl Zeilen in mrolympia.dat: 9

Das Script macht nichts anderes, als die Anzahl der Felder und Zeilen zu zählen. Die while-Schleife durchläuft hierbei jedes einzelne Feld einer Zeile. In der while-Schleife wird die Variable »field« und »i« (welche als Abbruchbedingung für die while-Schleife dient) inkrementiert. Am Ende einer Zeile trifft die Abbruchbedingung zu und die while-Schleife ist fertig. Daher wird hinter der while-Schleife »i« wieder auf 0 gesetzt und der Zähler für die Zeilen inkrementiert. Jetzt ist die while-Schleife für die neue Zeile bereit, da »i« wieder 0 ist.

In awk steht Ihnen auch eine zweite Form der while-Schleife mit do-while zur Verfügung. Der Unterschied zu herkömmlichen while-Schleife besteht darin, dass die do-while-Schleife die Bedingung erst nach der Ausführung alle Anweisungen überprüft. Die Syntax:

do {
   anweisung1
   anweisung2
} while( Bedingung)

Umgeschrieben auf das Beispiel der while-Schleife, würde dieselbe Programmausführung wie folgt aussehen:

#!/usr/bin/awk -f
#
# Programmname: countfl2.awk
{
   do {
      fields++
      i++
   } while (i<=NF)
   i=0
   lines++
}
END {
   print "Anzahl Felder in " FILENAME ": " fields
   print "Anzahl Zeilen in " FILENAME ": " lines
}

Sprungbefehle – break, continue, exit, next

Zur Steuerung von Schleifen können Sie auch bei awk auf die Befehle break und continue (siehe Abschnitte 4.13.1 und 4.13.2) wie in der Shell-Programmierung zurückgreifen. Mit break können Sie somit aus einer Schleife herausspringen und mit continue den aktuellen Schleifendurchgang abbrechen und mit dem nächsten fortsetzen.

exit ist zwar nicht direkt ein Sprungbefehl im Script, aber man kann es hier hinzufügen. Mit exit beenden Sie das komplette awk-Script. Gewöhnlich wird exit in awk-Scripts verwendet, wenn die Weiterarbeit keinen Sinn mehr ergibt oder ein unerwarteter Fehler aufgetreten ist. exit kann hier mit oder ohne Rückgabewert verwendet werden. Der Rückgabewert kann dann in der Shell zur Behandlung des aufgetretenen Fehlers bearbeitet werden.

while ( Bedingung ) {
   ...
   if( Bedingung )
      break    # while-Schleife beenden
   if( Bedingung )
      continue # hochspringen zum nächsten Schleifendurchlauf
   if( Bedingung )
      exit 1   # Schwerer Fehler – Script beenden
   ...
}

Ein für Sie wahrscheinlich etwas unbekannterer Befehl ist next. Mit ihm können Sie die Verarbeitung der aktuellen Zeile unterbrechen und mit der nächsten Zeile fortfahren. Dies kann zum Beispiel sinnvoll sein, wenn Sie eine Zeile bearbeiten, die zu wenig Felder enthält:

...
# weniger als 10 Felder -> überspringen
if( NF < 10 ) next


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 << zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Shell-Programmierung
Shell-Programmierung
bestellen
 Buchtipps
Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-Programmierung


Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern