awk/Lesen von Eingabedateien

Aus Mikiwiki
Zur Navigation springen Zur Suche springen

Wenn Eingabedateien an awk übergeben werden, so werden alle Dateien nacheinander gelesen. Der Name der aktuellen Eingabedatei steht in der Variable "FILENAME".

Die Eingabe wird in Einheiten gelesen, die als "Datensätze" bezeichnet werden, und wird von den Regeln des Programms Datensatz für Datensatz verarbeitet. Normalerweise besteht ein Datensatz aus einer Zeile. Jeder Datensatz wird automatisch in Stücke zerlegt, die dann "Felder" genannt werden.

Wie Eingaben in Datensätze aufgeteilt werden

awk teilt die Eingabe des awk-Programms in Datensätze und Felder auf. Dabei merkt es sich die Anzahl Datensätze, die aus der aktuellen Eingabedatei eingelesen wurden, und speichert den Wert in der Variable "FNR". Mit jeder neuen eingelesenen Datei wird der Wert auf "0" zurückgesetzt. Die Variable "NR" enthält die Anzahl aller bisher eingelesenen Datensätze aus allen Dateien. Sie beginnt mit "0", wird aber nie automatisch zurückgesetzt.

Datensätze werden durch den Datensatzseparator (normalerweise der Zeilenumbruch) voneinander getrennt. Mit der Variable "RS" kann ein anderes Zeichen als Datensatzseparator bestimmt werden. Der neue Datensatzseparator sollte dabei in doppelte Anführungszeichen eingeschlossen werden, die eine Zeichenkonstante bezeichnen.

Das folgende Beispiel ändert den Wert der Variable "RS" auf "0", bevor irgendeine Eingabe gelesen wird. Danach wird die Eingabedatei "bbslist" gelesen und die zweite Regel des Programms (eine Aktion ohne Muster) gibt jeden Datensatz aus. Weil der Befehl "print" am Ende seiner Ausgabe einen Zeilenumbruch hinzufügt, kopiert das awk-Programm die Eingabe, wobei jeder Schrägstrich durch einen Zeilenumbruch ersetzt wird.

$ awk 'BEGIN { RS = "/" }
       { print $0 }' bbslist
aardvark     555-5553     1200
300          B
alpo-net     555-3412     2400
1200
300     A
...

Auf der Befehlszeile kann der Datensatzseparator wie folgt geändert werden. Damit wird die Variable "RS" gesetzt, bevor die Datei "bbslist" verarbeitet wird.

$ awk '{ print $0 } RS = "/" bbslist

Die leere Zeichenkette "" hat als Wert der Variable "RS" eine Sonderbedeutung: damit werden alle Datensätze durch eine odere mehrere Leerzeilen und sonst nichts getrennt.

Unter gawk muss der Wert von "RS" nicht aus einem einzelnen Zeichen, sondern kann aus einem beliebigen regulären Ausdruck bestehen. Im allgemeinen endet jeder Datensatz an der nächsten Zeichenfolge, die mit dem regulären Ausdruck übereinstimmt; der nächste Datensatz beginnt am Ende der übereinstimmenden Zeichenkette.

Nachdem gawk das Ende eines Datensatzes gefunden hat, wird die Variable "RT" mit dem Text der Eingabe gefüllt, die mit der Variable "RS" übereinstimmt. Wenn "RS" also ein Einzelzeichen ist, enthält "RT" ebendieses Zeichen. Ist "RS" aber ein regulärer Ausdruck, so enthält "RT" den aktuellen Eingabetext, der mit dem regulären Ausdruck übereinstimmte.

Das folgende Beispiel setzt die Variable "RS" auf einen regulären Ausdruck, der entweder mit einem Zeilenumbruch oder einer Reihe von einem oder mehreren Grossbuchstaben mit optionalen führenden oder abschliessenden Leerzeichen übereinstimmt.

$ echo datensatz 1 AAAA datensatz 2 BBBB datensatz 3 |
> gawk 'BEGIN { RS = "\n|( * [[:upper:]]+ *)" }
> { print "Datensatz =", $0, "und RT =", RT }'
Datensatz = datensatz 1 und RT =  AAAA
Datensatz = datensatz 2 und RT =  BBBB
Datensatz = datensatz 3 und RT =

$

Der Abschluss der Ausgabe enthält eine zusätzliche Leerzeile. Der Grund dafür ist, dass der Wert der Variable "RT" ein Zeilenumbruch ist, und der Befehl "print" seinen eigenen abschliessenden Zeilenumbruch hinzufügt.

Felder

Beim Lesen eines Datensatzes wird der Datensatz vom Interpreter automatisch geparst oder in "Felder" genannte Stücke aufgeteilt. Standardmässig werden Felder durch Leerzeichen (Leerschlag, Tabulator, evtl. auch der Zeilenumbruch) getrennt. Mit dem Dollarzeichen ("$") kann auf diese Felder zugegriffen werden: "$1" bezeichnet das erste Feld, "$2" das zweite, usw. Die Variable "$0" bezeichnet den ganzen Datensatz.

Die Variable "NF" enthält jeweils die Anzahl Felder des aktuellen Datensatzes. Gleichgültig wieviele Felder ein Datensatz hat, mit "$NF" kann auf das letztes Feld des Datensatzes zugegriffen werden.

Ausgabe jedes Datensatzes der Datei "bbsfile", dessen erstes Feld die Zeichenkette "foo" enthält. Der Operator "~" ist ein sogenannter Übereinstimmungsoperator und testet, obe eine Zeichenfolge (hier das Feld "$1") mit einem gegebenen regulären Ausdruck übereinstimmt.

$ awk '$1 ~ /foo/ { print $0 }' bbslist
fooey        555-1234     2400/1200/300     B
foot         555-6699     1200/300          B
macfoo       555-6480     1200/300          A
sabafoo      555-2127     1200/300          C

Ausgabe des ersten und letzten Feldes jedes Datensatzes der Datei "bbsfile", der die Zeichenkette "foo" enthält.

$ awk '/foo/ { print $1, $NF }' bbslist
fooey B
foot B
macfoo A
sabafoo C

Die Nummer eines Feldes muss keine Konstante sein. Jeder Ausdruck der awk-Sprache kann nach dem Zeichen "$" verwendet werden, um ein Feld zu bezeichnen. Der Wert des Ausdrucks bestimmt dabei die Feldnummer. Wenn der Wert anstatt eine Nummer eine Zeichenkette ist, so wird er zu einer Nummer umgewandelt. Negative Feldnummern sind dabei nicht erlaubt und beenden das Programm.

Nach Evaluation des Ausdrucks "(2*2)" wird der Wert als Nummer des auszugebenden Feldes angesehen. Ausgegeben wird also das vierte Feld der Datei "bbslist".

$ awk '{ print $(2*2) }' bbslist

Ändern von Feldinhalten

Die von awk gesehenen Feldinhalte können innerhalb eines awk-Programms verändert und so ausgegeben werden.

Im folgenden Beispiel wird der originale Wert des dritten Feldes der Datei "inventory" in der Variable "nboxes" gespeichert. Danach wird der Wert der Variable "$3" neu belegt, indem vom ursprünglichen Wert 10 abgezogen werden. Danach werden die Werte der VAriablen "nboxes" und "$3" ausgegeben.

$ awk '{ nboxes = $3 ; $3 = $3 - 10
> print nboxes, $3 }' inventory
25 15
32 22
...

Wird der Wert eines Feldes geändert, so wird der Text des Eingabedatensatzes neu zusammengestellt, sodass er danach den neuen Feldinhalt anstelle des alten enthält. "$0" ändert sich also und zeigt das geänderte Feld.

Im folgenden Beispiel wird eine Kopie der Datei "inventory" ausgegeben, bei der im zweiten Feld jeweils 1o abegezogen wurden.

$ awk '{ $2 = $2 - 10; print $0 }' inventory
Jan 3 25 15 115
Feb 5 32 24 226
...

Möglich ist auch, zusätzliche Felder zu erzeugen, die nicht im ursprünglichen Datensatz vorkommen. "$0" ändert sich auch in diesem Fall und hängt das zusätzliche Feld mit der notwendigen Anzahl Feldtrenner an die bestehenden Felder an.

Im folgenden Beispiel wird ein achtes Feld erzeugt, dessen Wert die Summe des zweiten und dritten Feldes ist.

$ awk '{ $8 = ($2 + $3) ; print $0 }' inventory
Jan 13 25 15 115   38
Feb 15 32 24 226   47
...

Eine Änderung an einem bestehenden Feld ändert zwar den Wert von "$0", aber nicht den der Variable "NF", selbst dann nicht, wenn einem Feld eine leere Zeichenkette zugewiesen wird.

$ echo v w x y z | awk '{ OFS = ":" ; $2 = "" ; print $0 ; print NF }'
v::x:y:z
5
$ echo v w x y z | awk '{ OFS = ":" ; $2 = "" ; $7 = "neu" ; print $0 ; print NF }'
v::x:y:z::neu
7

Das Herabsetzen des Werts der Variable "NF" verwirft die Inhalte der Felder nach dem neuen Wert und stellt die Ausgabe neu zusammen.

 $ echo v w x y z | awk '{ print "NF alt =", NF ; NF = 3 ; print "NF neu = ", NF ; print $0 }'
 NF alt = 5
 NF neu = 3
 v w x

Feldtrenner

Der Feldtrenner besteht entweder aus einem Einzelzeichen oder aus einem regulären Ausdruck und steuert die Art, in der awk einen Datensatz in Felder aufteilt. awk durchsucht den Eingabedatensatz nach Zeichenfolgen, die mit dem Feldtrenner übereinstimmen; die Felder selber sind der Text zwischen den Übereinstimmungen. Natürlich ist der Feldtrenner sorgfältig zu wählen und er sollte wirklich nur an den Stellen vorkommen, welche die gewünschten Felder trennen.

In den folgenden Beispielen wird das Zeichen "♦" verwendet, um einen Leerschlag in der Ausgabe darzustellen.

Ist der Feldtrenner "oo", so wird der Datensatz "moo goo gai pan" in drei Felder aufgeteilt: "m", "♦g", "♦gai♦pan". Zu beachten sind die führenden Leerzeichen beim zweiten und dritten Feld.

Der Feldtrenner wird unter awk durch die Variable "FS" dargestellt und ist standardmässig mit dem Wert "♦" (also einem Leerschlag) belegt. Der Wert dieser Variable kann mit dem Zuweisungsoperator "=" verändert werden. Oft wird das am Beginn eines Programms gemacht, bevor irgendwelche Eingaben verarbeitet wurden, sodass bereits der erste Datensatz mit dem richtigen Feldtrenner gelesen wird. Dazu wird das besondere BEGIN-Muster verwendet. Das folgende Beispiel bestimmt das Komma zum Feldtrenner.

$ echo Hans Mustermann, Spezialweg 5, 8999 Niene | awk 'BEGIN { FS = "," } ; { print $2 }'
♦Spezialweg♦5
FS == "♦" Felder werden durch eine Folge von Leerzeichen getrennt, wobei führende und abschliessende Leerzeichen nicht beachtet werden. Das ist die Standardeinstellung.
FS == irgendein Einzelzeichen Felder werden durch jedes Vorkommen des Zeichens getrennt. Mehrfaches Vorkommen des Zeichens trennt leere Felder, genauso wie führendes und abschliessendes Vorkommen. Das Zeichen kann auch ein rexexp-Metazeichen sein und braucht nicht gefluchtet zu werden.
FS == regexp Felder werden durch Vorkommen der Zeichen getrennt, die mit dem regulären Ausdruck übereinstimmen. Führendes und abschliessendes Vorkommen des regulären Ausdrucks trennen leere Felder.
FS == "" Jedes einzelne Zeichen im Datensatz wird zu einem eigenen Feld. Dabei handelt es sich um einen gawk-Erweiterung, die nicht vom POSIX-Standard vorgesehen ist.

Verwendung regulärer Ausdrücke zum Trennen von Feldern

Im Fall der Verwendung eines regulären Ausdrucks als Wert der Variable "FS" trennt im Datensatz jede Übereinstimmung mit dem regulären Ausdruck die Felder.

Im folgenden Beispiel wird jeder Bereich, der ein Komma, gefolgt von einem Leerschlag und einem Tabulator enthält, als Feldtrenner betrachtet.

FS = ", \t"

Folgendes Beispiel (die Standardbelegung der Variable "FS") verwendet einen einzelnen Leerschlag als Feldtrenner, wobei vor Verarbeitung des Datensatzes führende und abschliessende Leerzeichen entfernt werden.

$ echo ' a b c d ' | awk '{ print $2 }'
b
$ echo ' a b c d ' | awk 'BEGIN { FS = " " } ; { print $2 }'
b

Folgendes Beispiel verwendet einen einzelnen Leerschlag als Feldtrenner.

FS = "[ ]"

Folgendes Beispiel verwendet einen Folge von einem oder mehreren Leerschlägen, Tabulatoren oder Zeilenumbrüchen als Feldtrenner. Das erste Feld wird dabei als "null" oder leer angesehen.

$ echo ' a b c d ' | awk 'BEGIN { FS = "[ \t\n]" } ; { print $2 }'
a

Im folgenden Beispiel gibt die Anweidung "print" den Datensatz so aus, wie er gelesen wurde - die führenden Leerzeichen bleiben dabei erhalten. Die Zuweisung an "$2" erzeugt "$0" neu, indem "$1" bis "$NF" aneinandergehängt werden, getrennt durch den Wert der Variable "OFS". Weil die führenden Leerzeichen nach dem Finden von "$1" ignoriert werden, sind sie nicht mehr Teil von "$0" und werden also durch die zweite "print"-Anweisung auch nicht mehr ausgegeben.

$ echo '   a b c d ' | awk '{ print; $2 = $2; print }'
   a b c d
a b c d

Jedes Zeichen als einzelnes Feld

Um jedes Zeichen eines Datensatzes einzeln zu untersuchen, kann in gawk der Variable "FS" einfach der Wert "" zugewiesen werden. Damit wird jedes einzelne Zeichen des Datensatzes zu einem eigenen Feld.

$ echo a b | awk 'BEGIN { FS = "" }
                  { for (i = 1; i <= NF; i = i + 1)
                    print "Field", i, "is", $i
                  }'
Field 1 is a
Field 2 is
Field 3 is b

Setzen der Variable "FS" auf der Befehlszeile

Die Variable "FS" kann über die Option "-F" auf der Befehlszeile gesetzt werden. Das folgende Beispiel setzt "FS" auf das Komma.

awk -F, 'programm' eingabedatei

Der für das Argument "-F" verwendete Wert wird genau in der Weise verarbeitet wie eine Zuweisung für die eingebaute Variable "FS". Sonderzeichen im Feldtrenner müssen also ebenfalls richtig gefluchtet werden. Folgendes Beispiel verwendet "\" als Feldtrenner:

# dasselbe wie FS = "\\"
awk -F\\\\ 'program' eingabedatei

Ausgabe der Benutzer, für die kein Passwort gesetzt ist:

$ awk -F: '$2 == ""' /etc/passwd

Lesen von Festbreitendaten

Siehe Effective awk programming, S. 46-48

Mehrzeilige Datensätze

In einigen Datenbanken ist ein Datensatz über mehrere Zeilen verteilt. In diesem Fall muss zuerst das Datenformat gewählt werden.

Eine Technik besteht darin, ein im übrigen Datensatz nicht vorkommendes Zeichen oder eine Zeichenkette zum Trennen von Datensätzen zu verwenden. Beispielsweise könnte dafür das Formfeed-Zeichen ("\f") verwendet werden, womit jeder Datensatz zu einer Seite der Datei würde. Zu diesem Zweck würde einfach die Variable "RS" auf den Wert "\f" gesetzt.

Eine andere Technik verwendet Leerzeilen, um die Datensätze voneinander zu trennen. Eine leere Zeichenfolge als Wert von "RS" bedeutet, dass Datensätze durch eine oder mehrere Leerzeilen getrennt sind. Dieselbe Wirkung wie 'RS = ""' kann durch Zuweisung von 'RS = "\n\n+"' erreicht werden. Es ist also gleichgültig, wieviele Leerzeilen sich zwischen den Datensätzen befinden, solange zumindest eine vorhanden ist. Im ersten Beispiel 'RS = ""' kommt dazu, dass führende Leerzeilen nicht beachtet werden, abschliessende Leerzeilen werden entfernt. Im zweiten Beispiel wird weder an den führenden noch abschliessenden Leerzeilen etwas geändert.

Nachdem die Eingabe also in Datensätze getrennt wurde, müssen als zweiter Schritt die Felder in den Datensätzen voneinander getrennt werden.

Ein Weg dahin ist es, jede Zeile auf die übliche Weise in Felder zu zerlegen. Wenn die Variable "RS" aus einer leeren Zeichenkette besteht, so wird der Zeilenumbruch immer als Feldtrenner fungieren. Dies geschieht zusätzlich zu den Trennungen, die über die Variable "FS" gesteuert werden. Sollte das ausnahmsweise nicht gewünscht sein, so kann der Datensatz auch mit Hilfe der Funktion "split" aufgeteilt werden.

Eine andere Möglichkeit besteht darin, die Felder zu trennen, indem jedes Feld auf eine eigene Zeile gesetzt wird. Um das zu erreichen, wird die Variable "FS" auf die Zeichenkette "\n" (ein Zeilenumbruch) gesetzt. Im folgenden Beispiel wird eine Adressliste, in welcher die einzelnen Adressen durch Leerzeilen getrennt sind, so behandelt, dass jede Zeile zu einem Feld wird:

$ cat addresslist
Hans Mustermann
Neue Strasse 26
4353 Irgendwo

Frieda Geldbringer
Froschmatt 32
4545 Immerda

$ cat addressen.awk
BEGIN { RS = "" ; FS = "\n" }
{
  print "Name:", $1
  print "Adresse:", $2
  print "PLZ und Ort:", $3
  print ""
}

$ awk -f adressen.awk addresslist
Name       : Hans Mustermann
Adresse    : Neue Strasse 26
PLZ und Ort: 4353 Irgendwo

Name       : Frieda Geldbringer
Adresse    : Froschmatt 32
PLZ und Ort: 4545 Immerda

Die folgende Liste zeigt, wie Datensätze auf der Grundlage des Werts der Variable "RS" aufgeteilt werden.

RS == "\n" Die Datensätze werden durch den Zeilenumbruch ("\n") getrennt, sodass jede Zeile der Datei zu einem eigenen Datensatz wird. Das ist die Standardeinstellung.
RS == irgendein Einzelzeichen Datensätze werden durch jedes Vorkommen des Zeichens getrennt. Mehrfaches Vorkommen des Zeichens hintereinander trennt leere Datensätze voneinander.
RS == "" Datensätze werden durch Leerzeilen getrennt. Der Zeilenumbruch dient immer als Feldtrenner, zusätzlich zum Wert, den die Variable "FS" sonst bereits haben mag. Führende und abschliessende Zeilenumbrüche in der Datei werden ignoriert.
RS == regexp Datensätze werden durch Vorkommen der Zeichen getrennt, die mit dem regulären Ausdruck übereinstimmen. Führende und abschliessende Übereinstimmungen mit dem regulären Ausdruck begrenzen leere Datensätze.

getline

Bisher wurden die Eingabedaten über awk's Haupteingabestrom erhalten - also entweder über die Standardeingabe (stdin) oder aus auf der Befehlszeile angegebenen Dateien. Der in awk eingebaute Befehl "getline" kann verwendet werden, um die Eingabe ausdrücklich selber zu steuern.

Der Befehl "getline" eignet sich nicht für Anfänger. Vielleichtw erde ich mich später mal mit diesem Befehl beschäftigen.