[ awk-Tutorium ]

 

Kapitel 4

Komplexere Programme

 

4.1    Wenn-dann Konstrukte

Bedingungen können nicht nur außerhalb des Anweisungsblocks mit geschweiften Klammern stehen, sie können auch innerhalb abgefragt werden. Dazu gibt es die if-Anweisung:

    if ([Bedingung]) [einzelne Anweisung]
    if ([Bedingung]) {[mehrere Anweisungen]}

Wenn mehrere Anweisungen von der Bedingung abhängig ausgeführt werden sollen, so müssen sie zu einem Block in geschweiften Klammern zusammengefasst werden.

Also sind die beiden Zeilen

    $1=="x" { print }
    { if ($1=="x") print }

gleichwertig. Für einfache Abfragen sind Abfragen außerhalb des {}-Blocks wohl übersichtlicher, if-Anweisungen dienen eher zur weiteren Abfrage innerhalb des Blocks.

Einen großen Vorteil bietet die if-Anweisung jedoch: Mit dem Zusatz else können Ausführungen angegeben werden, die nur dann ausgeführt werden, wenn die Bedingung nicht erfüllt ist:

    if ([Bedingung]) [Anweisung A] else [Anweisung B]

   Auch hier können Anweisungen zu Blöcken zusammengefasst werden. Wird nur eine einzelne Anweisung zu [A] angegeben, so muß diese mit einem Semikolon abgeschlossen werden, auch wenn dies auf den ersten Blick den »Satzfluss« stört.

Also, wieder ein Beispiel:

    { if (/^\$/) print > "comment.dat"; else print; }

Hier werden alle Zeilen, die mit einem »$« beginnen, in eine separate Datei comment.dat geschrieben, alle anderen auf den normalen Output.

   Mit > können wie in der Shell Ausgaben in Dateien umgeleitet werden. Achtung: Dateinamen sind in awk immer Strings und müssen daher in Anführungsstrichen stehen.

 

4.2    Schleifen

Ein weiteres wichtiges Instrument sind Schleifen. Eine typische Schleife ist die while-Schleife:

    while ([Bedingung]) [Anweisung]

Hier wird die Anweisung solange ausgeführt, bis die Bedingung falsch wird. ist die Bedingung von Anfang an falsch, passiert nichts:

    n = 1; while (n < 256) {print n; n*=2}

Natürlich muss in der Anweisung etwas passieren, das die Bedingung irgendwann einmal falsch werden lässt. Etwas wie

    n = 0; while (n < 5) {print n}

führt zu einer so genannten Endlosschleife, die ununterbrochen den Wert 0 ausgibt, der natürlich immer kleiner als fünf ist.

Eine Abwandlung der while-Schleife ist die do-while-Schleife:

    do [Anweisung] while ([Bedingung])

Auch hier werden die Anweisungen solange durchlaufen, bis die Bedingung falsch wird. Allerdings wird die Anweisung immer mindestens einmal ausgeführt, was bei der reinen while-Schleife nicht sein muss.

Schleifen werden oft zum Zählen verwendet. Hierfür gibt es eine besondere Schleife, for:

    for ([Init]; [Bedingung]; [Update]) [Anweisung]

Dies ist eine Abkürzung für

    [Init];
    while (
[Bedingung]) {
        [Anweisung]
        [Update]
    }

Eine Schleife, die die Variable i von 0 bis 99 zählt, und sie ausgibt, wäre

    for (i=0; i<100; i++) print i;

Jeder der drei Ausdrücke in der Klammer kann weggelassen werden. Fehlt z.B. [Init], so wird der momentane Wert der Variable benutzt.

   Mit break kann aus jeder Schleife - auch aus einer Endlosschleife - herausgesprungen werden in die nächsthöhere. Mit continue wird der Durchlauf übersprungen, es geht bei [Update] weiter.

 

4.3    Funktionen

awk erlaubt die Definition eigener Funktionen, in anderen Sprachen auch Subroutinen oder Prozeduren genannt. Eine Funktion wird folgendermaßen definiert:

    function [Name] ([Argumente]) {
        [Anweisungen]
    }

Die Definition der Funktion muss außerhalb aller Blöcke in geschweiften Klammern stehen. Als Argumente können beliebig viele Variablen, durch Kommas getrennt, angegeben werden. Wenn es keine Argumente gibt, muss trotzdem ein leeres Klammerpaar angegeben werden:

    function multiply(i,j) {
        print i "*" j, "=", i*j;
    }

Der Aufruf der Funktion aus einem Anweisungsblock heraus wäre

    multiply($4,$5);

zum Beispiel. Alle Variablen, die als Argumente übergeben werden, sind lokal. Das heißt, sie sind nur innerhalb des Anweisungsblocks der Funktion bekannt, nicht jedoch außerhalb.

Es können jedoch globale Variablen, die außerhalb der Funktion definiert wurden, benutzt werden. Wenn eine Variable innerhalb einer Funktion zum ersten Mal benutzt, also implizit initialisiert wird, ist sie eine globale Variable.

   Die Ausnahme ist hier, wenn es eine lokale Variable gibt, die denselben Namen wie eine globale Variable hat. In diesem Fall ist die lokale gemeint. Alle Variablen, die außerhalb von Funktionen definiert werden, sind global.

   Achtung! Die erste Klammer muss direkt an den Funktionsnamen anschließen, sonst hat awk Schwierigkeiten bei der Interpretation des Aufrufs!

Eine nützliche Sache ist die Rückgabe von Werten aus einer Funktion. Dies geschieht mit return:

    function max(i,j) {
        if (i > j) return i;
        else return j;
    }

Diese Funktion schreibt nichts auf den Bildschirm, sondern gibt nur einen Wert zurück. Der Aufruf

    max(1,3)

bringt nichts, obwohl die Anweisungen der Funktion abgearbeitet werden. Um den Rückgabewert der Funktion nutzen zu können, muß der Aufruf lauten:

    j = max(1,3)

Dann hat j den Wert des Maximums von 1 und 3, also 3. Man kann den Wert aber auch direkt in einem Aufruf verwenden:

    print max(1,3)

schreibt eine 3. Man kann sogar soweit gehen, den Rückgabewert als Argument in einem weiteren Funktionsaufruf zu benutzen:

    print max(max(1,3),8)

schreibt eine »8«.

Man kann die Funktion auch aus sich selbst heraus aufrufen, dies nennt man Rekursion:

    function factorial(n) {
        if (n==1) return 1;
        else return n*factorial(n-1);
    }

ist eine rekursive Funktion zum Berechnen von Fakultäten. Aber Vorsicht: Da die lokalen Variablen für jeden Aufruf extra angelegt werden müssen, kann es bei langen Rekursionen zum Überlauf des Stapelspeichers kommen.

 

4.4    Weitere Funktionen

Neben den bereits bekannten Stringoperationen gibt es folgende Funktionen:

exit [Abbruchstatus]
bricht das Skript sofort ab und gibt den Abbruchstatus an das System zurück. exit ohne Status gibt Null zurück, was soviel heißt wie »alles in Ordung«.

next
überspringt alle weiteren Bedingungs-Anweisungsblöcke für die momentane Zeile und geht zur nächsten Zeile über. Das ist natürlich nur sinnvoll, wenn es mehrere Blöcke gibt oder wenn man alle anderen Anweisungen des jetzigen Blocks ignorieren möchte.

close([filename])
Schließt die Datei mit dem Namen [filename] und beendet das Lesen von ihr. Mit close kann man auch Dateien schließen, die man durch Dateiumleitung mit > geöffnet hat. Die ist manchmal notwendig, wenn man zu viele dateien gleichzeitig geöffnet hat und das Limit erreicht ist.

getline [string] < [filename] Liest die nächste Zeile von [filename] ein und schreibt sie auf die String-Variable [string]. Wenn die Angabe von [string] fehlt, wird auf $0 geschrieben, fehlt [filename], so wird von der momentan offenen Datei gelesen. getline gibt auch einen Wert zurück: 1 für erfolgreiches Einlesen der Zeile, 0 für Dateiende oder -1, wenn ein Fehler aufgetreten ist.

system ([cmd])
Führt das Systemkommando aus, das durch den String [cmd] beschrieben wird. Ist [cmd] z.B. "ls -lrt", so wird der Inhalt des momentanen Verzeichnisses in langer Form, abwärts nach Datum sortiert, ausgegeben. Diese Anweisung ist natürlich dann besonders nützlich, wenn man anstelle eines fixen Strings eine Stringvariable einsetzt. Die Syntax des Kommandos richtet sich natürlcih nach dem verwendeten Betriebssystem.

 

4.5    Felder

Felder sind Werte, die unter einem Namen zusammengefasst sind und mit einem Index versehen werden. Üblicherweise haben Felder ganzzahlige, aufeianderfolgende Indizes. Daten in einem Feld werden mit dem gemeinsamen Feldnamen und ihrem Index in eckigen Klammern angesprochen:

    [Feld] [[Index]]

Ein Feldeintrag ist wie eine Variable: Er kann mit anderen Werten verglichen werden und einen Wert zugewiesen bekommen. Felddaten sind zu Anfang Null oder der Nullstring. (Genau wie Variablen können Felddaten Zahlen oder Strings sein.)

Das besondere an Feldindizes in awk ist, dass sie nicht unbedingt aufeinanderfolgen und nicht numerisch sein müssen. Das macht das Arbeiten mit Feldern in awk besonders flexibel:

    /^NODE/ {
        nodeID = substr ($0,9,8) + 0;
        x[nodeID] = substr($0,17,8) + 0;
        y[nodeID] = substr($0,25,8) + 0;
        z[nodeID] = substr($0,33,8) + 0;
    }

Für Felder gibt es eine besondere Bedingung,

    ([Index] in [Feld])

prüft, ob es in [Feld] einen Eintrag unter [Index] gibt. Diese Notation kann auch für for-Schleifen verwendet werden:

    for ([Index] in [Feld]) [Anweisung]

arbeitet die Anweisung für alle Indizes im Feld ab. Das ist oft sehr praktisch:

    /^[A-Za-z]/ { a[$1]++ }
    END { for (i in a) print a[i], "Mal", i; }

zählt zu Beispiel die Anzahl und Typen der verwendeten Keywords, wenn man davon ausgeht, dass Keywords mit Buchstaben anfangen.

Feldeinträge können mit

    delete [Feld] [[Index]]

wieder aus dem Feld gelöscht werden. In einem Feld können auch mehrere Indizes verwendet werden, die dann durch Kommas getrennt innerhalb der eckigen Klammern stehen:

    [Feld] [[Index1], [Index2], [Index3]]

Aufgaben:

Aufgabe 4a
Wie sieht ein Skript aus, das alle Wörter einer Zeile in umgekehrter Reihenfolge ausgibt? Wie eines, das die ganze Zeile verkehrtherum schreibt?

Aufgabe 4b
Wie kann man vor Bearbeiten einer Datei eine Menge von Zahlen von einer externen Datei id.dat lesen und sie auf ein Feld schreiben? Die Datei id.dat enthält eine Zahl je Zeile und die gelesene ID soll Index des Felds sein, der Eintrag 0.

Aufgabe 4c
Schreibe ein Skript, das einen Datensatz kopiert, aber zwei Zeilen herausnimmt, wenn die erste mit dem Keyword »SOLID« beginnt. (Eine Anwendung hierfür wäre PAM-Crash, bei dem die Definition eines Solid-Elements über zwei Zeilen geht.)


Nächstes Kapitel
Voriges Kapitel
nach oben
Inhalt


________
Startseite --> Dies & das --> awk-Tutorium --> Kapitel 4
Übersicht
Martin Oehm

[ www.martin-oehm.de - Startseite ]
[ Dies & das ]
[ awk-Tutorium ]

Inhalt

Vorwort

1. Kapitel
2. Kapitel
3. Kapitel
4. Kapitel
5. Kapitel

Kurzübersicht
Lösungen