awk/Lesen von Eingabedateien: Unterschied zwischen den Versionen
Michi (Diskussion | Beiträge) (Die Seite wurde neu angelegt: Wenn Eingabedateien an awk übergeben werden, so werden alle Dateien nacheinander gelesen. Der Name der aktuellen Eingabedatei steht in der Variable "FILENAME". Die Ei...) |
Michi (Diskussion | Beiträge) |
||
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
Zeile 131: | Zeile 131: | ||
{| class=wikitable width=100% | {| class=wikitable width=100% | ||
| width=15% | FS == "♦" || Felder werden durch eine Folge von Leerzeichen getrennt, wobei führende und abschliessende Leerzeichen nicht beachtet werden. Das ist die Standardeinstellung. | | width=15% | <tt>FS == "♦"</tt> || Felder werden durch eine Folge von Leerzeichen getrennt, wobei führende und abschliessende Leerzeichen nicht beachtet werden. Das ist die Standardeinstellung. | ||
|- | |- | ||
| FS == <i>irgendein Einzelzeichen</i> || 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. | | <tt>FS == <i>irgendein Einzelzeichen</i></tt> || 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 == <i>regexp</i> || 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. | | <tt>FS == <i>regexp</i></tt> || 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. | | <tt>FS == ""</tt> || 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. | ||
|} | |} | ||
Zeile 245: | Zeile 245: | ||
{| class=wikitable width=100% | {| class=wikitable width=100% | ||
| width=15% | 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. | | width=15% | <tt>RS == "\n"</tt> || Die Datensätze werden durch den Zeilenumbruch ("\n") getrennt, sodass jede Zeile der Datei zu einem eigenen Datensatz wird. Das ist die Standardeinstellung. | ||
|- | |- | ||
| RS == <i>irgendein Einzelzeichen</i> || Datensätze werden durch jedes Vorkommen des Zeichens getrennt. Mehrfaches Vorkommen des Zeichens hintereinander trennt leere Datensätze voneinander. | | <tt>RS == <i>irgendein Einzelzeichen</i></tt> || 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. | | <tt>RS == ""</tt> || 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 == <i>regexp</i> || 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. | | <tt>RS == <i>regexp</i></tt> || 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. | ||
|} | |} | ||
Aktuelle Version vom 17. Januar 2009, 22:37 Uhr
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.