Kategorie:Gawk: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
Zeile 2: | Zeile 2: | ||
''awkward ''heißt im Englischen soviel wie schwierig, ungünstig. | ''awkward ''heißt im Englischen soviel wie schwierig, ungünstig. | ||
* Ganz so problematisch erweist sich das Erlernen der Programmiersprache Awk dann doch nicht, auch wenn ihre Handhabung von perfektionierter Einfachheit weit entfernt ist. | * Ganz so problematisch erweist sich das Erlernen der Programmiersprache Awk dann doch nicht, auch wenn ihre Handhabung von perfektionierter Einfachheit weit entfernt ist. | ||
* Das Gleichnis der Wortstämme ist Produkt des Zufalls, denn der Begriff Awk gründet sich auf den Initialen seiner Erfinder * Alfred V. '''A'''ho, | |||
Das Gleichnis der Wortstämme ist Produkt des Zufalls, denn der Begriff Awk gründet sich auf den Initialen seiner Erfinder * Alfred V. '''A'''ho, | |||
* Peter J. '''W'''einberger, | * Peter J. '''W'''einberger, | ||
* Brian '''K'''ernighan | * Brian '''K'''ernighan | ||
* und erweiterte erstmals 1978 den Werkzeugfundus von Unix Version 7. | |||
und erweiterte erstmals 1978 den Werkzeugfundus von Unix Version 7. | * Jener Kernighan prägte übrigens gemeinsam mit Dennies Ritchie maßgeblich die Entstehung der Programmiersprache C - wen wundern da noch die Analogien beider Sprachen? | ||
Jener Kernighan prägte übrigens gemeinsam mit Dennies Ritchie maßgeblich die Entstehung der Programmiersprache C - wen wundern da noch die Analogien beider Sprachen? | |||
1987 wartete Awk mit einer komplett überarbeiteten Fassung auf. | 1987 wartete Awk mit einer komplett überarbeiteten Fassung auf. | ||
* Wesentliche Bestandteile fanden im später definierten POSIX-Standard Einzug. | * Wesentliche Bestandteile fanden im später definierten POSIX-Standard Einzug. | ||
* Der freie Awk-Abkömmling der GNU-Gemeinde gawk zeigt sich zu POSIX konform und wird in den folgenden Abschnitten behandelt. | |||
Der freie Awk-Abkömmling der GNU-Gemeinde gawk zeigt sich zu POSIX konform und wird in den folgenden Abschnitten behandelt. | * Der wesentliche Unterschied von Awk zu anderen Programmiersprachen wie den Skriptsprachen der UNIX Shells, C oder Tcl/Tk besteht in der ''datenorientierten Arbeitsweise'', während die typischen Vertreter prozeduraler Programmiersprachen ''funktionsorientiert'' wirken. | ||
* Ein awk-Programm wirkt implizit wie eine endlose Schleife, die fortwährend durchlaufen wird, bis keine Daten mehr in der Eingabe stehen oder das Programm »bewusst« verlassen wird, d. h. | |||
Der wesentliche Unterschied von Awk zu anderen Programmiersprachen wie den Skriptsprachen der UNIX Shells, C oder Tcl/Tk besteht in der ''datenorientierten Arbeitsweise'', während die typischen Vertreter prozeduraler Programmiersprachen ''funktionsorientiert'' wirken. | |||
Ein awk-Programm wirkt implizit wie eine endlose Schleife, die fortwährend durchlaufen wird, bis keine Daten mehr in der Eingabe stehen oder das Programm »bewusst« verlassen wird, d. h. | |||
* der Steuerfluss ist maßgeblich durch die Daten gegeben. | * der Steuerfluss ist maßgeblich durch die Daten gegeben. | ||
* In den meisten anderen Programmiersprachen wird das Hauptprogramm aber einmalig initiiert und Funktionen beeinflussen den Fortschritt der Berechnung. | |||
In den meisten anderen Programmiersprachen wird das Hauptprogramm aber einmalig initiiert und Funktionen beeinflussen den Fortschritt der Berechnung. | * Awk ähnelt somit eher dem Streameditor (sed); er vermag allerdings bedeutend mehr als die »bloße« Modifikation von Textdateien, denn Awk kennt Variablen, Vergleiche, Funktionen, Schleifen u.a.m. | ||
* und ermöglicht eine Interaktion mit dem System. | |||
Awk ähnelt somit eher dem Streameditor (sed) | * Da in Linux-Installationen nahezu ausschließlich die GNU-Implementierung des Werkzeugs vorzufinden ist, werden wir nachfolgend immer die Bezeichnung »awk« verwenden und meinen damit eigentlich »gawk«. | ||
* und ermöglicht eine Interaktion mit dem System. | * Zeigen die Beispiele auf Ihrem System abweichende oder fehlerhafte Reaktionen, überprüfen Sie, ob »awk« in ihrem System ein Link auf »gawk« ist. | ||
Da in Linux-Installationen nahezu ausschließlich die GNU-Implementierung des Werkzeugs vorzufinden ist, werden wir nachfolgend immer die Bezeichnung »awk« verwenden und meinen damit eigentlich »gawk«. | |||
Zeigen die Beispiele auf Ihrem System abweichende oder fehlerhafte Reaktionen, überprüfen Sie, ob »awk« in ihrem System ein Link auf »gawk« ist. | |||
== Allgemeiner Programmaufbau == | == Allgemeiner Programmaufbau == | ||
[[Image:Grafik4.png|alt="Abbildung 1: Die 3 Bestandteile eines Awk-Programms"]] | [[Image:Grafik4.png|alt="Abbildung 1: Die 3 Bestandteile eines Awk-Programms"]] | ||
* Ein Awk-Programm lässt sich in '''drei wesentliche Bestandteile '''zerlegen: | |||
Ein Awk-Programm lässt sich in '''drei wesentliche Bestandteile '''zerlegen: | |||
# Eine optionale Anweisung, die '''einmalig vor''' der Verarbeitung der Eingabedaten durchlaufen wird | # Eine optionale Anweisung, die '''einmalig vor''' der Verarbeitung der Eingabedaten durchlaufen wird | ||
# Ein '''Hauptprogramm''', bestehend aus beliebig vielen Anweisungen, das für jede Eingabezeile erneut ausgeführt wird, es sei denn, eine bestimmte Bedingung führt zum vorzeitigen Abbruch | # Ein '''Hauptprogramm''', bestehend aus beliebig vielen Anweisungen, das für jede Eingabezeile erneut ausgeführt wird, es sei denn, eine bestimmte Bedingung führt zum vorzeitigen Abbruch | ||
# Eine optionale Anweisung, die '''einmalig nach''' der Verarbeitung der Eingabedaten durchlaufen wird | # Eine optionale Anweisung, die '''einmalig nach''' der Verarbeitung der Eingabedaten durchlaufen wird | ||
* Eine '''Anweisung '''besteht wiederum aus einem optionalen '''Muster, '''gefolgt von einem '''Kommandoblock, '''der in geschweifte Klammern eingeschlossen wird. | |||
Eine '''Anweisung '''besteht wiederum aus einem optionalen '''Muster, '''gefolgt von einem '''Kommandoblock, '''der in geschweifte Klammern eingeschlossen wird. | |||
/Muster/ { Kommando [; Kommando] } | /Muster/ { Kommando [; Kommando] } | ||
* Der Kommandoblock darf durchaus auf mehrere Zeilen verteilt werden. | |||
Der Kommandoblock darf durchaus auf mehrere Zeilen verteilt werden. | * Er endet mit der abschließenden geschweiften Klammer, sodass die folgende Aufteilung legitim und aus Gründen der Übersichtlichkeit bei größeren Kommandoblöcken zu empfehlen ist: | ||
Er endet mit der abschließenden geschweiften Klammer, sodass die folgende Aufteilung legitim und aus Gründen der Übersichtlichkeit bei größeren Kommandoblöcken zu empfehlen ist: | |||
/Muster/ { | /Muster/ { | ||
Kommando; | Kommando; | ||
Zeile 50: | Zeile 33: | ||
... | ... | ||
} | } | ||
* Semikola und Zeilenumbrüche trennen Kommandos, sodass dem letzten Kommando auf einer Zeile kein Semikolon folgen muss. | |||
Semikola und Zeilenumbrüche trennen Kommandos, sodass dem letzten Kommando auf einer Zeile kein Semikolon folgen muss. | |||
* Da es aber auch nichts schadet, werden wir es in den weiteren Beispielen verwenden. | * Da es aber auch nichts schadet, werden wir es in den weiteren Beispielen verwenden. | ||
* Die '''Muster '''können reguläre Ausdrücke oder Variablenvergleiche sein.* Sie werden mit der aktuellen Eingabezeile verglichen, woraufhin bei Übereinstimmung der Kommandoblock betreten wird. | |||
Die '''Muster '''können reguläre Ausdrücke oder Variablenvergleiche sein.* Sie werden mit der aktuellen Eingabezeile verglichen, woraufhin bei Übereinstimmung der Kommandoblock betreten wird. | |||
* Fehlt das Muster, wird der Kommandoblock auf jeden Fall ausgeführt. | * Fehlt das Muster, wird der Kommandoblock auf jeden Fall ausgeführt. | ||
* Zwei '''spezielle Muster '''kennzeichnen die anfänglich bzw. | |||
Zwei '''spezielle Muster '''kennzeichnen die anfänglich bzw. | |||
* abschließend auszuführenden Anweisungen.* Die Kommandos hinter dem Muster '''BEGIN '''werden zumeist zu Variableninitialisierungen oder einführenden Ausgaben genutzt. | * abschließend auszuführenden Anweisungen.* Die Kommandos hinter dem Muster '''BEGIN '''werden zumeist zu Variableninitialisierungen oder einführenden Ausgaben genutzt. | ||
* Dementsprechend ermöglicht das Muster '''END '''finale Aktionen, wie die Ausgabe von Ergebnissen. | * Dementsprechend ermöglicht das Muster '''END '''finale Aktionen, wie die Ausgabe von Ergebnissen. | ||
* 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: | |||
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 { | BEGIN { | ||
print "Zählen von Eingabezeilen"; | print "Zählen von Eingabezeilen"; | ||
Zeile 71: | Zeile 49: | ||
END { print "Ergebnis: " zaehler; } | END { print "Ergebnis: " zaehler; } | ||
* Wie Sie das Programm starten können? Nach Studium des folgenden Abschnitts werden Sie es wissen... | |||
Wie Sie das Programm starten können? Nach Studium des folgenden Abschnitts werden Sie es wissen... | |||
== Programmstart == | == Programmstart == | ||
=== Kurze Awk-Programme === | === Kurze Awk-Programme === | ||
Die Möglichkeiten zum Aufruf von Awk sind vielfältig. | * Die Möglichkeiten zum Aufruf von Awk sind vielfältig. | ||
* Für kurze und einmalig verwendete Programme bietet es sich an, diese unmittelbar auf der Kommandozeile anzugeben: | * Für kurze und einmalig verwendete Programme bietet es sich an, diese unmittelbar auf der Kommandozeile anzugeben: | ||
'''awk''' 'Programm' <Datei> [<Datei>] | |||
'''awk''' 'Programm' <Datei> [<Datei>] | * Das eigentliche Awk-Programm muss vor der Auswertung durch die Shell geschützt werden, deshalb die Einbettung in einfache Hochkommata. | ||
* Alle folgenden Elemente der Kommandozeile werden von '''awk '''als Dateinamen interpretiert. | |||
Das eigentliche Awk-Programm muss vor der Auswertung durch die Shell geschützt werden, deshalb die Einbettung in einfache Hochkommata. | * Fehlt ein solcher Name, erwartet Awk die Daten von der Standardeingabe. | ||
* Mit Hilfe dieses Schemas lassen sich auf einfache Art und Weise Felder aus den Zeilen der Eingabe extrahieren. | |||
Alle folgenden Elemente der Kommandozeile werden von '''awk '''als Dateinamen interpretiert. | * Zur Demonstration lassen wir das erste Feld der Passwortdatei ausgeben, wobei die Zeilen nummeriert werden (der Feldseparator ist der Doppelpunkt und ist durch die eingebaute Variable FS festgelegt): | ||
Fehlt ein solcher Name, erwartet Awk die Daten von der Standardeingabe. | |||
Mit Hilfe dieses Schemas lassen sich auf einfache Art und Weise Felder aus den Zeilen der Eingabe extrahieren. | |||
Zur Demonstration lassen wir das erste Feld der Passwortdatei ausgeben, wobei die Zeilen nummeriert werden (der Feldseparator ist der Doppelpunkt und ist durch die eingebaute Variable FS festgelegt): | |||
$ '''awk 'BEGIN {FS = ":"} {print NR,$1}' /etc/passwd''' | $ '''awk 'BEGIN {FS = ":"} {print NR,$1}' /etc/passwd''' | ||
1 root | 1 root | ||
Zeile 98: | Zeile 67: | ||
5 news | 5 news | ||
... | ... | ||
* Möchte man ein awk-Programm auf die Ausgabe eines Kommandos anwenden, lässt sich dies über eine Pipe realisieren: | |||
Möchte man ein awk-Programm auf die Ausgabe eines Kommandos anwenden, lässt sich dies über eine Pipe realisieren: | * Kommando | '''awk''' 'Programm' | ||
* Zur Demonstration »verschönert« nachfolgende Kommandofolge die Ausgabe von date : | |||
Kommando | '''awk''' 'Programm' | |||
Zur Demonstration »verschönert« nachfolgende Kommandofolge die Ausgabe von date : | |||
$ '''date | awk '{print "Der " $2,$3".", "des Jahres", $6}'''' | $ '''date | awk '{print "Der " $2,$3".", "des Jahres", $6}'''' | ||
Der 26. | Der 26. | ||
* Jun. | * Jun. | ||
* des Jahres 2013 | * des Jahres 2013 | ||
=== Umfangreiche Awk-Programme === | === Umfangreiche Awk-Programme === | ||
Einfache Programme erledigen meist nur einfachste Aufgaben, aber die Anforderungen sind oft komplexer. | * Einfache Programme erledigen meist nur einfachste Aufgaben, aber die Anforderungen sind oft komplexer. | ||
* So wird man umfangreiche awk-Programme in separate Dateien schreiben, wie man in der Shell-Programmierung komplexere Konstrukte in Shell-Skripten formuliert. | |||
So wird man umfangreiche awk-Programme in separate Dateien schreiben, wie man in der Shell-Programmierung komplexere Konstrukte in Shell-Skripten formuliert. | * Awk bezieht seine Instruktionen aus einer Steuerdatei, wenn deren Namen explizit mit der Option '''-f '''angegeben wurde: | ||
Awk bezieht seine Instruktionen aus einer Steuerdatei, wenn deren Namen explizit mit der Option '''-f '''angegeben wurde: | |||
'''awk''' -f <Programm.awk> <Datei> [<Datei>] | '''awk''' -f <Programm.awk> <Datei> [<Datei>] | ||
* Bei Verwendung einer Pipe: | |||
Bei Verwendung einer Pipe: | |||
'''Kommando''' | awk -f <Programm.awk> | '''Kommando''' | awk -f <Programm.awk> | ||
* Awk-Anwendungen, die man immer wieder benötigt, wird man bevorzugt in Dateien fassen; Beispiele werden uns im weiteren noch reichlich begegnen. | |||
Awk-Anwendungen, die man immer wieder benötigt, wird man bevorzugt in Dateien fassen; Beispiele werden uns im weiteren noch reichlich begegnen. | * Noch einfacher gestaltet sich der awk-Programmaufruf bei Verwendung von '''awk-Skripten'''. | ||
* Man bedient sich der Aufrufsyntax der entsprechenden UNIX Shell und weist die Shell an, die nachfolgenden Anweisungen dem Awk-Interpreter zuzuführen. | |||
Noch einfacher gestaltet sich der awk-Programmaufruf bei Verwendung von '''awk-Skripten'''. | * Zuoberst muss einem awk-Skript nur die Zeile '''#!/usr/bin/awk -f '''(bzw. | ||
Man bedient sich der Aufrufsyntax der entsprechenden UNIX Shell und weist die Shell an, die nachfolgenden Anweisungen dem Awk-Interpreter zuzuführen. | |||
Zuoberst muss einem awk-Skript nur die Zeile ''' | |||
* der korrekte Zugriffspfad zum awk-Programm) stehen. | * der korrekte Zugriffspfad zum awk-Programm) stehen. | ||
* Versieht man eine solche Datei noch mit den entsprechenden Ausführungsrechten (»chmod u+x Programm.awk«), genügt ein simpler Aufruf: | |||
Versieht man eine solche Datei noch mit den entsprechenden Ausführungsrechten (»chmod u+x Programm.awk«), genügt ein simpler Aufruf: | |||
<Programm.awk> <Datei> | <Programm.awk> <Datei> | ||
* Bei Verwendung einer Pipe: | |||
Bei Verwendung einer Pipe: | * Kommando | <Programm.awk> | ||
Kommando | <Programm.awk> | |||
'''Tipp''' | '''Tipp''' | ||
* Wenn Sie beim Editieren eines awk-Skripts vim oder kate verwenden, so stellt dieser Editor den Text mit Syntax-Highligthing dar, wenn der Skriptname auf ».awk« endet. | |||
Wenn Sie beim Editieren eines awk-Skripts vim oder kate verwenden, so stellt dieser Editor den Text mit Syntax-Highligthing dar, wenn der Skriptname auf ».awk« endet. | * Davon abgesehen, ist die Namensgebung des Skripts Ihnen überlassen. | ||
Davon abgesehen, ist die Namensgebung des Skripts Ihnen überlassen. | |||
=== Kommandozeilenoptionen === | === Kommandozeilenoptionen === | ||
Einige Optionen wurden schon verwendet, ohne ihre konkrete Bedeutung zu erläutern. | * Einige Optionen wurden schon verwendet, ohne ihre konkrete Bedeutung zu erläutern. | ||
* Dies soll hier nachgeholt werden: | * Dies soll hier nachgeholt werden: | ||
{| | {| | ||
|- | |- | ||
Zeile 155: | Zeile 101: | ||
| | awk arbeitet auf Feldern einer Eingabezeile. | | | awk arbeitet auf Feldern einer Eingabezeile. | ||
* Normalerweise dient das Leerzeichen/Tabulator zur Trennung einzelner Felder. | * Normalerweise dient das Leerzeichen/Tabulator zur Trennung einzelner Felder. | ||
* Mit der Option -F wird der Wert der internen Variable '''FS '''(field separator) verändert. | |||
Mit der Option -F wird der Wert der internen Variable '''FS '''(field separator) verändert. | * Das bereits erwähnte Beispiel zur Ausgabe des ersten Feldes der Passwortdatei, indem das BEGIN-Muster zum Setzen des Feldtrenners genutzt wurde, lässt sich somit auch wie folgt realisieren: | ||
Das bereits erwähnte Beispiel zur Ausgabe des ersten Feldes der Passwortdatei, indem das BEGIN-Muster zum Setzen des Feldtrenners genutzt wurde, lässt sich somit auch wie folgt realisieren: | |||
$ '''awk -F : '{print NR,$1}' /etc/passwd''' | $ '''awk -F : '{print NR,$1}' /etc/passwd''' | ||
1 root | 1 root | ||
Zeile 189: | Zeile 132: | ||
* Die Operatoren ** und **= können nicht statt ^ und ^= genutzt werden | * Die Operatoren ** und **= können nicht statt ^ und ^= genutzt werden | ||
* Die fflush() Funktion ist nicht verfügbar | * Die fflush() Funktion ist nicht verfügbar | ||
|- | |- | ||
|} | |} | ||
=== Kommandozeilenparameter === | === Kommandozeilenparameter === | ||
Awk' s Umgang mit Kommandozeilenparametern ist etwas eigenwillig. | * Awk' s Umgang mit Kommandozeilenparametern ist etwas eigenwillig. | ||
* C-Programmierern sind sicherlich die Variablennamen '''argc '''und '''argv '''geläufig, die bevorzugt gewählt werden, um Kommandozeilenargumente an das Hauptprogramm zu übergeben. | |||
C-Programmierern sind sicherlich die Variablennamen '''argc '''und '''argv '''geläufig, die bevorzugt gewählt werden, um Kommandozeilenargumente an das Hauptprogramm zu übergeben. | * Awk verfügt über zwei builtin-Variablen '''ARGC '''und '''ARGV, '''die die Anzahl auf der Kommandozeile stehenden Parameter (ARGC) angeben und den Zugriff darauf über ein Feld (ARGV) ermöglichen. | ||
* Das verwirrende daran ist, dass Awk seine eigenen Kommandozeilenoptionen nicht mitzählt. | |||
Awk verfügt über zwei builtin-Variablen '''ARGC '''und '''ARGV, '''die die Anzahl auf der Kommandozeile stehenden Parameter (ARGC) angeben und den Zugriff darauf über ein Feld (ARGV) ermöglichen. | |||
Das verwirrende daran ist, dass Awk seine eigenen Kommandozeilenoptionen nicht mitzählt. | |||
==== Dazu ein Beispiel (arguments.awk): ==== | ==== Dazu ein Beispiel (arguments.awk): ==== | ||
#!/usr/bin/awk -f | |||
BEGIN { | BEGIN { | ||
Zeile 209: | Zeile 147: | ||
print i, ".Argument: ", ARGV[i]; | print i, ".Argument: ", ARGV[i]; | ||
} | } | ||
* Wir verzichten jetzt auf die Beschreibung der verwendeter Kontrollkonstrukte und eingebauter Variablen und kommen später darauf zurück. | |||
Wir verzichten jetzt auf die Beschreibung der verwendeter Kontrollkonstrukte und eingebauter Variablen und kommen später darauf zurück. | |||
==== Achten Sie in den folgenden Testläufen auf die Reihenfolge der Argumente: ==== | ==== Achten Sie in den folgenden Testläufen auf die Reihenfolge der Argumente: ==== | ||
$ '''./arguments.awk -F : --posix n=3 "Berlin"''' | $ '''./arguments.awk -F : --posix n=3 "Berlin"''' | ||
Zeile 228: | Zeile 164: | ||
3 .Argument: --posix | 3 .Argument: --posix | ||
4 .Argument: Berlin | 4 .Argument: Berlin | ||
==== Aus dem Beispiel sind mehrere Regeln abzuleiten: ==== | ==== Aus dem Beispiel sind mehrere Regeln abzuleiten: ==== | ||
# ARGC ist mindestens 1 (da der Programmname immer als erstes Argument übergeben wird) | # ARGC ist mindestens 1 (da der Programmname immer als erstes Argument übergeben wird) | ||
Zeile 235: | Zeile 170: | ||
# Sobald Awk ein Argument als »nicht-eigene Option« erkennt, behandelt es alle weiteren Argumente als »fremde Optionen« (diese verlieren damit auch ihre übliche Wirkung) | # Sobald Awk ein Argument als »nicht-eigene Option« erkennt, behandelt es alle weiteren Argumente als »fremde Optionen« (diese verlieren damit auch ihre übliche Wirkung) | ||
# Für Awk ist jedes Argument zunächst der Name einer Datei | # Für Awk ist jedes Argument zunächst der Name einer Datei | ||
* Die letzte Eigenschaft ist sofort anhand von Fehlermeldungen ersichtlich, sobald das Beispielprogramm »normale Anweisungen« (außer BEGIN und END) umfasst. | |||
Die letzte Eigenschaft ist sofort anhand von Fehlermeldungen ersichtlich, sobald das Beispielprogramm »normale Anweisungen« (außer BEGIN und END) umfasst. | * Argumente würden allerdings jeglichen Nutzen entbehren, ließen sie sich nicht doch vor '''Awk's '''Interpretation schützen. | ||
* Da Awk die so übergebenen Dateien erst mit dem Eintritt in die Hauptschleife zu öffnen versucht, bleibt das BEGIN-Muster als der Ort der Einflussnahme übrig. | |||
Argumente würden allerdings jeglichen Nutzen entbehren, ließen sie sich nicht doch vor '''Awk's '''Interpretation schützen. | |||
Da Awk die so übergebenen Dateien erst mit dem Eintritt in die Hauptschleife zu öffnen versucht, bleibt das BEGIN-Muster als der Ort der Einflussnahme übrig. | |||
* Ein Argument sollte hier ausgewertet und anschließend aus dem Array ARGV gelöscht werden. | * Ein Argument sollte hier ausgewertet und anschließend aus dem Array ARGV gelöscht werden. | ||
* Das folgende Beispiel nutzt das erste Kommandozeilenargument, um die eingebaute Variable '''FS '''neu zu belegen: | |||
Das folgende Beispiel nutzt das erste Kommandozeilenargument, um die eingebaute Variable '''FS '''neu zu belegen: | #!/usr/bin/awk -f | ||
BEGIN { | BEGIN { | ||
Zeile 254: | Zeile 184: | ||
} | } | ||
... | ... | ||
* Nehmen Sie das Beispiel nicht zu ernst... | |||
Nehmen Sie das Beispiel nicht zu ernst... | |||
* den Field Separator FS würde jeder erfahrene Awk-Programmierer mittels der Option -F setzen. | * den Field Separator FS würde jeder erfahrene Awk-Programmierer mittels der Option -F setzen. | ||
Überhaupt ist im Beispiel die Annahme einer festen Position des Arguments eine unsaubere Methode und sollte vermieden werden. | Überhaupt ist im Beispiel die Annahme einer festen Position des Arguments eine unsaubere Methode und sollte vermieden werden. | ||
* Besser wäre eine positionsunabhängige Behandlung aller Argumente. | * Besser wäre eine positionsunabhängige Behandlung aller Argumente. | ||
==== Initiale Werte ==== | ==== Initiale Werte ==== | ||
Dienen Argumente einzig dazu, im Programm verwendeten Variablen initiale Werte zuzuweisen, so kann dies vorteilhaft erfolgen, indem dem Variablennamen auf der Kommandozeile der Startwert per Gleichheitszeichen zugewiesen wird. | * Dienen Argumente einzig dazu, im Programm verwendeten Variablen initiale Werte zuzuweisen, so kann dies vorteilhaft erfolgen, indem dem Variablennamen auf der Kommandozeile der Startwert per Gleichheitszeichen zugewiesen wird. | ||
* Allerdings stehen derartige Variablen erst in der Hauptschleife und nicht im BEGIN-Block zur Verfügung (es sei denn, auf sie wird über ARGV zugegriffen). | |||
Allerdings stehen derartige Variablen erst in der Hauptschleife und nicht im BEGIN-Block zur Verfügung (es sei denn, auf sie wird über ARGV zugegriffen). | * Als Beispiel dient eine awk-basierte Variante des Kommandos »head«, das die ersten n Zeilen der Eingabe (10 in der Voreinstellung) ausgibt: | ||
#!/usr/bin/awk -f | |||
Als Beispiel dient eine awk-basierte Variante des Kommandos »head«, das die ersten n Zeilen der Eingabe (10 in der Voreinstellung) ausgibt: | |||
BEGIN { | BEGIN { | ||
Zeile 276: | Zeile 200: | ||
{ if (n < FNR) exit; } | { if (n < FNR) exit; } | ||
{ print $0; } | { print $0; } | ||
* Speicherten wir nun das Programm unter dem Namen »head.awk« (chmod nicht vergessen!) und bringen es ohne weitere Argumente zur Ausführung, so werden - gemäß der Voreinstellung »n=10« im BEGIN-Muster - die ersten 10 Zeilen der Eingabedatei ausgegeben: | |||
Speicherten wir nun das Programm unter dem Namen »head.awk« (chmod nicht vergessen!) und bringen es ohne weitere Argumente zur Ausführung, so werden - gemäß der Voreinstellung »n=10« im BEGIN-Muster - die ersten 10 Zeilen der Eingabedatei ausgegeben: | |||
$ '''./head.awk /etc/services ''' | $ '''./head.awk /etc/services ''' | ||
# | |||
# Network services, Internet style | |||
# | |||
# Note that it is presently the policy of IANA to assign a single well-known | |||
# port number for both TCP and UDP; hence, most entries here have two entries | |||
# even if the protocol doesn't support UDP operations. | |||
# | |||
# This list could be found on: | |||
# [http://www.iana.org/assignments/port-numbers http://www.iana.org/assignments/port-numbers] | |||
# | |||
* Um eine abweichende Anzahl Zeilen zur Ausgabe zu bringen, muss der Variablen n beim Kommandoaufruf der entsprechenden Wert mitgegeben werden: | |||
Um eine abweichende Anzahl Zeilen zur Ausgabe zu bringen, muss der Variablen n beim Kommandoaufruf der entsprechenden Wert mitgegeben werden: | |||
$ '''./head.awk n=2 /etc/services''' | $ '''./head.awk n=2 /etc/services''' | ||
# | |||
# Network services, Internet style | |||
* Die Zuweisung an die Variable muss vor dem Dateinamen stehen; im anderen Fall wäre n noch nicht bekannt, wenn Awk die Datei betrachtet. | |||
Die Zuweisung an die Variable muss vor dem Dateinamen stehen; im anderen Fall wäre n noch nicht bekannt, wenn Awk die Datei betrachtet. | |||
* Mehrere Variablen lassen sich durch Leerzeichen getrennt angeben. | * Mehrere Variablen lassen sich durch Leerzeichen getrennt angeben. | ||
* Zwischen Variablennamen und Wert darf sich nur das Gleichheitszeichen befinden (auch keine Leerzeichen!): | |||
Zwischen Variablennamen und Wert darf sich nur das Gleichheitszeichen befinden (auch keine Leerzeichen!): | |||
==== Syntaxfehler ==== | ==== Syntaxfehler ==== | ||
[mailto:dirkw@lincln01 $ ]'''./head.awk n = 2 /etc/services''' | [mailto:dirkw@lincln01 $ ]'''./head.awk n = 2 /etc/services''' | ||
awk: ./head.awk:4: Fatal: Die Datei »n« kann nicht zum Lesen geöffnet werden (Datei oder Verzeichnis nicht gefunden) | awk: ./head.awk:4: Fatal: Die Datei »n« kann nicht zum Lesen geöffnet werden (Datei oder Verzeichnis nicht gefunden) | ||
== Reguläre Ausdrücke in Mustern == | == Reguläre Ausdrücke in Mustern == | ||
Ein Muster ist die maßgebliche Methode, um den Programmfluss von Awk zu steuern. | * Ein Muster ist die maßgebliche Methode, um den Programmfluss von Awk zu steuern. | ||
* Nur wenn der Inhalt des aktuell bearbeiteten Datensatzes mit dem angegebenen Muster übereinstimmt, wird der zugehörige Kommandoblock ausgeführt. | |||
Nur wenn der Inhalt des aktuell bearbeiteten Datensatzes mit dem angegebenen Muster übereinstimmt, wird der zugehörige Kommandoblock ausgeführt. | * Um dieses Schema flexibel zu gestalten, verhelfen reguläre Ausdrücke zur Formulierung der Muster. | ||
* Anstelle starrer Vergleichsmuster treten Platzhalter, sodass von der Eingabe quasi nur noch eine »Ähnlichkeit« mit dem Muster gefordert wird, um die Kommandofolge anzuwenden. | |||
Um dieses Schema flexibel zu gestalten, verhelfen reguläre Ausdrücke zur Formulierung der Muster. | * Zulässig sind die erweiterten Reguläre Ausdrücke wie sie von egrep verwendet werden: | ||
Anstelle starrer Vergleichsmuster treten Platzhalter, sodass von der Eingabe quasi nur noch eine »Ähnlichkeit« mit dem Muster gefordert wird, um die Kommandofolge anzuwenden. | |||
Zulässig sind die erweiterten Reguläre Ausdrücke wie sie von egrep verwendet werden: | |||
{| | {| | ||
|- | |- | ||
Zeile 393: | Zeile 304: | ||
| | '''\'''' | | | '''\'''' | ||
| | matches the empty string at the end of a buffer. | | | matches the empty string at the end of a buffer. | ||
|- | |- | ||
|} | |} | ||
==== Beispiel ==== | ==== Beispiel ==== | ||
Um alle Leerzeilen der Datei /etc/inittab auszugeben, hilft: | * Um alle Leerzeilen der Datei /etc/inittab auszugeben, hilft: | ||
$ '''awk '/^$/ {print "Zeile" FNR "ist leer"}' /etc/fstab''' | $ '''awk '/^$/ {print "Zeile" FNR "ist leer"}' /etc/fstab''' | ||
Zeile9ist leer | Zeile9ist leer | ||
Zeile 404: | Zeile 313: | ||
Zeile13ist leer | Zeile13ist leer | ||
Zeile17ist leer | Zeile17ist leer | ||
* Die interne Variable '''FNR '''enthält die Nummer der aktuell bearbeiteten Zeile. | |||
Die interne Variable '''FNR '''enthält die Nummer der aktuell bearbeiteten Zeile. | |||
==== Beispiel ==== | ==== Beispiel ==== | ||
Steht eine gültige Zahl (Integer) in der Eingabe? | * Steht eine gültige Zahl (Integer) in der Eingabe? | ||
$ '''awk '/^[[:digit:]]+$/ {print "Ja!"}'''' | |||
$ ''' | |||
123457900 | 123457900 | ||
Ja! | Ja! | ||
12A | 12A | ||
[Ctrl]-[D] | [Ctrl]-[D] | ||
* Die enthaltene Anwendung von Zeichenklassen ([:digit:]) wird später behandelt. | |||
Die enthaltene Anwendung von Zeichenklassen ([:digit:]) wird später behandelt. | |||
=== Metazeichen / regulärer Ausdruck === | === Metazeichen / regulärer Ausdruck === | ||
Verwechseln Sie die regulären Ausdrücke nicht mit den Metazeichen der Shells! Zwar ist das Funktionsprinzip identisch, selten aber die konkrete Semantik der Zeichen (bspw. * oder ?): | * Verwechseln Sie die regulären Ausdrücke nicht mit den Metazeichen der Shells! Zwar ist das Funktionsprinzip identisch, selten aber die konkrete Semantik der Zeichen (bspw. * oder ?): | ||
* Liste alle mit "a" beginnenden Dateien (Shell): | |||
Liste alle mit "a" beginnenden Dateien (Shell): | |||
$ '''ls -1 a*''' | $ '''ls -1 a*''' | ||
a.out | a.out | ||
ascii2short.c | ascii2short.c | ||
awk.txt | awk.txt | ||
* Suche in der Ausgabe von "ls" nach mit "a" beginnenden Dateien (awk) | |||
Suche in der Ausgabe von "ls" nach mit "a" beginnenden Dateien (awk) | |||
$ '''ls | awk '/^a+/ {print}'''' | $ '''ls | awk '/^a+/ {print}'''' | ||
a.out | a.out | ||
ascii2short.c | ascii2short.c | ||
awk.txt | awk.txt | ||
* Das Beispiel verdeutlicht die unterschiedliche Syntax zwischen Shell und awk bei der Realisierung derselben Aufgabe. | |||
Das Beispiel verdeutlicht die unterschiedliche Syntax zwischen Shell und awk bei der Realisierung derselben Aufgabe. | |||
==== Übung ==== | ==== Übung ==== | ||
Ersetzen Sie einmal das awk-Muster durch »/a*/« und vergleichen die Resultate. | * Ersetzen Sie einmal das awk-Muster durch »/a*/« und vergleichen die Resultate. | ||
* Was ist da passiert? | * Was ist da passiert? | ||
=== Muster und Zeichenketten === | === Muster und Zeichenketten === | ||
Bislang arbeitete die Mustererkennung stets über die gesamte Eingabezeile. | * Bislang arbeitete die Mustererkennung stets über die gesamte Eingabezeile. | ||
"Zeichenkette ~ /Muster/" | "Zeichenkette ~ /Muster/" | ||
… dehnt sich dieses Schema auf beliebige Zeichenketten aus. | … dehnt sich dieses Schema auf beliebige Zeichenketten aus. | ||
* So extrahiert nachfolgendes Awk-Programm alle Benutzernamen aus der Datei /etc/passwd, deren Heimatverzeichnisse unterhalb von /home liegen: | |||
So extrahiert nachfolgendes Awk-Programm alle Benutzernamen aus der Datei /etc/passwd, deren Heimatverzeichnisse unterhalb von /home liegen: | |||
$ '''awk -F ':' '$6 ~ /^\/home/ {print $1}' /etc/passwd''' | $ '''awk -F ':' '$6 ~ /^\/home/ {print $1}' /etc/passwd''' | ||
tux | tux | ||
user | user | ||
* Die Zeichenkette ist hier das 6.Feld ($6) der Passwortdatei (Heimatverzeichnis); die Syntax des Musters sollte sich anhand der einleitenden Tabelle der regulären Ausdrücke erklären. | |||
Die Zeichenkette ist hier das 6.Feld ($6) der Passwortdatei (Heimatverzeichnis); die Syntax des Musters sollte sich anhand der einleitenden Tabelle der regulären Ausdrücke erklären. | * Felder und deren Indizierung sind Gegenstand des folgenden Abschnitts. | ||
Felder und deren Indizierung sind Gegenstand des folgenden Abschnitts. | |||
==== Flexible Muster ==== | ==== Flexible Muster ==== | ||
Der Zeichenkettenvergleich ist auch hilfreich, wenn das zu betrachtende Muster flexibel gestaltet werden soll. | * Der Zeichenkettenvergleich ist auch hilfreich, wenn das zu betrachtende Muster flexibel gestaltet werden soll. | ||
* Die Zeichenkette ist dann die aktuell bearbeitete Zeile ($0, wird später behandelt); als Muster dient der Inhalt einer Variablen (muster im Beispiel): | * Die Zeichenkette ist dann die aktuell bearbeitete Zeile ($0, wird später behandelt); als Muster dient der Inhalt einer Variablen (muster im Beispiel): | ||
$ '''cat sinnlos.awk''' | $ '''cat sinnlos.awk''' | ||
#!/usr/bin/awk -f | |||
$0 ~ muster { print $0; } | $0 ~ muster { print $0; } | ||
* Die Variable <tt>muster</tt> wird dann als Kommandozeilenargument geeignet belegt: | |||
Die Variable <tt>muster</tt> wird dann als Kommandozeilenargument geeignet belegt: | |||
$ '''./sinnlos.awk muster='.+' <Datei>''' | $ '''./sinnlos.awk muster='.+' <Datei>''' | ||
=== Zeichenklassen === | === Zeichenklassen === | ||
Der Begriff »Wort« ließe sich einfach als eine Folge von Buchstaben umschreiben; eine »ganze Zahl« demzufolge als Ansammlung von Ziffern. | * Der Begriff »Wort« ließe sich einfach als eine Folge von Buchstaben umschreiben; eine »ganze Zahl« demzufolge als Ansammlung von Ziffern. | ||
* Ein simples awk-Programm, das Zeichenketten aus der Eingabe in die Kategorien »Wort« oder »Zahl« eingliedert, könnte wie folgt verfasst sein: | |||
Ein simples awk-Programm, das Zeichenketten aus der Eingabe in die Kategorien »Wort« oder »Zahl« eingliedert, könnte wie folgt verfasst sein: | # Testet die Eingabe auf Wort, Zahl oder Sonstiges. | ||
/[0-9]+/ { print "Eine Zahl" } | /[0-9]+/ { print "Eine Zahl" } | ||
/[A-Za-z]+/ { print "Ein Wort" } | /[A-Za-z]+/ { print "Ein Wort" } | ||
* Auf den ersten Blick ist kein Fallstrick zu erkennen, aber* Wer garantiert, dass ein Skript tatsächlich nur in Umgebungen eingesetzt wird, die denselben Zeichensatz verwenden wie der Skriptentwickler? | |||
Auf den ersten Blick ist kein Fallstrick zu erkennen, aber* Wer garantiert, dass ein Skript tatsächlich nur in Umgebungen eingesetzt wird, die denselben Zeichensatz verwenden wie der Skriptentwickler? | |||
* Nicht in jedem Zeichensatz bilden Ziffern bzw. | * Nicht in jedem Zeichensatz bilden Ziffern bzw. | ||
* Buchstaben eine zusammenhängende Folge. | * Buchstaben eine zusammenhängende Folge. | ||
* Und Angaben »[von-bis]« beruhen genau auf jenem Prinzip. | * Und Angaben »[von-bis]« beruhen genau auf jenem Prinzip. | ||
* Um Skripte portabel (POSIX-Standard) zu halten - und mitunter auch kürzer - sollte daher Zeichenklassen der Vorzug vor Bereichsangaben gegeben werden. | |||
Um Skripte portabel (POSIX-Standard) zu halten - und mitunter auch kürzer - sollte daher Zeichenklassen der Vorzug vor Bereichsangaben gegeben werden. | * Es ist dann Aufgabe der konkreten Awk-Implementierung, eine '''Zeichenklasse '''auf den verwendeten Zeichensatz abzubilden. | ||
* Unser Beispiel mit Zeichenklassen schreibt sich dann so: | |||
Es ist dann Aufgabe der konkreten Awk-Implementierung, eine '''Zeichenklasse '''auf den verwendeten Zeichensatz abzubilden. | # Testet die Eingabe auf Wort, Zahl oder Sonstiges. | ||
/[[:digit:]]+/ { print "Eine Zahl" } | |||
Unser Beispiel mit Zeichenklassen schreibt sich dann so: | /[[:alpha:]]+/ { print "Ein Wort" } | ||
* Vorausgesetzt Ihr System wurde sauber konfiguriert (Belegung der Shellvariablen $LANG), sollten nun auch die deutschen Umlaute korrekt erfasst werden: | |||
$ echo Überprüfung | awk '/[[:alpha:]]+/ {print "Ein Wort";}' | |||
Vorausgesetzt Ihr System wurde sauber konfiguriert (Belegung der Shellvariablen $LANG), sollten nun auch die deutschen Umlaute korrekt erfasst werden: | |||
Ein Wort | Ein Wort | ||
==== Weitere Zeichenklassen: ==== | ==== Weitere Zeichenklassen: ==== | ||
{| | {| | ||
Zeile 508: | Zeile 384: | ||
| | '''[:alpha:] ''' | | | '''[:alpha:] ''' | ||
| | Alphabetische Zeichen.Um zu testen, ob in einer Variablen eine gültige Zahl (ganzzahlig) gespeichert ist, bietet sich folgendes Konstrukt an: | | | Alphabetische Zeichen.Um zu testen, ob in einer Variablen eine gültige Zahl (ganzzahlig) gespeichert ist, bietet sich folgendes Konstrukt an: | ||
'''echo "0815" | awk '/^[[:digit:]]+$/ {print "eine Zahl"}'''' | |||
''' | |||
Eine Zahl | Eine Zahl | ||
|- | |- | ||
Zeile 541: | Zeile 416: | ||
| | '''[:xdigit:] ''' | | | '''[:xdigit:] ''' | ||
| | Hexadezimale Zeichen | | | Hexadezimale Zeichen | ||
|- | |- | ||
|} | |} | ||
== Datenfelder und Variablen == | == Datenfelder und Variablen == | ||
=== Wichtige eingebaute Variablen === | === Wichtige eingebaute Variablen === | ||
Awk arbeitet unter der Annahme, dass die Eingabe strukturiert ist. | * Awk arbeitet unter der Annahme, dass die Eingabe strukturiert ist. | ||
* Im einfachsten Fall interpretiert '''awk '''jede Eingabezeile als Datensatz und jedes enthaltene Wort als '''Feld. '''Ein '''Wort''' ist dabei jede von Feldseparatoren begrenzte Zeichenfolge. | * Im einfachsten Fall interpretiert '''awk '''jede Eingabezeile als Datensatz und jedes enthaltene Wort als '''Feld. '''Ein '''Wort''' ist dabei jede von Feldseparatoren begrenzte Zeichenfolge. | ||
* In der Voreinstellung trennen Leerzeichen und Tabulatoren Wörter; durch Belegung der builtin-Variablen '''FS '''lassen sich beliebige Separatoren definieren. | |||
In der Voreinstellung trennen Leerzeichen und Tabulatoren Wörter; durch Belegung der builtin-Variablen '''FS '''lassen sich beliebige Separatoren definieren. | |||
Über den '''Feldoperator $''' gestattet Awk den Zugriff auf die Felder der zuletzt eingelesenen Eingabezeile. | Über den '''Feldoperator $''' gestattet Awk den Zugriff auf die Felder der zuletzt eingelesenen Eingabezeile. | ||
* Im Unterschied zur Programmiersprache C beginnt die Nummerierung der Felder bei 1 und - im Gegensatz zu den Positionsparametern der Shell - endet sie nicht bei 9 (implementierungsabhängig werden 100 Felder garantiert; gawk auf x86-Architektur kann vermutlich 232 Felder indizieren). | |||
Im Unterschied zur Programmiersprache C beginnt die Nummerierung der Felder bei 1 und - im Gegensatz zu den Positionsparametern der Shell - endet sie nicht bei 9 (implementierungsabhängig werden 100 Felder garantiert; gawk auf x86-Architektur kann vermutlich 232 Felder indizieren). | * Um z.B. | ||
Um z.B. | |||
* das 1. | * das 1. | ||
* und 11. | * und 11. | ||
* Feld einer Eingabezeile auszugeben, hilft Folgendes: | * Feld einer Eingabezeile auszugeben, hilft Folgendes: | ||
$ '''echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print $1,$11;}'''' | $ '''echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print $1,$11;}'''' | ||
0 10 | 0 10 | ||
* Der Feldoperator '''$0 '''steht für die gesamte Zeile; die Anzahl der Felder der aktuell bearbeiteten Zeile ist in der Variablen '''NF '''gespeichert. | |||
Der Feldoperator '''$0 '''steht für die gesamte Zeile; die Anzahl der Felder der aktuell bearbeiteten Zeile ist in der Variablen '''NF '''gespeichert. | |||
$ '''echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print NF,$0;}'''' | $ '''echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print NF,$0;}'''' | ||
12 0 1 2 3 4 5 6 7 8 9 10 11 | 12 0 1 2 3 4 5 6 7 8 9 10 11 | ||
* Die aktuelle Zeilennummer hält Awk in der Variablen '''NR.''' | |||
Die aktuelle Zeilennummer hält Awk in der Variablen '''NR.''' | * Um die Zeilen einer Datei zu nummerieren, könnte somit folgender Aufruf dienen: | ||
Um die Zeilen einer Datei zu nummerieren, könnte somit folgender Aufruf dienen: | |||
$ '''awk '{print NR,$0;}' <zu_nummerierende_Datei>''' | $ '''awk '{print NR,$0;}' <zu_nummerierende_Datei>''' | ||
* Bei aufmerksamer Betrachtung des einführenden Beispiels »head.awk« ist Ihnen vermutlich aufgefallen, dass wir dort '''FNR '''anstatt '''NR '''zur Nummerierung verwendeten. | |||
Bei aufmerksamer Betrachtung des einführenden Beispiels »head.awk« ist Ihnen vermutlich aufgefallen, dass wir dort '''FNR '''anstatt '''NR '''zur Nummerierung verwendeten. | * Der Unterschied ist, dass Letzteres (NR) fortlaufend die Anzahl der Durchläufe der Hauptschleife zählt, während FNR im Falle mehrerer Eingabedateien die Zählung für jede Datei von vorn beginnt. | ||
Der Unterschied ist, dass Letzteres (NR) fortlaufend die Anzahl der Durchläufe der Hauptschleife zählt, während FNR im Falle mehrerer Eingabedateien die Zählung für jede Datei von vorn beginnt. | |||
==== Beispiel ==== | ==== Beispiel ==== | ||
»wc -l« mit Mitteln von Awk, wobei außerdem die Variable '''FILENAME '''verwendet wird, die den Namen der aktuell bearbeiteten Datei enthält : | »wc -l« mit Mitteln von Awk, wobei außerdem die Variable '''FILENAME '''verwendet wird, die den Namen der aktuell bearbeiteten Datei enthält : | ||
#!/usr/bin/awk -f | |||
BEGIN { zeile=0; } | BEGIN { zeile=0; } | ||
Zeile 595: | Zeile 455: | ||
if ( FNR != NR ) { print NR, "insgesamt"; } | if ( FNR != NR ) { print NR, "insgesamt"; } | ||
} | } | ||
* Als Testfall wenden wir das kleine Programm auf sich selbst an und vergleichen die Ausgabe mit der von »wc -l«: | |||
Als Testfall wenden wir das kleine Programm auf sich selbst an und vergleichen die Ausgabe mit der von »wc -l«: | |||
$ '''./wc-l.awk /etc/fstab /etc/passwd''' | $ '''./wc-l.awk /etc/fstab /etc/passwd''' | ||
17 /etc/passwd | 17 /etc/passwd | ||
Zeile 606: | Zeile 464: | ||
46 /etc/passwd | 46 /etc/passwd | ||
63 insgesamt | 63 insgesamt | ||
* Abgesehen von der Formatierung (wir später noch behoben) verhält sich unser Programm exakt wie das Vorbild. | |||
Abgesehen von der Formatierung (wir später noch behoben) verhält sich unser Programm exakt wie das Vorbild. | * Eher unüblich ist die Manipulation der Variablen '''RS''', die den '''Zeilenseparator''' spezifiziert. | ||
* Sinnvoll ist es für Datensätze, die über mehrere Zeilen verteilt stehen und ein anderes Zeichen (bspw. | |||
Eher unüblich ist die Manipulation der Variablen '''RS''', die den '''Zeilenseparator''' spezifiziert. | |||
Sinnvoll ist es für Datensätze, die über mehrere Zeilen verteilt stehen und ein anderes Zeichen (bspw. | |||
* eine Leerzeile) eine eindeutige Strukturierung erlaubt. | * eine Leerzeile) eine eindeutige Strukturierung erlaubt. | ||
* Das die Ausgabe von Awk betreffende Pedand zu RS ist der '''Output Record Selector (ORS).''' | |||
Das die Ausgabe von Awk betreffende Pedand zu RS ist der '''Output Record Selector (ORS).''' | * In der Voreinstellung dient der Zeilenumbruch zur Separierung der Ausgaben; durch Neubelegung von ORS kann dies geändert werden. | ||
* Nachfolgender Kommandoaufruf ersetzt die Unix-Dateiendung (Newline) durch das Windows-Äquivalent (Carriage Return, Newline): | |||
In der Voreinstellung dient der Zeilenumbruch zur Separierung der Ausgaben; durch Neubelegung von ORS kann dies geändert werden. | |||
Nachfolgender Kommandoaufruf ersetzt die Unix-Dateiendung (Newline) durch das Windows-Äquivalent (Carriage Return, Newline): | |||
$ '''awk 'BEGIN {ORS="\r\n";}{print $0;}' <Datei>''' | $ '''awk 'BEGIN {ORS="\r\n";}{print $0;}' <Datei>''' | ||
* Natürlich hätte es auch »recode« getan... | |||
Natürlich hätte es auch »recode« getan... | |||
* Neben ORS zum Ändern des Zeilenseparators existiert mit '''OFS '''eine Variable, die das Trennzeichen für einzelne Datenfelder (Voreinstellung: Leerzeichen) festlegt. | * Neben ORS zum Ändern des Zeilenseparators existiert mit '''OFS '''eine Variable, die das Trennzeichen für einzelne Datenfelder (Voreinstellung: Leerzeichen) festlegt. | ||
=== Weitere eingebaute Variablen === | === Weitere eingebaute Variablen === | ||
Die weiteren von Awk verwendeten internen Variablen sollen an dieser Stelle nur kurz benannt werden. | * Die weiteren von Awk verwendeten internen Variablen sollen an dieser Stelle nur kurz benannt werden. | ||
* Einigen Vertretern werden wir später erneut begegnen. | |||
Einigen Vertretern werden wir später erneut begegnen. | |||
{| | {| | ||
|- | |- | ||
| | '''CONVFMT''' | | | '''CONVFMT''' | ||
| | Steuert die Konvertierung von Zahlen in Zeichenketten. | | | Steuert die Konvertierung von Zahlen in Zeichenketten. | ||
* Die Voreinstellung »%.6g« bewirkt, dass Gleitkommazahlen mit einer Genauigkeit von bis zu 6 Ziffern konvertiert werden. | |||
Die Voreinstellung »%.6g« bewirkt, dass Gleitkommazahlen mit einer Genauigkeit von bis zu 6 Ziffern konvertiert werden. | |||
* Die zulässigen Werte sind genau jene, die auch »printf« zur Zahlenausgabe verwendet: | * Die zulässigen Werte sind genau jene, die auch »printf« zur Zahlenausgabe verwendet: | ||
$ '''awk 'BEGIN { print "Pi = " 3.1415926532; }'''' | $ '''awk 'BEGIN { print "Pi = " 3.1415926532; }'''' | ||
Pi = 3.14159 | Pi = 3.14159 | ||
Zeile 646: | Zeile 491: | ||
| | Die Feldvariable ENVIRON enthält alle Umgebungsvariablen. | | | Die Feldvariable ENVIRON enthält alle Umgebungsvariablen. | ||
* Der Index ist hierbei der Name der Umgebungsvariablen: | * Der Index ist hierbei der Name der Umgebungsvariablen: | ||
$ '''awk 'BEGIN { print ENVIRON["HOME"]; }'''' | $ '''awk 'BEGIN { print ENVIRON["HOME"]; }'''' | ||
/home/user | /home/user | ||
Zeile 652: | Zeile 496: | ||
| | '''IGNORECASE''' | | | '''IGNORECASE''' | ||
| | Steuert die Unterscheidung von Klein- und Großschreibung beim Mustervergleich. | | | Steuert die Unterscheidung von Klein- und Großschreibung beim Mustervergleich. | ||
* Steht die Variable auf »0« sind bspw. "ab" und "Ab" unterschiedliche Zeichenketten. | |||
Steht die Variable auf »0« sind bspw. "ab" und "Ab" unterschiedliche Zeichenketten. | * Bei jedem Wert ungleich »0«, spielt Klein- und Großschreibung keine Rolle. | ||
Bei jedem Wert ungleich »0«, spielt Klein- und Großschreibung keine Rolle. | |||
|- | |- | ||
| | '''OFMT''' | | | '''OFMT''' | ||
Zeile 668: | Zeile 510: | ||
| | '''SUBSEP''' | | | '''SUBSEP''' | ||
| | Das Zeichen, das in Feldvariablen die einzelnen Elemente trennt (\034). | | | Das Zeichen, das in Feldvariablen die einzelnen Elemente trennt (\034). | ||
|- | |- | ||
|} | |} | ||
=== Eigene Variablen === | === Eigene Variablen === | ||
==== Datentypen ==== | ==== Datentypen ==== | ||
Awk unterscheidet einzig (Gleitkomma-)Zahlen und Zeichenketten. | * Awk unterscheidet einzig (Gleitkomma-)Zahlen und Zeichenketten. | ||
* Von welchem Typ eine Variable ist, hängt vom aktuellen Kontext ab. | * Von welchem Typ eine Variable ist, hängt vom aktuellen Kontext ab. | ||
* Findet eine numerische Berechnung statt, werden die beteiligten Variablen als Zahlen interpretiert und bei Zeichenkettenoperationen eben als Zeichenketten. | * Findet eine numerische Berechnung statt, werden die beteiligten Variablen als Zahlen interpretiert und bei Zeichenkettenoperationen eben als Zeichenketten. | ||
* Notfalls lässt sich ein konkreter Kontext erzwingen, indem bspw. | |||
Notfalls lässt sich ein konkreter Kontext erzwingen, indem bspw. | |||
* der Wert '0' zu einer Variable addiert oder die leere Zeichenkette "" an eine solche angehangen wird. | * der Wert '0' zu einer Variable addiert oder die leere Zeichenkette "" an eine solche angehangen wird. | ||
==== Internen Konvertierungen ==== | ==== Internen Konvertierungen ==== | ||
Für den Awk-Programmierer von Interesse sind die internen Konvertierungen, die beim Vergleich von Variablen stattfinden. | * Für den Awk-Programmierer von Interesse sind die internen Konvertierungen, die beim Vergleich von Variablen stattfinden. | ||
* Zwei numerische Variablen werden als Zahlen verglichen, genauso wie zwei Zeichenkettenvariablen als Zeichenketten verglichen werden. | |||
Zwei numerische Variablen werden als Zahlen verglichen, genauso wie zwei Zeichenkettenvariablen als Zeichenketten verglichen werden. | * Ist eine Variable eine Zahl und die andere eine numerische Zeichenkette, so wird die Zeichenkette in eine Zahl konvertiert. | ||
* Handelt es sich um keine numerische Zeichenkette, wird die Variable mit der Zahl in eine Zeichenkette gewandelt und nachfolgend ein Vergleich der Zeichenketten vorgenommen. | |||
Ist eine Variable eine Zahl und die andere eine numerische Zeichenkette, so wird die Zeichenkette in eine Zahl konvertiert. | |||
Handelt es sich um keine numerische Zeichenkette, wird die Variable mit der Zahl in eine Zeichenkette gewandelt und nachfolgend ein Vergleich der Zeichenketten vorgenommen. | |||
==== Variablennamen ==== | ==== Variablennamen ==== | ||
Variablennamen in Awk dürfen aus Buchstaben, Ziffern und dem Unterstrich bestehen, wobei zum Beginn keine Ziffer stehen darf. | * Variablennamen in Awk dürfen aus Buchstaben, Ziffern und dem Unterstrich bestehen, wobei zum Beginn keine Ziffer stehen darf. | ||
* Klein- und Großschreibung werden unterschieden, sodass bspw. | |||
Klein- und Großschreibung werden unterschieden, sodass bspw. | |||
* Variable, variable, und VARIABLE unterschiedliche Bezeichner sind. | * Variable, variable, und VARIABLE unterschiedliche Bezeichner sind. | ||
* Eine Variable wird definiert, indem sie benannt und ihr ein Wert zugewiesen wird. | |||
Eine Variable wird definiert, indem sie benannt und ihr ein Wert zugewiesen wird. | |||
* Die in anderen Programmiersprachen üblichen Typbezeichner kennt Awk nicht. | * Die in anderen Programmiersprachen üblichen Typbezeichner kennt Awk nicht. | ||
* Eine Variable kann auch nur deklariert werden, awk initialisiert sie dann selbsttätig als leeren Zeichenkette. | |||
Eine Variable kann auch nur deklariert werden, awk initialisiert sie dann selbsttätig als leeren Zeichenkette. | |||
== Operatoren == | == Operatoren == | ||
Awk kennt nahezu das komplette Repertoire an Operatoren, die die Programmiersprachen zur Manipulation von Ausdrücken zur Verfügung stellen. | * Awk kennt nahezu das komplette Repertoire an Operatoren, die die Programmiersprachen zur Manipulation von Ausdrücken zur Verfügung stellen. | ||
=== Arithmetische Operatoren === | === Arithmetische Operatoren === | ||
Als arithmetische Operatoren beinhaltet Awk: | * Als arithmetische Operatoren beinhaltet Awk: | ||
{| | {| | ||
|- | |- | ||
Zeile 714: | Zeile 543: | ||
| | Subtraktion | | | Subtraktion | ||
|- | |- | ||
| | ''' | | | '''*''' | ||
| | Multiplikation | | | Multiplikation | ||
|- | |- | ||
Zeile 726: | Zeile 555: | ||
| | Potenzieren (Posix) | | | Potenzieren (Posix) | ||
|- | |- | ||
| | ''' | | | '''**''' | ||
| | Potenzieren (gawk u.a.) | | | Potenzieren (gawk u.a.) | ||
|- | |- | ||
|} | |} | ||
Da letzterer Potenzoperator (**) nicht im POSIX-Standard enthalten ist, muss eine konkrete Awk-Implementierung diesen nicht zwangläufig verstehen. | * Da letzterer Potenzoperator (**) nicht im POSIX-Standard enthalten ist, muss eine konkrete Awk-Implementierung diesen nicht zwangläufig verstehen. | ||
* Verwenden Sie daher stets »^«, um Ihre Awk-Skripte portabel zu halten. | * Verwenden Sie daher stets »^«, um Ihre Awk-Skripte portabel zu halten. | ||
* Im Falle der Division sollten Sie prüfen, dass der Divisor nicht 0 wird, da Awk sonst mit einem Ausnahmefehler das Programm abbricht: | |||
Im Falle der Division sollten Sie prüfen, dass der Divisor nicht 0 wird, da Awk sonst mit einem Ausnahmefehler das Programm abbricht: | |||
$ '''awk 'BEGIN { x=5; y; print x/y; }'''' | $ '''awk 'BEGIN { x=5; y; print x/y; }'''' | ||
awk: Kommandozeile:1: Fatal: Division durch Null wurde versucht | awk: Kommandozeile:1: Fatal: Division durch Null wurde versucht | ||
* Für arithmetische Operationen gelten die üblichen Vorrangregeln. | |||
Für arithmetische Operationen gelten die üblichen Vorrangregeln. | |||
$ '''awk 'BEGIN { print 5*4+20, 5*(4+20); }'''' | $ '''awk 'BEGIN { print 5*4+20, 5*(4+20); }'''' | ||
40 120 | 40 120 | ||
=== Zuweisungsoperatoren === | === Zuweisungsoperatoren === | ||
Das Gleichheitszeichen zur Zuweisung eines Wertes an eine Variable sollte hinreichend bekannt sein; dem C erfahrenen Programmierer sollten auch die folgenden Operatoren vertraut erscheinen: | * Das Gleichheitszeichen zur Zuweisung eines Wertes an eine Variable sollte hinreichend bekannt sein; dem C erfahrenen Programmierer sollten auch die folgenden Operatoren vertraut erscheinen: | ||
{| | {| | ||
|- | |- | ||
Zeile 779: | Zeile 602: | ||
|- | |- | ||
|} | |} | ||
Mit Ausnahme von Inkrement- und Dekrement-Operatoren bleibt es Ihnen überlassen, ob Sie die Kurzform der ausführlichen Schreibweise vorziehen. | * Mit Ausnahme von Inkrement- und Dekrement-Operatoren bleibt es Ihnen überlassen, ob Sie die Kurzform der ausführlichen Schreibweise vorziehen. | ||
* Für Inkrement bzw. | |||
Für Inkrement bzw. | |||
* Dekrement entsprechen genau genommen nur ++x und --x (Prefix-Operatoren) der Semantik der langen Darstellung, denn nur hier werden die Zuweisungen zuerst ausgeführt und erst anschließend der Wert von x evaluiert. | * Dekrement entsprechen genau genommen nur ++x und --x (Prefix-Operatoren) der Semantik der langen Darstellung, denn nur hier werden die Zuweisungen zuerst ausgeführt und erst anschließend der Wert von x evaluiert. | ||
* Als Postfix-Operatoren (x++, x--) verwendet, geht der alte Wert der Variable x in einem Ausdruck ein und erst nach Auswertung erfolgt die Zuweisung des neuen Wertes an x. | |||
Als Postfix-Operatoren (x++, x--) verwendet, geht der alte Wert der Variable x in einem Ausdruck ein und erst nach Auswertung erfolgt die Zuweisung des neuen Wertes an x. | |||
==== Beispiele ==== | ==== Beispiele ==== | ||
$ '''awk 'BEGIN { x=5; print x = x + 1, x; }'''' | $ '''awk 'BEGIN { x=5; print x = x + 1, x; }'''' | ||
Zeile 793: | Zeile 613: | ||
$ '''awk 'BEGIN { x=5; print ++x, x; }'''' | $ '''awk 'BEGIN { x=5; print ++x, x; }'''' | ||
6 6 | 6 6 | ||
* Das folgende Beispiel demonstriert, dass tatsächlich in scheinbar gleichwertigen Ausdrücken abweichende Resultate erzielt werden: | |||
Das folgende Beispiel demonstriert, dass tatsächlich in scheinbar gleichwertigen Ausdrücken abweichende Resultate erzielt werden: | |||
$ '''awk 'BEGIN { while ( ++x < 5 ) print x }'''' | $ '''awk 'BEGIN { while ( ++x < 5 ) print x }'''' | ||
1 | 1 | ||
Zeile 807: | Zeile 625: | ||
4 | 4 | ||
5 | 5 | ||
* Das nächste Beispiel zählt die Dateien im aktuellen Verzeichnis und berechnet ihren Speicherbedarf: | |||
Das nächste Beispiel zählt die Dateien im aktuellen Verzeichnis und berechnet ihren Speicherbedarf: | |||
$ '''ls -l | awk '{ ++files; sum+=$5 } END { print sum, "Bytes in", files, "Dateien"; }'''' | $ '''ls -l | awk '{ ++files; sum+=$5 } END { print sum, "Bytes in", files, "Dateien"; }'''' | ||
=== Vergleichsoperatoren === | === Vergleichsoperatoren === | ||
{| | {| | ||
Zeile 827: | Zeile 642: | ||
| | Größer als oder gleich | | | Größer als oder gleich | ||
|- | |- | ||
| | ''' | | | '''==''' | ||
| | Gleich | | | Gleich | ||
|- | |- | ||
Zeile 840: | Zeile 655: | ||
|- | |- | ||
|} | |} | ||
Der Zweck der ersten 6 Operatoren sollte leicht erkenntlich sein. | * Der Zweck der ersten 6 Operatoren sollte leicht erkenntlich sein. | ||
* Beachten Sie, dass der Test auf Gleichheit »==« anstatt »=« verwendet. | * Beachten Sie, dass der Test auf Gleichheit »==« anstatt »=« verwendet. | ||
* Eine Verwechslung wäre kein Syntaxfehler (und ist daher als Fehler schwer zu erkennen), führt aber zu abweichenden Ergebnissen! | |||
Eine Verwechslung wäre kein Syntaxfehler (und ist daher als Fehler schwer zu erkennen), führt aber zu abweichenden Ergebnissen! | * Bei den Operatoren ~ und !~ dürfen als rechtsseitige Operanden auch reguläre Ausdrücke stehen, während bei allen anderen Operatoren einzig Variablen und Konstanten zulässig sind. | ||
Bei den Operatoren ~ und !~ dürfen als rechtsseitige Operanden auch reguläre Ausdrücke stehen, während bei allen anderen Operatoren einzig Variablen und Konstanten zulässig sind. | |||
==== Beispiel ==== | ==== Beispiel ==== | ||
Finde alle Benutzer, deren Heimatverzeichnis unter /home liegt: | * Finde alle Benutzer, deren Heimatverzeichnis unter /home liegt: | ||
$ '''awk '$6 ~ /^\/home.*/ { print $1; }' /etc/passwd''' | $ '''awk '$6 ~ /^\/home.*/ { print $1; }' /etc/passwd''' | ||
user | user | ||
tux | tux | ||
=== Logische Operatoren === | === Logische Operatoren === | ||
{| | {| | ||
Zeile 867: | Zeile 677: | ||
|- | |- | ||
|} | |} | ||
Logische oder boolesche Operatoren dienen maßgeblich zur Verknüpfung mehrerer Vergleichsoperatoren oder mehrerer regulärer Ausdrücke. | * Logische oder boolesche Operatoren dienen maßgeblich zur Verknüpfung mehrerer Vergleichsoperatoren oder mehrerer regulärer Ausdrücke. | ||
* Das Beispiel listet alle Dateien des aktuellen Verzeichnisses auf, die zwischen 4097 und 8192 Bytes groß sind: | |||
Das Beispiel listet alle Dateien des aktuellen Verzeichnisses auf, die zwischen 4097 und 8192 Bytes groß sind: | |||
$ '''ls -l /etc/ | awk '$5 >= 4097 && $5 <= 8192 {print $NF; }'''' | $ '''ls -l /etc/ | awk '$5 >= 4097 && $5 <= 8192 {print $NF; }'''' | ||
bogofilter.cf | bogofilter.cf | ||
Zeile 880: | Zeile 688: | ||
my.cnf | my.cnf | ||
... | ... | ||
* Ein weiteres Beispiel veranschaulicht die Verwendung der Negation, indem im aktuellen Verzeichnis alle Dateien gesucht werden, die ''nicht'' dem aktuell angemeldeten Benutzer gehören: | |||
Ein weiteres Beispiel veranschaulicht die Verwendung der Negation, indem im aktuellen Verzeichnis alle Dateien gesucht werden, die ''nicht'' dem aktuell angemeldeten Benutzer gehören: | |||
$ '''ls -l | awk 'FNR > 1 && !($3 == ENVIRON["USER"]) {print $NF; }'''' | $ '''ls -l | awk 'FNR > 1 && !($3 == ENVIRON["USER"]) {print $NF; }'''' | ||
* Der Vergleich ist auch mit »!=« möglich. | |||
Der Vergleich ist auch mit »!=« möglich. | |||
==== Priorität der Auswertung ==== | ==== Priorität der Auswertung ==== | ||
Verknüpfen Sie mehrere Ausdrücke per logischer Operatoren, so kann die Priorität der Auswertung eine Rolle spielen:* Eine Negation wird vor dem logischen UND und dieses vor dem logischen ODER betrachtet. | * Verknüpfen Sie mehrere Ausdrücke per logischer Operatoren, so kann die Priorität der Auswertung eine Rolle spielen:* Eine Negation wird vor dem logischen UND und dieses vor dem logischen ODER betrachtet. | ||
* Nutzen Sie die Klammerung, um ggf. | * Nutzen Sie die Klammerung, um ggf. | ||
* eine andere Bearbeitungsreihenfolge zu erzwingen. | * eine andere Bearbeitungsreihenfolge zu erzwingen. | ||
== Kontrollstrukturen == | == Kontrollstrukturen == | ||
Der Mustervergleich arbeitet über die Eingabedaten und regelte, ob für den aktuellen Datensatz Aktionen durchzuführen ist. | * Der Mustervergleich arbeitet über die Eingabedaten und regelte, ob für den aktuellen Datensatz Aktionen durchzuführen ist. | ||
* Die im weiteren vorgestellten Konstrukte gestatten den Kontrollfluss innerhalb einer solchen Aktionsfolge. | * Die im weiteren vorgestellten Konstrukte gestatten den Kontrollfluss innerhalb einer solchen Aktionsfolge. | ||
=== Bedingte Ausführung === | === Bedingte Ausführung === | ||
==== if-else ==== | ==== if-else ==== | ||
Wohl jede Programmiersprache verfügt über ein if-else-Konstrukt, die Realisierung von awk orientiert sich an der C-Syntax: | * Wohl jede Programmiersprache verfügt über ein if-else-Konstrukt, die Realisierung von awk orientiert sich an der C-Syntax: | ||
* if (Bedingung) { | |||
if (Bedingung) { | # diese Aktionen | ||
} | } | ||
[ else { | [ else { | ||
# jene Aktionen | |||
} ] | } ] | ||
* Evaluiert die Bedingung zu wahr (!=0), so werden die Aktionen des '''if '''-Zweigs ausgeführt, anderenfalls wird der optionale '''else '''-Zweig betreten. | |||
Evaluiert die Bedingung zu wahr (!=0), so werden die Aktionen des '''if '''-Zweigs ausgeführt, anderenfalls wird der optionale '''else '''-Zweig betreten. | * Folgt einem Zweig nur eine einzelne Aktion, können die geschweiften Klammern entfallen. | ||
* Das nachfolgende Beispiel widerspricht eigentlich dem Einsatzzweck von Awk, bedarf es doch keinerlei Eingabedaten. | |||
Folgt einem Zweig nur eine einzelne Aktion, können die geschweiften Klammern entfallen. | |||
Das nachfolgende Beispiel widerspricht eigentlich dem Einsatzzweck von Awk, bedarf es doch keinerlei Eingabedaten. | |||
* Es testet, ob das Skript mit (mind.) einem Argument aufgerufen wurde. | * Es testet, ob das Skript mit (mind.) einem Argument aufgerufen wurde. | ||
* Fehlt dieses, endet das Skript mit einer Fehlerausgabe, anderenfalls wird der Wert des ersten Arguments potenziert: | |||
Fehlt dieses, endet das Skript mit einer Fehlerausgabe, anderenfalls wird der Wert des ersten Arguments potenziert: | |||
$ '''cat potenz.awk''' | $ '''cat potenz.awk''' | ||
#!/usr/bin/awk -f | |||
BEGIN { | BEGIN { | ||
Zeile 928: | Zeile 724: | ||
} | } | ||
} | } | ||
'''Anmerkung: '''Während der Abhandlung zu Variablen wurde erwähnt, dass nicht initialisierte Variablen intern mit »0« (numerischer Kontext) bzw. | '''Anmerkung: '''Während der Abhandlung zu Variablen wurde erwähnt, dass nicht initialisierte Variablen intern mit »0« (numerischer Kontext) bzw. | ||
* mit »""« (Zeichenkettenkontext) belegt werden. | * mit »""« (Zeichenkettenkontext) belegt werden. | ||
* Da »0« eine erlaubte Eingabe des Skripts darstellt, erzwingen wir mit dem Vergleich mit der leeren Zeichenkette letzteren Kontext. | |||
Da »0« eine erlaubte Eingabe des Skripts darstellt, erzwingen wir mit dem Vergleich mit der leeren Zeichenkette letzteren Kontext. | * Awk kennt kein zu case äquivalentes Konstrukt, sodass das zur Bewertung mehrerer Bedingungen verschachtelte if-else-Anweisungen verwendet werden: | ||
* if (Bedingung 1) { | |||
Awk kennt kein zu case äquivalentes Konstrukt, sodass das zur Bewertung mehrerer Bedingungen verschachtelte if-else-Anweisungen verwendet werden: | # Aktionen | ||
if (Bedingung 1) { | |||
} | } | ||
else if (Bedingung 2) { | else if (Bedingung 2) { | ||
# Aktionen | |||
} | } | ||
[...] | [...] | ||
else { | else { | ||
# Aktionen | |||
} | } | ||
* Bei mehreren erfüllten Bedingungen werden einzig die Aktionen der ersten zutreffenden ausgeführt. | |||
Bei mehreren erfüllten Bedingungen werden einzig die Aktionen der ersten zutreffenden ausgeführt. | |||
* Wird keine Bedingung wahr, werden alle Aktionen des optionalen else-Zweigs abgearbeitet. | * Wird keine Bedingung wahr, werden alle Aktionen des optionalen else-Zweigs abgearbeitet. | ||
==== Conditional-if ==== | ==== Conditional-if ==== | ||
Manchmal soll einer Variable in Abhängigkeit vom Ergebnis einer Bedingung ein Wert zugewiesen werden. | * Manchmal soll einer Variable in Abhängigkeit vom Ergebnis einer Bedingung ein Wert zugewiesen werden. | ||
* Mit »if-else« wäre das folgende Fragment denkbar: | * Mit »if-else« wäre das folgende Fragment denkbar: | ||
* if (Bedingung) | |||
if (Bedingung) | |||
variable = Wert_1 | variable = Wert_1 | ||
else | else | ||
variable = Wert_2 | variable = Wert_2 | ||
* Prägnanter ist die Schreibweise des Conditional-if: | |||
Prägnanter ist die Schreibweise des Conditional-if: | * variable = Bedingung ? Wert_1 :Wert_2 | ||
* Es bleibt Ihnen überlassen, welche Varianten Sie wählen. | |||
variable = Bedingung ? Wert_1 :Wert_2 | |||
Es bleibt Ihnen überlassen, welche Varianten Sie wählen. | |||
* Dir letztere Schreibweise unterstreicht deutlicher das Ansinnen, einer Variable einen Wert zuzuweisen. | * Dir letztere Schreibweise unterstreicht deutlicher das Ansinnen, einer Variable einen Wert zuzuweisen. | ||
* Da conditional-if im Gegensatz zu if-else einen Wert liefert, finden sich auch hin und wieder Situationen, wo eine Umschreibung per if-else umständlich, wenn nicht gar unmöglich ist: | |||
Da conditional-if im Gegensatz zu if-else einen Wert liefert, finden sich auch hin und wieder Situationen, wo eine Umschreibung per if-else umständlich, wenn nicht gar unmöglich ist: | |||
$ '''awk 'BEGIN { x=1; while (x <= 3) printf("%d Osterei%s\n", x, x++>1 ?"er":"") }'''' | $ '''awk 'BEGIN { x=1; while (x <= 3) printf("%d Osterei%s\n", x, x++>1 ?"er":"") }'''' | ||
1 Osterei | 1 Osterei | ||
2 Ostereier | 2 Ostereier | ||
3 Ostereier | 3 Ostereier | ||
'''printf '''wird im Abschnitt Ein/Ausgabe besprochen. | '''printf '''wird im Abschnitt Ein/Ausgabe besprochen. | ||
=== Schleifen === | === Schleifen === | ||
==== for ==== | ==== for ==== | ||
Die for-Schleife wird bevorzugt, wenn eine bekannte Anzahl Durchläufe der Anweisungen im Schleifenrumpf erfolgen soll: | * Die for-Schleife wird bevorzugt, wenn eine bekannte Anzahl Durchläufe der Anweisungen im Schleifenrumpf erfolgen soll: | ||
* for (Zähler initialisieren; Zähler testen; Zähler verändern) { | |||
for (Zähler initialisieren; Zähler testen; Zähler verändern) { | # Aktionen | ||
} | } | ||
* Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört. | |||
Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört. | * Die mit Zähler initialisieren, Zähler testen und Zähler verändern bezeichneten Ausdrücke beschreiben den »gebräuchlichen« Verwendungszweck. | ||
* Wie auch in C wird der erste Ausdruck (Zähler initialisieren) einmalig vor Eintritt in die Schleife ausgeführt. | |||
Die mit Zähler initialisieren, Zähler testen und Zähler verändern bezeichneten Ausdrücke beschreiben den »gebräuchlichen« Verwendungszweck. | * Der zweite Ausdruck (Zähler testen) wird jeweils ''vor dem nächsten'' Durchlauf der Schleife betrachtet und der dritte Ausdruck (Zähler verändern) wird nach Bearbeitung der Anweisung des Schleifenrumpfes berechnet: | ||
Wie auch in C wird der erste Ausdruck (Zähler initialisieren) einmalig vor Eintritt in die Schleife ausgeführt. | |||
Der zweite Ausdruck (Zähler testen) wird jeweils ''vor dem nächsten'' Durchlauf der Schleife betrachtet und der dritte Ausdruck (Zähler verändern) wird nach Bearbeitung der Anweisung des Schleifenrumpfes berechnet: | |||
$ '''awk 'BEGIN {for ( i=0; i<3; i++) print i;}'''' | $ '''awk 'BEGIN {for ( i=0; i<3; i++) print i;}'''' | ||
0 | 0 | ||
Zeile 999: | Zeile 775: | ||
2 | 2 | ||
3 | 3 | ||
* Die Beispiele zeigen auch, dass die Angabe der Ausdrücke optional ist. | |||
Die Beispiele zeigen auch, dass die Angabe der Ausdrücke optional ist. | * Analog zu C kann eine Endlosschleife wie folgt angegeben werden: | ||
Analog zu C kann eine Endlosschleife wie folgt angegeben werden: | |||
$ '''awk 'BEGIN {for (;;) printf(".") }'''' | $ '''awk 'BEGIN {for (;;) printf(".") }'''' | ||
* Was Awk im Gegensatz zu C allerdings nicht gestattet, ist die Angabe einer kommaseparierten Liste von Ausdrücken (»for (i=0,j=0;...;...)«). | |||
Was Awk im Gegensatz zu C allerdings nicht gestattet, ist die Angabe einer kommaseparierten Liste von Ausdrücken (»for (i=0,j=0;...;...)«). | |||
==== while ==== | ==== while ==== | ||
'''while '''-Schleifen werden bevorzugt, wenn die Anzahl der Schleifendurchläufe vom Ergebnis eines Ausdrucks abhängt. | '''while '''-Schleifen werden bevorzugt, wenn die Anzahl der Schleifendurchläufe vom Ergebnis eines Ausdrucks abhängt. | ||
* while (Bedingung) { | |||
while (Bedingung) { | # Aktionen | ||
} | } | ||
* Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört. | |||
Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört. | |||
* Das folgende Beispiel berechnet die Fakultät zu einer per Argument übergebenen Zahl: | * Das folgende Beispiel berechnet die Fakultät zu einer per Argument übergebenen Zahl: | ||
$ '''cat fakultaet.awk''' | $ '''cat fakultaet.awk''' | ||
#!/usr/bin/awk -f | |||
BEGIN { | BEGIN { | ||
Zeile 1.037: | Zeile 805: | ||
$ '''./fakultaet.awk 5''' | $ '''./fakultaet.awk 5''' | ||
Fakultaet von 5 ist 120 | Fakultaet von 5 ist 120 | ||
==== do-while ==== | ==== do-while ==== | ||
In Situationen, in denen ein Programmabschnitt ''mindestens einmal'' - und in Abhängigkeit von einer Bedingung weitere Male - zu durchlaufen ist, bietet sich die '''do-while '''-Schleife an: | * In Situationen, in denen ein Programmabschnitt ''mindestens einmal'' - und in Abhängigkeit von einer Bedingung weitere Male - zu durchlaufen ist, bietet sich die '''do-while '''-Schleife an: | ||
* do { | |||
do { | # Aktionen | ||
} while (Bedingung) | } while (Bedingung) | ||
* Eine Anwendung könnte der Test einer Nutzereingabe sein, die solange zu wiederholen ist, bis sie dem geforderten Format entspricht: | |||
Eine Anwendung könnte der Test einer Nutzereingabe sein, die solange zu wiederholen ist, bis sie dem geforderten Format entspricht: | * do { | ||
do { | |||
printf("Geben Sie eine Zahl ein: "); | printf("Geben Sie eine Zahl ein: "); | ||
getline zahl < "-"; | getline zahl < "-"; | ||
} while ( zahl !~ /^digit:+$/ ) | } while ( zahl !~ /^digit:+$/ ) | ||
'''getline '''wird im Abschnitt Ein/Ausgabe behandelt. | '''getline '''wird im Abschnitt Ein/Ausgabe behandelt. | ||
=== Weitere Kontrollanweisungen === | === Weitere Kontrollanweisungen === | ||
Betrachtet man das Hauptprogramm von Awk als eine Schleife (die über alle Zeilen der Eingabe läuft), so besitzen die nachfolgend vorgestellten Anweisungen eine Gemeinsamkeit: Sie verändern den Kontrollfluss von Schleifen.* '''break '''und '''continue '''wirken mit '''for '''und '''[do-]while '''zusammen | * Betrachtet man das Hauptprogramm von Awk als eine Schleife (die über alle Zeilen der Eingabe läuft), so besitzen die nachfolgend vorgestellten Anweisungen eine Gemeinsamkeit: Sie verändern den Kontrollfluss von Schleifen.* '''break '''und '''continue '''wirken mit '''for '''und '''[do-]while '''zusammen | ||
* '''exit, next '''und '''nextfile '''betreffen die Hauptschleife. | * '''exit, next '''und '''nextfile '''betreffen die Hauptschleife. | ||
==== break ==== | ==== break ==== | ||
Mit '''break '''kann eine for-, while- oder do-while-Schleife vorzeitig verlassen werden. | * Mit '''break '''kann eine for-, while- oder do-while-Schleife vorzeitig verlassen werden. | ||
* Die Programmausführung fährt mit der der Schleife unmittelbar folgenden Anweisung fort. | |||
Die Programmausführung fährt mit der der Schleife unmittelbar folgenden Anweisung fort. | |||
$ '''awk 'BEGIN { for (;;) if (x++ == 5) break; print x}'''' | $ '''awk 'BEGIN { for (;;) if (x++ == 5) break; print x}'''' | ||
6 | 6 | ||
==== continue ==== | ==== continue ==== | ||
Wird '''continue '''innerhalb einer for-, while- oder do-while-Schleife ausgeführt, wird die Bearbeitung des aktuellen Schleifendurchlaufs abgebrochen und der nächste Durchlauf begonnen. | * Wird '''continue '''innerhalb einer for-, while- oder do-while-Schleife ausgeführt, wird die Bearbeitung des aktuellen Schleifendurchlaufs abgebrochen und der nächste Durchlauf begonnen. | ||
$ '''awk 'BEGIN { for (i=1;i<10;i++) {if (i%2) continue; print i } }'''' | $ '''awk 'BEGIN { for (i=1;i<10;i++) {if (i%2) continue; print i } }'''' | ||
2 | 2 | ||
Zeile 1.074: | Zeile 831: | ||
6 | 6 | ||
8 | 8 | ||
==== exit ==== | ==== exit ==== | ||
Der '''exit '''-Befehl dient zum (vorzeitigen) Beenden der Hauptschleife von Awk. | * Der '''exit '''-Befehl dient zum (vorzeitigen) Beenden der Hauptschleife von Awk. | ||
* Das Programm wird unverzüglich mit Ausführung des optionalen END-Statements fortgeführt. | * Das Programm wird unverzüglich mit Ausführung des optionalen END-Statements fortgeführt. | ||
* Als Argument kann '''exit '''ein Ausdruck mitgegeben werden. | |||
Als Argument kann '''exit '''ein Ausdruck mitgegeben werden. | |||
* Der Wert des Ausdrucks ist der Rückgabewert von Awk. | * Der Wert des Ausdrucks ist der Rückgabewert von Awk. | ||
* Ohne Angabe des Arguments wird implizit 0 angenommen. | * Ohne Angabe des Arguments wird implizit 0 angenommen. | ||
* Eine diesbezügliche Ausnahme bildet die Verwendung von '''exit '''innerhalb der END-Anweisungen. | |||
Eine diesbezügliche Ausnahme bildet die Verwendung von '''exit '''innerhalb der END-Anweisungen. | |||
* Fehlt hier das Argument, gilt ein in der Hauptschleife (bzw. | * Fehlt hier das Argument, gilt ein in der Hauptschleife (bzw. | ||
* innerhalb von BEGIN) gesetzter Wert: | * innerhalb von BEGIN) gesetzter Wert: | ||
$ '''awk 'BEGIN { exit 3 }; END { exit }'; echo $?''' | $ '''awk 'BEGIN { exit 3 }; END { exit }'; echo $?''' | ||
3 | 3 | ||
$ '''awk 'BEGIN { exit 3 }; END { exit 0 }'; echo $?''' | $ '''awk 'BEGIN { exit 3 }; END { exit 0 }'; echo $?''' | ||
0 | 0 | ||
==== next ==== | ==== next ==== | ||
'''next '''wirkt wie '''continue, '''nur dass jetzt der aktuelle Durchlauf des Hauptprogramms unterbrochen und mit dem nächsten Durchlauf - sprich: mit der nächsten Zeile der Eingabe - fortgefahren wird. | '''next '''wirkt wie '''continue, '''nur dass jetzt der aktuelle Durchlauf des Hauptprogramms unterbrochen und mit dem nächsten Durchlauf - sprich: mit der nächsten Zeile der Eingabe - fortgefahren wird. | ||
==== nextfile ==== | ==== nextfile ==== | ||
Diese Erweiterung von GNU-Awk (nicht POSIX) wirkt wie next, d.h. | * Diese Erweiterung von GNU-Awk (nicht POSIX) wirkt wie next, d.h. | ||
* es wird zum Beginn des Hauptprogramms gesprungen. | * es wird zum Beginn des Hauptprogramms gesprungen. | ||
* Die aktuelle Datei wird geschlossen und als Datensatz die erste Zeile der nächsten Eingabedatei geladen. | |||
Die aktuelle Datei wird geschlossen und als Datensatz die erste Zeile der nächsten Eingabedatei geladen. | |||
== Ein- und Ausgabe == | == Ein- und Ausgabe == | ||
=== Eingabe === | === Eingabe === | ||
Das Erfassen von Nutzereingaben ist nur eine Anwendungsvariante der Funktion '''getline.''' | * Das Erfassen von Nutzereingaben ist nur eine Anwendungsvariante der Funktion '''getline.''' | ||
* Und genau genommen vermag sie das auch nur, weil ihr die Standardeingabe als Quelldatei untergeschoben werden kann. | |||
Und genau genommen vermag sie das auch nur, weil ihr die Standardeingabe als Quelldatei untergeschoben werden kann. | |||
'''getline''' [variable] [< "<Datei>"] | '''getline''' [variable] [< "<Datei>"] | ||
<Kommando> | '''getline''' [variable] | <Kommando> | '''getline''' [variable] | ||
* Im einfachsten Fall des Funktionsaufrufs ohne Argumente lädt '''getline '''einfach die nächste Eingabezeile. | |||
Im einfachsten Fall des Funktionsaufrufs ohne Argumente lädt '''getline '''einfach die nächste Eingabezeile. | |||
* Alle im Skript folgenden Anweisungen arbeiten folglich mit diesem neuen Datensatz. | * Alle im Skript folgenden Anweisungen arbeiten folglich mit diesem neuen Datensatz. | ||
* In dieser Form manipuliert '''getline '''die Awk-Variablen $0, NR, NF und FNR. | |||
In dieser Form manipuliert '''getline '''die Awk-Variablen $0, NR, NF und FNR. | |||
* Folgt dem Funktionsaufruf eine Variable, so landet der Inhalt der Eingabe in dieser. | * Folgt dem Funktionsaufruf eine Variable, so landet der Inhalt der Eingabe in dieser. | ||
* Der Arbeitspuffer mit dem aktuellen Datensatz ($0) wird hierbei nicht verändert, jedoch werden die gelesenen Daten aus der Eingabe entfernt. | |||
Der Arbeitspuffer mit dem aktuellen Datensatz ($0) wird hierbei nicht verändert, jedoch werden die gelesenen Daten aus der Eingabe entfernt. | * Das folgende Beispiel nutzt das Verhalten, um jeweils zwei Zeilen der Eingabe zu einer Zeile in der Ausgabe zusammenzufassen: | ||
Das folgende Beispiel nutzt das Verhalten, um jeweils zwei Zeilen der Eingabe zu einer Zeile in der Ausgabe zusammenzufassen: | |||
$ '''cat 2to1.awk''' | $ '''cat 2to1.awk''' | ||
#!/usr/bin/awk -f | |||
{ | { | ||
if ( (getline nextLine) < 1) | if ( (getline nextLine) < 1) | ||
Zeile 1.128: | Zeile 870: | ||
print $0, nextLine; | print $0, nextLine; | ||
} | } | ||
'''getline '''liefert bei erfolgreich gelesener Eingabe eine »1« zurück. | '''getline '''liefert bei erfolgreich gelesener Eingabe eine »1« zurück. | ||
* Das Beispiel nutzt den Rückgabewert, um die Variable zurückzusetzen, falls in der Eingabe eine ungerade Anzahl Datensätze steht. | |||
Das Beispiel nutzt den Rückgabewert, um die Variable zurückzusetzen, falls in der Eingabe eine ungerade Anzahl Datensätze steht. | * Eine »-1« liefert '''getline '''im Fehlerfall; eine »0« bei Erreichen des Dateiendes. | ||
Eine »-1« liefert '''getline '''im Fehlerfall; eine »0« bei Erreichen des Dateiendes. | |||
'''getline '''in Verbindung mit < "<Datei>" versucht den nächsten Datensatz aus der Datei zu lesen. | '''getline '''in Verbindung mit < "<Datei>" versucht den nächsten Datensatz aus der Datei zu lesen. | ||
* Dieser steht in $0 zur Verfügung, FN enthält die Anzahl der Felder dieses Dateisatzes. | * Dieser steht in $0 zur Verfügung, FN enthält die Anzahl der Felder dieses Dateisatzes. | ||
* Mit jedem Aufruf wird ein weiterer Datensatz aus der Datei geliefert. | |||
Mit jedem Aufruf wird ein weiterer Datensatz aus der Datei geliefert. | |||
* Um von der Standardeingabe zu lesen, ist als Dateiname »-« anzugeben: | * Um von der Standardeingabe zu lesen, ist als Dateiname »-« anzugeben: | ||
$ '''awk 'BEGIN {getline < "-"; print "Die Eingabe war ", $0; }'''' | $ '''awk 'BEGIN {getline < "-"; print "Die Eingabe war ", $0; }'''' | ||
'''Anmerkung''': Speziell in gawk steht der Dateiname »/dev/stdin« zur Verfügung, der anstatt dem Minus verwendet werden kann. | '''Anmerkung''': Speziell in gawk steht der Dateiname »/dev/stdin« zur Verfügung, der anstatt dem Minus verwendet werden kann. | ||
* Als weitere Quelle kann '''getline '''seine Eingaben auch aus einer Pipe beziehen. | |||
Als weitere Quelle kann '''getline '''seine Eingaben auch aus einer Pipe beziehen. | |||
* Auf der linken Seite der Pipe muss ein Kommando stehen, das in doppelte Anführungszeichen einzuschließen ist: | * Auf der linken Seite der Pipe muss ein Kommando stehen, das in doppelte Anführungszeichen einzuschließen ist: | ||
$ '''awk 'BEGIN {"date" | getline datum; close("date"); print "Aktuelles Datum: ", datum;}'''' | $ '''awk 'BEGIN {"date" | getline datum; close("date"); print "Aktuelles Datum: ", datum;}'''' | ||
Aktuelles Datum: Mit Dez 12 21:42:35 CET 2001 | Aktuelles Datum: Mit Dez 12 21:42:35 CET 2001 | ||
'''close '''wird im Anschluss behandelt. | '''close '''wird im Anschluss behandelt. | ||
=== Escape-Sequenzen === | === Escape-Sequenzen === | ||
Escape-Sequenzen sind nicht auf die Verwendung in Ausgaben beschränkt, werden aber in diesem Zusammenhang häufig genutzt. | * Escape-Sequenzen sind nicht auf die Verwendung in Ausgaben beschränkt, werden aber in diesem Zusammenhang häufig genutzt. | ||
{| | {| | ||
|- | |- | ||
Zeile 1.196: | Zeile 926: | ||
einem | einem | ||
Treppeneffekt:) | Treppeneffekt:) | ||
=== Ausgabe mit print === | === Ausgabe mit print === | ||
'''print '''[Parameter [, Parameter] ] | '''print '''[Parameter [, Parameter] ] | ||
'''print '''([Parameter [, Parameter] ]) | '''print '''([Parameter [, Parameter] ]) | ||
'''print '''ist für die Ausgabe zu bevorzugen, wenn Sie keine Anforderungen an die Formatierung stellen. | '''print '''ist für die Ausgabe zu bevorzugen, wenn Sie keine Anforderungen an die Formatierung stellen. | ||
* Geben Sie hierzu einfach die Liste der auszugebenden Ausdrücke, jeweils getrennt durch ein Komma, an. | * Geben Sie hierzu einfach die Liste der auszugebenden Ausdrücke, jeweils getrennt durch ein Komma, an. | ||
* Jedes Element wird in der Ausgabe vom Folgenden durch ein Leerzeichen getrennt werden. | |||
Jedes Element wird in der Ausgabe vom Folgenden durch ein Leerzeichen getrennt werden. | |||
* Dem letzten Element lässt '''print '''einen Zeilenumbruch folgen (das voreingestellte Verhalten kann mittels der Variablen OFS und ORS geändert werden) | * Dem letzten Element lässt '''print '''einen Zeilenumbruch folgen (das voreingestellte Verhalten kann mittels der Variablen OFS und ORS geändert werden) | ||
* Sie können gar auf jegliche Elemente verzichten. | |||
Sie können gar auf jegliche Elemente verzichten. | |||
* In dem Fall gibt '''print '''die aktuell bearbeitet Zeile aus, d.h. | * In dem Fall gibt '''print '''die aktuell bearbeitet Zeile aus, d.h. | ||
* zwischen »print« und »print $0« besteht bez. | * zwischen »print« und »print $0« besteht bez. | ||
* des Ergebnisses kein Unterschied. | * des Ergebnisses kein Unterschied. | ||
* Die an '''print '''zu übergebenden Argumente können wahlweise in runde Klammern eingeschlossen werden. | |||
Die an '''print '''zu übergebenden Argumente können wahlweise in runde Klammern eingeschlossen werden. | |||
* Notwendig ist dies, wenn die Ausdrücke den Vergleichsoperator »>« enthalten, da dieser sonst als Ausgabeumleitung verstanden wird. | * Notwendig ist dies, wenn die Ausdrücke den Vergleichsoperator »>« enthalten, da dieser sonst als Ausgabeumleitung verstanden wird. | ||
* Es ist kein Syntaxfehler, wenn Sie auf die Kommata zwischen den auszugebenden Ausdrücken verzichten. | |||
Es ist kein Syntaxfehler, wenn Sie auf die Kommata zwischen den auszugebenden Ausdrücken verzichten. | |||
* Awk interpretiert dies als das Zusammenhängen von Zeichenketten, sodass als ersichtlicher Unterschied zumeist das trennende Leerzeichen fehlt: | * Awk interpretiert dies als das Zusammenhängen von Zeichenketten, sodass als ersichtlicher Unterschied zumeist das trennende Leerzeichen fehlt: | ||
$ '''awk 'BEGIN { print "foo", "bar";}'''' | $ '''awk 'BEGIN { print "foo", "bar";}'''' | ||
foo bar | foo bar | ||
$ '''awk 'BEGIN { print "foo" "bar";}'''' | $ '''awk 'BEGIN { print "foo" "bar";}'''' | ||
foobar | foobar | ||
* Eine begrenzte Formatierung lässt sich für numerische Werte erzwingen, indem das in OFMT gespeicherte Ausgabeformat (Voreinstellung "%.6g") geändert wird: | |||
Eine begrenzte Formatierung lässt sich für numerische Werte erzwingen, indem das in OFMT gespeicherte Ausgabeformat (Voreinstellung "%.6g") geändert wird: | |||
$ '''awk 'BEGIN { print rand();}'''' | $ '''awk 'BEGIN { print rand();}'''' | ||
0.487477 | 0.487477 | ||
Zeile 1.231: | Zeile 952: | ||
$ '''awk 'BEGIN { OFMT="%.1f"; print rand();}'''' | $ '''awk 'BEGIN { OFMT="%.1f"; print rand();}'''' | ||
0.5 | 0.5 | ||
* Beachten Sie die enthaltene mathematisch korrekte Rundung der Werte! | |||
Beachten Sie die enthaltene mathematisch korrekte Rundung der Werte! | |||
=== Formatierte Ausgabe mit printf === | === Formatierte Ausgabe mit printf === | ||
Die Syntax von '''printf '''kann ihre Anlehnung an die verbreitete Sprache nicht verbergen. | * Die Syntax von '''printf '''kann ihre Anlehnung an die verbreitete Sprache nicht verbergen. | ||
'''printf''' (Formatzeichenkette[, Parameter(liste)]) | '''printf''' (Formatzeichenkette[, Parameter(liste)]) | ||
* Zwar dürfen in Awk die runden Klammern auch entfallen, aber das ist fast schon der einzige Unterschied zu C. | |||
Zwar dürfen in Awk die runden Klammern auch entfallen, aber das ist fast schon der einzige Unterschied zu C. | |||
* Die Formatzeichenkette kann jedes ASCII-Zeichen, Escape-Sequenzen oder einen der folgenden Platzhalter umfassen: | * Die Formatzeichenkette kann jedes ASCII-Zeichen, Escape-Sequenzen oder einen der folgenden Platzhalter umfassen: | ||
{| | {| | ||
|- | |- | ||
Zeile 1.284: | Zeile 1.000: | ||
|- | |- | ||
|} | |} | ||
Die Anzahl der Elemente der Parameterliste muss mindestens die Anzahl Platzhalter in der Formatzeichenkette umfassen. Überschüssige Angaben werden schlicht ignoriert. | * Die Anzahl der Elemente der Parameterliste muss mindestens die Anzahl Platzhalter in der Formatzeichenkette umfassen. Überschüssige Angaben werden schlicht ignoriert. | ||
* Der Typ des i-ten Parameters sollte zum i-ten Platzhalter »passen«, allerdings reagiert Awk recht großzügig und wandelt die Typen ggf. | |||
Der Typ des i-ten Parameters sollte zum i-ten Platzhalter »passen«, allerdings reagiert Awk recht großzügig und wandelt die Typen ggf. | |||
* ineinander um. | * ineinander um. | ||
* Meist weicht das Resultat aber vom Gewünschten ab. | * Meist weicht das Resultat aber vom Gewünschten ab. | ||
$ '''awk 'BEGIN { printf("%i\n", 11.100); }'''' | $ '''awk 'BEGIN { printf("%i\n", 11.100); }'''' | ||
11 | 11 | ||
Zeile 1.301: | Zeile 1.015: | ||
$ '''awk 'BEGIN { printf("%s\t%s\n", 11.100, "11.100"); }'''' | $ '''awk 'BEGIN { printf("%s\t%s\n", 11.100, "11.100"); }'''' | ||
11.1 11.100 | 11.1 11.100 | ||
* Eine exakte Positionierung und angepasstes Format der auszugebenden Parameter wäre mit den Platzhaltern allein nicht immer möglich, daher gestattet '''printf '''- C lässt grüßen - die Angabe von Breite und Ausrichtung eines Parameters sowie eines Modifizierers. | |||
Eine exakte Positionierung und angepasstes Format der auszugebenden Parameter wäre mit den Platzhaltern allein nicht immer möglich, daher gestattet '''printf '''- C lässt grüßen - die Angabe von Breite und Ausrichtung eines Parameters sowie eines Modifizierers. | * Für Gleitkommaangaben kommt noch die Genauigkeit der Nachkommastellen hinzu. | ||
Für Gleitkommaangaben kommt noch die Genauigkeit der Nachkommastellen hinzu. | |||
* Die optionalen Angaben stehen zwischen Prozentzeichen und Formatidentifikator: | * Die optionalen Angaben stehen zwischen Prozentzeichen und Formatidentifikator: | ||
%ModifiziererBreite.GenauigkeitFormatidentifikator | %ModifiziererBreite.GenauigkeitFormatidentifikator | ||
* Der wohl gebräuchlichste Modifizierer beeinflusst die horizontale Anordnung eines Parameters. | |||
Der wohl gebräuchlichste Modifizierer beeinflusst die horizontale Anordnung eines Parameters. | |||
* In der Voreinstellung rechtsbündig, erzwingt ein dem Prozentzeichen folgendes Minus die linksbündige Ausrichtung: | * In der Voreinstellung rechtsbündig, erzwingt ein dem Prozentzeichen folgendes Minus die linksbündige Ausrichtung: | ||
$ '''awk 'BEGIN { for (i=0; i<6; i++) printf("%4i\n", i**i) }'''' | $ '''awk 'BEGIN { for (i=0; i<6; i++) printf("%4i\n", i**i) }'''' | ||
1 | 1 | ||
Zeile 1.326: | Zeile 1.035: | ||
256 | 256 | ||
3125 | 3125 | ||
* Als Modifizierer stehen zur Verfügung: | |||
Als Modifizierer stehen zur Verfügung: | |||
{| | {| | ||
|- | |- | ||
Zeile 1.343: | Zeile 1.050: | ||
| | Nummerische Werte werden von links her mit Nullen aufgefüllt | | | Nummerische Werte werden von links her mit Nullen aufgefüllt | ||
|- | |- | ||
| | ''' | | | '''#''' | ||
| | Druckt bspw. | | | Druckt bspw. | ||
|- | |- | ||
|} | |} | ||
bei "%x" ein "0x" vor den Wert | * bei "%x" ein "0x" vor den Wert | ||
$ '''awk 'BEGIN { printf ("%#x %04i %+4i\n", 12, 12, 12)}'''' | $ '''awk 'BEGIN { printf ("%#x %04i %+4i\n", 12, 12, 12)}'''' | ||
0xc 0012 +12 | 0xc 0012 +12 | ||
* Von C's printf hat Awk ebenso die variable Angabe der Breiten- und Genauigkeitswerte übernommen. | |||
Von C's printf hat Awk ebenso die variable Angabe der Breiten- und Genauigkeitswerte übernommen. | |||
* Anstatt des Wertes steht im Formatierer nun ein Stern. | * Anstatt des Wertes steht im Formatierer nun ein Stern. | ||
* Der zu wählende Wert erscheint in der Argumentenliste vor dem jeweiligen Argument (Reihenfolge und Anzahl sollte übereinstimmen!): | |||
Der zu wählende Wert erscheint in der Argumentenliste vor dem jeweiligen Argument (Reihenfolge und Anzahl sollte übereinstimmen!): | |||
==== Die »übliche« Form: ==== | ==== Die »übliche« Form: ==== | ||
$ '''awk 'BEGIN { printf("%12.11f\n", 11/13 ) }'''' | $ '''awk 'BEGIN { printf("%12.11f\n", 11/13 ) }'''' | ||
==== Die »dynamische« Form: ==== | ==== Die »dynamische« Form: ==== | ||
$ '''awk 'BEGIN {width=12; precision=11; printf("%*.*f\n", width, precision, 53/17 ) }'''' | $ '''awk 'BEGIN {width=12; precision=11; printf("%*.*f\n", width, precision, 53/17 ) }'''' | ||
0.84615384615 | 0.84615384615 | ||
=== Umleitung der Ausgabe === | === Umleitung der Ausgabe === | ||
Die nachfolgend am Beispiel von '''print '''demonstrierten Mechanismen funktionieren ebenso mit '''printf.''' | * Die nachfolgend am Beispiel von '''print '''demonstrierten Mechanismen funktionieren ebenso mit '''printf.''' | ||
* Normalerweise landen die Ausgaben auf dem Terminal (Standardausgabe). | |||
Normalerweise landen die Ausgaben auf dem Terminal (Standardausgabe). | * Ihr Ziel kann mittels der schon von den Shells bekannten Umleitungen ebenso eine Datei oder eine Pipe sein. | ||
Ihr Ziel kann mittels der schon von den Shells bekannten Umleitungen ebenso eine Datei oder eine Pipe sein. | |||
'''print Argument > Ausgabedatei ''' | '''print Argument > Ausgabedatei ''' | ||
<div >Schreiben in Datei; existiert diese, wird ihr Inhalt überschrieben </div> | <div >Schreiben in Datei; existiert diese, wird ihr Inhalt überschrieben </div> | ||
'''print Argument >> Ausgabedatei ''' | '''print Argument >> Ausgabedatei ''' | ||
<div >Anfügen ans Ende der Datei; existiert sie nicht, wird sie erzeugt </div> | <div >Anfügen ans Ende der Datei; existiert sie nicht, wird sie erzeugt </div> | ||
'''print Argument | Kommando ''' | '''print Argument | Kommando ''' | ||
<div >Schreiben in eine Pipe, aus der ein Kommando liest </div> | <div >Schreiben in eine Pipe, aus der ein Kommando liest </div> | ||
* Anwendungsbeispiele für den Nutzen der Umleitung von Ausgaben in einer Datei finden sich reichlich in der alltäglichen Administration, z.B. | |||
Anwendungsbeispiele für den Nutzen der Umleitung von Ausgaben in einer Datei finden sich reichlich in der alltäglichen Administration, z.B. | |||
* bei der Auswertung der Logdateien. | * bei der Auswertung der Logdateien. | ||
* Gerade bei Servern häufen sich die Meldungen in der Datei /var/log/messages nur allzu schnell. | |||
Gerade bei Servern häufen sich die Meldungen in der Datei /var/log/messages nur allzu schnell. | |||
* Per Hand regelmäßig nach verdächtigen Zeilen Ausschau zu halten, wird rasch zur Last. | * Per Hand regelmäßig nach verdächtigen Zeilen Ausschau zu halten, wird rasch zur Last. | ||
* So könnte Awk eine vorherige Selektion treffen und thematische Logdateien anlegen. | |||
So könnte Awk eine vorherige Selektion treffen und thematische Logdateien anlegen. | |||
* Das folgende Beispiel zeigt eine Anwendung, die die Nachrichten des sshd und des telnetd filtert: | * Das folgende Beispiel zeigt eine Anwendung, die die Nachrichten des sshd und des telnetd filtert: | ||
#!/usr/bin/awk -f | |||
# 'filter.awk' filtert Meldungen des sshd und telnetd | |||
# Aufruf: filter.awk /var/log/messages | |||
/.*sshd.*/ { print >> ENVIRON["HOME"]"/sshd.log"} | /.*sshd.*/ { print >> ENVIRON["HOME"]"/sshd.log"} | ||
/.*in.telnetd.*/ { print >> ENVIRON["HOME"]"/telnet.log"} | /.*in.telnetd.*/ { print >> ENVIRON["HOME"]"/telnet.log"} | ||
* Verwenden Sie in einem der Argumente von print(f) den Vergleichsoperator »>«, so müssen Sie die Argumente klammern, da der Operator ansonsten als Umleitung verstanden wird! | |||
Verwenden Sie in einem der Argumente von print(f) den Vergleichsoperator »>«, so müssen Sie die Argumente klammern, da der Operator ansonsten als Umleitung verstanden wird! | * Die Ausgabe von print(f) in eine Pipe zu speisen, bietet sich an, falls das Ergebnis durch ein Kommando weiter bearbeitet werden soll. | ||
* Schließen Sie dazu das Kommando inklusive seiner Argumente in doppelte Anführungszeichen ein. | |||
Die Ausgabe von print(f) in eine Pipe zu speisen, bietet sich an, falls das Ergebnis durch ein Kommando weiter bearbeitet werden soll. | |||
Schließen Sie dazu das Kommando inklusive seiner Argumente in doppelte Anführungszeichen ein. | |||
* Als Beispiel werden Funde von telnet-Nachrichten sofort per Mail an den lokalen Administrator gemeldet: | * Als Beispiel werden Funde von telnet-Nachrichten sofort per Mail an den lokalen Administrator gemeldet: | ||
#!/usr/bin/awk -f | |||
# 'mailer.awk' meldet Nachrichten des telnetd an Root | |||
# Aufruf: mailer.awk /var/log/messages | |||
/.*in.telnetd.*/ { print | "mail -s 'Telnet-Kontakt' root" } | /.*in.telnetd.*/ { print | "mail -s 'Telnet-Kontakt' root" } | ||
* Derartige Kommandoaufrufe lassen sich auch in einer Variable speichern und darüber referenzieren: | |||
Derartige Kommandoaufrufe lassen sich auch in einer Variable speichern und darüber referenzieren: | |||
... | ... | ||
BEFEHL="mail -s 'Telnet-Kontakt' root" | BEFEHL="mail -s 'Telnet-Kontakt' root" | ||
... | ... | ||
/.*in.telnetd.*/ { print | BEFEHL } | /.*in.telnetd.*/ { print | BEFEHL } | ||
=== Spezielle Dateinamen === | === Spezielle Dateinamen === | ||
Analog zu den Dateideskriptoren der Shells kennt GNU-Awk (nicht POSIX!) spezielle Dateinamen, um die Umleitung in konkrete Kanäle zu realisieren. | * Analog zu den Dateideskriptoren der Shells kennt GNU-Awk (nicht POSIX!) spezielle Dateinamen, um die Umleitung in konkrete Kanäle zu realisieren. | ||
* Wenn Sie den letzten Befehl mit der Pipe als Muster hernehmen, sollte Ihnen zur Ausgabe über den Standardfehler-Deskriptor zumindest eine trickreiche Variante einfallen: | |||
Wenn Sie den letzten Befehl mit der Pipe als Muster hernehmen, sollte Ihnen zur Ausgabe über den Standardfehler-Deskriptor zumindest eine trickreiche Variante einfallen: | |||
{ print | "cat 1>&2" } | { print | "cat 1>&2" } | ||
* Eleganter geht es mit der »Datei« /dev/stderr: | |||
Eleganter geht es mit der »Datei« /dev/stderr: | |||
{ print | "/dev/stderr" } | { print | "/dev/stderr" } | ||
* Awk kennt folgende Dateien: | |||
Awk kennt folgende Dateien: | |||
'''/dev/stdin''' Standardeingabe | '''/dev/stdin''' Standardeingabe | ||
'''/dev/stdout''' Standardausgabe | '''/dev/stdout''' Standardausgabe | ||
'''/dev/stderr''' Standardfehlerausgabe | '''/dev/stderr''' Standardfehlerausgabe | ||
'''/dev/fd/x''' Die mit dem Dateideskriptor x verbundene Datei | '''/dev/fd/x''' Die mit dem Dateideskriptor x verbundene Datei | ||
* Ein Zugriff auf bspw. /dev/fd/5 bedingt, dass zuvor eine Datei mit dem Deskriptor verbunden wurde. | |||
Ein Zugriff auf bspw. /dev/fd/5 bedingt, dass zuvor eine Datei mit dem Deskriptor verbunden wurde. | |||
==== Beispiel ==== | ==== Beispiel ==== | ||
$ '''exec 5>test.log''' | $ '''exec 5>test.log''' | ||
$ '''awk 'BEGIN {print "Testausgabe" > "/dev/fd/5"} '''' | $ '''awk 'BEGIN {print "Testausgabe" > "/dev/fd/5"} '''' | ||
* Nun raten Sie einmal, was in der Datei »test.log« drin steht? Vollständigkeitshalber sei noch erwähnt, dass Awk einige Dateien kennt, um Informationen über die Prozessumgebung auszulesen. | |||
Nun raten Sie einmal, was in der Datei »test.log« drin steht? Vollständigkeitshalber sei noch erwähnt, dass Awk einige Dateien kennt, um Informationen über die Prozessumgebung auszulesen. | * Dies sind: | ||
Dies sind: | |||
'''/dev/pid''' Prozess-ID | '''/dev/pid''' Prozess-ID | ||
'''/dev/ppid''' Prozess-ID des Eltern-Prozesses | '''/dev/ppid''' Prozess-ID des Eltern-Prozesses | ||
Zeile 1.461: | Zeile 1.132: | ||
GID 100 | GID 100 | ||
EGID 100 | EGID 100 | ||
=== Schließen von Dateien und Pipes === | === Schließen von Dateien und Pipes === | ||
Wenn Sie im Laufe eines Awk-Programms mehrfach via '''getline '''Daten aus ein und derselben Datei lesen, so liefert ein Aufruf die jeweils nächste Zeile dieser. | * Wenn Sie im Laufe eines Awk-Programms mehrfach via '''getline '''Daten aus ein und derselben Datei lesen, so liefert ein Aufruf die jeweils nächste Zeile dieser. | ||
* Bezieht '''getline '''seine Daten aus einer Pipe und erfolgt auch hierbei der mehrfache Zugriff auf ein und denselben Befehl, so wird der Befehl nur einmalig ausgeführt und jeder '''getline '''-Aufruf bringt den nächsten Datensatz aus der Ausgabe des Befehls hervor. | |||
Bezieht '''getline '''seine Daten aus einer Pipe und erfolgt auch hierbei der mehrfache Zugriff auf ein und denselben Befehl, so wird der Befehl nur einmalig ausgeführt und jeder '''getline '''-Aufruf bringt den nächsten Datensatz aus der Ausgabe des Befehls hervor. | * Nicht immer jedoch ist dies das gewünschte Verhalten, bspw. | ||
Nicht immer jedoch ist dies das gewünschte Verhalten, bspw. | |||
* wenn ein Befehl nur eine einzige Ausgabezeile liefert oder wir im Laufe der Berechnung an der ersten Zeile einer Datei interessiert sind: | * wenn ein Befehl nur eine einzige Ausgabezeile liefert oder wir im Laufe der Berechnung an der ersten Zeile einer Datei interessiert sind: | ||
$ awk 'BEGIN { | $ awk 'BEGIN { | ||
> for (i=0; i<2; i++) { | > for (i=0; i<2; i++) { | ||
Zeile 1.478: | Zeile 1.145: | ||
Don Dez 13 16:00:30 CET 2001 | Don Dez 13 16:00:30 CET 2001 | ||
Don Dez 13 16:00:30 CET 2001 | Don Dez 13 16:00:30 CET 2001 | ||
* Dass im Beispiel beide Male dieselbe Zeit ausgegeben wird, war sicherlich nicht das beabsichtigte Ergebnis. | |||
Dass im Beispiel beide Male dieselbe Zeit ausgegeben wird, war sicherlich nicht das beabsichtigte Ergebnis. | * Abhilfe schafft das Schließen der Pipe mit '''close''': | ||
Abhilfe schafft das Schließen der Pipe mit '''close''': | |||
$ awk 'BEGIN { | $ awk 'BEGIN { | ||
> for (i=0; i<2; i++) { | > for (i=0; i<2; i++) { | ||
Zeile 1.492: | Zeile 1.156: | ||
Don Dez 13 16:01:10 CET 2001 | Don Dez 13 16:01:10 CET 2001 | ||
Don Dez 13 16:02:15 CET 2001 | Don Dez 13 16:02:15 CET 2001 | ||
* Verwenden Sie '''close '''im Zusammenhang mit Dateien, wenn Sie aus diesen »von vorn« lesen möchten. | |||
Verwenden Sie '''close '''im Zusammenhang mit Dateien, wenn Sie aus diesen »von vorn« lesen möchten. | |||
== Arrays == | == Arrays == | ||
Ein Array ist eine Variable, die einen Satz von Daten - Elemente genannt - aufnehmen kann. | * Ein Array ist eine Variable, die einen Satz von Daten - Elemente genannt - aufnehmen kann. | ||
* Der Elementzugriff erfolgt über einen Index, der eine Nummer oder eine Zeichenkette sein kann, wobei - im Falle von Zeichenketten - die Groß- und Kleinschreibung stets keine Rolle spielt, unabhängig vom Wert der internen Variablen IGNORECASE! Der Name eines Arrays darf nicht mit dem Namen einer »einfachen« Variablen kollidieren. | |||
Der Elementzugriff erfolgt über einen Index, der eine Nummer oder eine Zeichenkette sein kann, wobei - im Falle von Zeichenketten - die Groß- und Kleinschreibung stets keine Rolle spielt, unabhängig vom Wert der internen Variablen IGNORECASE! Der Name eines Arrays darf nicht mit dem Namen einer »einfachen« Variablen kollidieren. | * Die Größe eines Arrays muss nicht vorab angegeben werden. | ||
* Jederzeit lassen sich weitere Elemente zum Array hinzufügen, löschen oder der Wert eines Elements verändern. | |||
Die Größe eines Arrays muss nicht vorab angegeben werden. | |||
Jederzeit lassen sich weitere Elemente zum Array hinzufügen, löschen oder der Wert eines Elements verändern. | |||
=== Einfügen / Löschen von Elementen === | === Einfügen / Löschen von Elementen === | ||
Mit jeder Zuweisung eines Wertes an einen neuen Index wächst das Array um ein weiteres Element. | * Mit jeder Zuweisung eines Wertes an einen neuen Index wächst das Array um ein weiteres Element. | ||
* Im Falle eines existierenden Indizes wird der alte Wert durch den neuen ersetzt: | * Im Falle eines existierenden Indizes wird der alte Wert durch den neuen ersetzt: | ||
* Array [Index] = Wert | |||
Array [Index] = Wert | * Neben eindimensionalen Arrays gestattet Awk auch die Verwendung mehrdimensionaler Felder. | ||
Neben eindimensionalen Arrays gestattet Awk auch die Verwendung mehrdimensionaler Felder. | |||
* Im zweidimensionalen Fall sind zwei Indizes zur Adressierung eines Elements notwendig: | * Im zweidimensionalen Fall sind zwei Indizes zur Adressierung eines Elements notwendig: | ||
* ZweiDimensionalesArray [Index1, Index2] = Wert | |||
ZweiDimensionalesArray [Index1, Index2] = Wert | * Intern bildet Awk mehrdimensionale Felder auf ein eindimensionales ab, indem die einzelnen Indizes implizit zu einer einzelnen Zeichenkette verkettet werden. | ||
* Um die Indizes identifizieren zu können, wird der durch '''SUBSEP '''definierte Separator als Trennzeichen zwischen diese gesetzt. | |||
Intern bildet Awk mehrdimensionale Felder auf ein eindimensionales ab, indem die einzelnen Indizes implizit zu einer einzelnen Zeichenkette verkettet werden. | |||
Um die Indizes identifizieren zu können, wird der durch '''SUBSEP '''definierte Separator als Trennzeichen zwischen diese gesetzt. | |||
* In der Voreinstellung von SUBSEP wird bspw. | * In der Voreinstellung von SUBSEP wird bspw. | ||
* aus »f[1,2]« intern ein »f["[mailto:1@2 1@2]"]«. | * aus »f[1,2]« intern ein »f["[mailto:1@2 1@2]"]«. | ||
* Beide Angaben sind äquivalent. | * Beide Angaben sind äquivalent. | ||
* Zum Entfernen eines Elements dient der '''delete '''-Operator. | |||
Zum Entfernen eines Elements dient der '''delete '''-Operator. | |||
* Erforderlich ist die Angabe des Indexes des zu löschenden Elements: | * Erforderlich ist die Angabe des Indexes des zu löschenden Elements: | ||
'''delete''' array [Index] | '''delete''' array [Index] | ||
* Wird '''delete '''einzig der Name eines Array übergeben - also ohne einen Index - werden sämtliche Elemente des Feldes gelöscht. | |||
Wird '''delete '''einzig der Name eines Array übergeben - also ohne einen Index - werden sämtliche Elemente des Feldes gelöscht. | |||
=== Index-Zugriff === | === Index-Zugriff === | ||
Der Zugriff auf ein Element eines Feldes ist stets über seinen Index möglich. | * Der Zugriff auf ein Element eines Feldes ist stets über seinen Index möglich. | ||
* Dies setzt voraus, dass der Index bekannt ist. | * Dies setzt voraus, dass der Index bekannt ist. | ||
* Erfolgt eines Referenzierung mittels eines unbekannten Indizes, wird eine leere Zeichenkette als Ergebnis geliefert und gleichzeitig diese als Element des Arrays angelegt: | |||
Erfolgt eines Referenzierung mittels eines unbekannten Indizes, wird eine leere Zeichenkette als Ergebnis geliefert und gleichzeitig diese als Element des Arrays angelegt: | |||
$ '''awk 'BEGIN { f[A]="foo"; | $ '''awk 'BEGIN { f[A]="foo"; | ||
> printf("_%s_%s_\n", f[A], f[B]);}'''' | > printf("_%s_%s_\n", f[A], f[B]);}'''' | ||
_foo_foo_ | _foo_foo_ | ||
* Eventuell haben Sie mit dem Verständnis des Beispiels so Ihre Probleme? Kein Wunder, es wurde bewusst ein Fehler eingebaut. | |||
Eventuell haben Sie mit dem Verständnis des Beispiels so Ihre Probleme? Kein Wunder, es wurde bewusst ein Fehler eingebaut. | |||
* Und zwar werden A und B als Namen von Variablen betrachtet und beide zur leeren Zeichenkette evaluiert. | * Und zwar werden A und B als Namen von Variablen betrachtet und beide zur leeren Zeichenkette evaluiert. | ||
* Hieraus resultiert, dass jeweils auf »f[""]« - also auf einundenselben Feldindex - zugegriffen wird. | |||
Hieraus resultiert, dass jeweils auf »f[""]« - also auf einundenselben Feldindex - zugegriffen wird. | |||
* Die korrigierte Variante bringt das vorhergesagte Ergebnis: | * Die korrigierte Variante bringt das vorhergesagte Ergebnis: | ||
$ a'''wk 'BEGIN { f["A"]="foo"; | $ a'''wk 'BEGIN { f["A"]="foo"; | ||
> printf("_%s_%s_\n", f["A"], f["B"]);}'''' | > printf("_%s_%s_\n", f["A"], f["B"]);}'''' | ||
_foo__ | _foo__ | ||
* Das automatische Anlegen eines neuen Indizes im Falle dessen Nichtexistenz ist vermutlich in etlichen Fällen unerwünscht. | |||
Das automatische Anlegen eines neuen Indizes im Falle dessen Nichtexistenz ist vermutlich in etlichen Fällen unerwünscht. | |||
* Aus diesem Grund bietet Awk eine Abfrage an, um festzustellen, ob ein Index existiert: | * Aus diesem Grund bietet Awk eine Abfrage an, um festzustellen, ob ein Index existiert: | ||
* if ( Index in Array ) { | |||
if ( Index in Array ) { | # tue etwas | ||
} | } | ||
* Ein komplexeres Beispiel zum Indexzugriff druckt die Felder der Datei /etc/passwd in tabellarischer Form, wobei die notwendige Spaltenbreite berechnet wird: | |||
Ein komplexeres Beispiel zum Indexzugriff druckt die Felder der Datei /etc/passwd in tabellarischer Form, wobei die notwendige Spaltenbreite berechnet wird: | #!/usr/bin/awk -f | ||
BEGIN { | BEGIN { | ||
Zeile 1.574: | Zeile 1.214: | ||
max[6], $6); | max[6], $6); | ||
} | } | ||
=== Scannen eines Arrays === | === Scannen eines Arrays === | ||
Unter »Scannen eines Arrays« wollen wir den sequentiellen Zugriff auf alle enthaltenen Elemente in der Reihenfolge ihres Einfügens verstehen. | * Unter »Scannen eines Arrays« wollen wir den sequentiellen Zugriff auf alle enthaltenen Elemente in der Reihenfolge ihres Einfügens verstehen. | ||
* Im Falle eindimensionaler Felder wird in Awk einfach in einer '''for '''-Schleife eine Variable der Reihe nach mit jedem Index aus dem Array verbunden. | |||
Im Falle eindimensionaler Felder wird in Awk einfach in einer '''for '''-Schleife eine Variable der Reihe nach mit jedem Index aus dem Array verbunden. | |||
* Im Schleifenrumpf kann nachfolgend auf das mit dem Index verbundene Elemente zugegriffen werden. | * Im Schleifenrumpf kann nachfolgend auf das mit dem Index verbundene Elemente zugegriffen werden. | ||
* for ( Index in Array ) { | |||
for ( Index in Array ) { | # tue etwas (mit Array[Index]) | ||
} | } | ||
* Das nachfolgende Beispiel demonstriert die Anwendung des Scannens, indem eine Liste der Häufigkeiten des Auftretens von Wörtern aus der Eingabe generiert wird: | |||
Das nachfolgende Beispiel demonstriert die Anwendung des Scannens, indem eine Liste der Häufigkeiten des Auftretens von Wörtern aus der Eingabe generiert wird: | #!/usr/bin/awk -f | ||
BEGIN { | BEGIN { | ||
RS="[[:space:]]" | |||
} | } | ||
{ ++wort[$0] } | { ++wort[$0] } | ||
Zeile 1.597: | Zeile 1.232: | ||
printf("%-20s %d\n", x, wort[x]); | printf("%-20s %d\n", x, wort[x]); | ||
} | } | ||
* Ein simpler Test beweist, dass das Skript (»WorkCounter.awk« genannt) tatsächlich funktioniert: | |||
Ein simpler Test beweist, dass das Skript (»WorkCounter.awk« genannt) tatsächlich funktioniert: | |||
$ '''echo drei eins drei zwei zwei drei | WordCounter.awk''' | $ '''echo drei eins drei zwei zwei drei | WordCounter.awk''' | ||
zwei 2 | zwei 2 | ||
drei 3 | drei 3 | ||
eins 1 | eins 1 | ||
* Der Zugriff auf die Elemente in einem mehrdimensionalen Feld kann analog erfolgen, wenn der Elementzugriff über den verketteten Index genügt. | |||
Der Zugriff auf die Elemente in einem mehrdimensionalen Feld kann analog erfolgen, wenn der Elementzugriff über den verketteten Index genügt. | * Werden hingegen die originalen Indizes benötigt, ist etwas mehr Aufwand zu betreiben: | ||
* for ( VollerIndex in Array ) { | |||
Werden hingegen die originalen Indizes benötigt, ist etwas mehr Aufwand zu betreiben: | |||
for ( VollerIndex in Array ) { | |||
split ( VollerIndex, Hilfsfeld, SUBSEP ) | split ( VollerIndex, Hilfsfeld, SUBSEP ) | ||
# Index1 steht in Hilfsfeld[1], | |||
# Index2 steht in Hilfsfeld[2]) usw. | |||
} | } | ||
} | } | ||
* Die Zeichenkettenfunktion '''split '''soll im folgenden Abschnitt behandelt werden. | |||
Die Zeichenkettenfunktion '''split '''soll im folgenden Abschnitt behandelt werden. | * Sie trennt den Inhalt von VollerIndex an den durch SUBSEP vorgegebenen Positionen auf und speichert die einzelnen Bestandteile in Hilfsfeld. | ||
* Ein sinnfreies Beispiel veranschaulicht die Methodik der Index-Aufsplittung: | |||
Sie trennt den Inhalt von VollerIndex an den durch SUBSEP vorgegebenen Positionen auf und speichert die einzelnen Bestandteile in Hilfsfeld. | |||
Ein sinnfreies Beispiel veranschaulicht die Methodik der Index-Aufsplittung: | |||
$ awk 'BEGIN { | $ awk 'BEGIN { | ||
> f[1,"foo",0815] = "egal"; | > f[1,"foo",0815] = "egal"; | ||
Zeile 1.631: | Zeile 1.257: | ||
foo | foo | ||
815 | 815 | ||
== Eingebaute Funktionen == | == Eingebaute Funktionen == | ||
Der bevorzugte Einsatzbereich von Awk ist die automatische Generierung von Reports und Statistiken. | * Der bevorzugte Einsatzbereich von Awk ist die automatische Generierung von Reports und Statistiken. | ||
* So existieren zahlreiche eingebaute Funktionen, die Awk zur Auswertung von Daten jeglicher Art prädestinieren. | * So existieren zahlreiche eingebaute Funktionen, die Awk zur Auswertung von Daten jeglicher Art prädestinieren. | ||
* Eine Funktion ist gekennzeichnet durch einen Namen und der Liste der Argumente, die dem Namen, eingeschlossen in runde Klammern, folgen. | |||
Eine Funktion ist gekennzeichnet durch einen Namen und der Liste der Argumente, die dem Namen, eingeschlossen in runde Klammern, folgen. | * Es ist bei einigen Funktionen zulässig, weniger Argumente anzugeben, als die Funktion eigentlich bedingt; welche voreingestellten Werte dann Awk einsetzt, unterscheidet sich von Funktion zu Funktion. | ||
* Stets ein Syntaxfehler hingegen ist, mehr Argumente einer Funktion mitzugeben, als bei ihrer Definition vereinbart wurden. | |||
Es ist bei einigen Funktionen zulässig, weniger Argumente anzugeben, als die Funktion eigentlich bedingt; welche voreingestellten Werte dann Awk einsetzt, unterscheidet sich von Funktion zu Funktion. | * Finden Ausdrücke als Argumente Verwendung, so werden diese vor der Übergabe an die Funktion ausgewertet, d.h. | ||
Stets ein Syntaxfehler hingegen ist, mehr Argumente einer Funktion mitzugeben, als bei ihrer Definition vereinbart wurden. | |||
Finden Ausdrücke als Argumente Verwendung, so werden diese vor der Übergabe an die Funktion ausgewertet, d.h. | |||
* bspw Funktion(i++); liefert (zumeist) ein anderes Ergebnis als Funktion(i); i++;. | * bspw Funktion(i++); liefert (zumeist) ein anderes Ergebnis als Funktion(i); i++;. | ||
* Allerdings ist die Reihenfolge der Auswertung der Argumente unspezifiziert; eine Funktion wie atan2(x++, x-1); kann in unterschiedlichen Implementierungen zur unterschiedlichen Ergebnissen führen. | |||
Allerdings ist die Reihenfolge der Auswertung der Argumente unspezifiziert; eine Funktion wie atan2(x++, x-1); kann in unterschiedlichen Implementierungen zur unterschiedlichen Ergebnissen führen. | |||
=== Mathematische Funktionen === | === Mathematische Funktionen === | ||
Zur numerischen Berechnung stellt Awk folgende Funktionen zur Verfügung: | * Zur numerischen Berechnung stellt Awk folgende Funktionen zur Verfügung: | ||
{| | {| | ||
|- | |- | ||
Zeile 1.678: | Zeile 1.296: | ||
| | <tt>'''srand([x]) '''</tt> | | | <tt>'''srand([x]) '''</tt> | ||
| | Setzt den Startwert für die Zufallszahlen-Generierung [mittels rand()]. | | | Setzt den Startwert für die Zufallszahlen-Generierung [mittels rand()]. | ||
* Geliefert wird der »alte« Startwert | |||
Geliefert wird der »alte« Startwert | |||
|- | |- | ||
|} | |} | ||
Die Funktion zur Erzeugung von Zufallszahl liefert nicht wirklich zufällige Zahlen, sondern eine Folge relativ gleichverteilter Zahlen im Intervall [0,1]. | * Die Funktion zur Erzeugung von Zufallszahl liefert nicht wirklich zufällige Zahlen, sondern eine Folge relativ gleichverteilter Zahlen im Intervall [0,1]. | ||
* Mit jedem Start eines Awk-Programms wird somit stets dieselbe Folge von Zufallszahlen generiert. | * Mit jedem Start eines Awk-Programms wird somit stets dieselbe Folge von Zufallszahlen generiert. | ||
* Genügt dieser »Zufall« nicht aus, muss mit '''srand() '''ein neuer Bezugspunkt für '''rand() '''gesetzt werden. '''srand() '''ohne Argument erzeugt diesen Startwert aus aktuellem Datum und Uhrzeit, womit '''rand() '''tatsächlich den Eindruck zufälliger Werte erweckt. | |||
Genügt dieser »Zufall« nicht aus, muss mit '''srand() '''ein neuer Bezugspunkt für '''rand() '''gesetzt werden. '''srand() '''ohne Argument erzeugt diesen Startwert aus aktuellem Datum und Uhrzeit, womit '''rand() '''tatsächlich den Eindruck zufälliger Werte erweckt. | * Einige Anwendungsbeispiele sollen die Verwendung der Funktionen demonstrieren: | ||
# Berechnung von π | |||
Einige Anwendungsbeispiele sollen die Verwendung der Funktionen demonstrieren: | |||
$ '''awk 'BEGIN { printf("π=%.50f\n", 4*atan2(1,1)); }'''' | $ '''awk 'BEGIN { printf("π=%.50f\n", 4*atan2(1,1)); }'''' | ||
π=3.14159265358979311599796346854418516159057617187500 | π=3.14159265358979311599796346854418516159057617187500 | ||
# Ganzzahliger Anteil eines Wertes | |||
$ '''awk 'BEGIN { print int(-7), int(-7.5), int (7), int(7.5)}'''' | $ '''awk 'BEGIN { print int(-7), int(-7.5), int (7), int(7.5)}'''' | ||
-7 -7 7 7 | -7 -7 7 7 | ||
# Der natürliche Logarithmus von e1 | |||
$ '''awk 'BEGIN { print log(exp(1))}'''' | $ '''awk 'BEGIN { print log(exp(1))}'''' | ||
1 | 1 | ||
'''Anmerkung''': Das Beispiel der Berechnung von π zeigt die Genauigkeitsschranke der verwendeten Awk-Implementierung; ab der 48. | '''Anmerkung''': Das Beispiel der Berechnung von π zeigt die Genauigkeitsschranke der verwendeten Awk-Implementierung; ab der 48. | ||
* Nachkommastelle ist Schluss. | * Nachkommastelle ist Schluss. | ||
* Allerdings genügt die interne Darstellung, um bei Umkehrrechnungen auf den Ausgangswert zu kommen (log(exp(1))). | * Allerdings genügt die interne Darstellung, um bei Umkehrrechnungen auf den Ausgangswert zu kommen (log(exp(1))). | ||
=== Zeichenkettenfunktionen === | === Zeichenkettenfunktionen === | ||
Die nachfolgend diskutierten Funktionen durchsuchen oder manipulieren Zeichenketten. | * Die nachfolgend diskutierten Funktionen durchsuchen oder manipulieren Zeichenketten. | ||
{| | {| | ||
|- | |- | ||
Zeile 1.726: | Zeile 1.337: | ||
| | <tt>'''split(String,Feld,[Seperator])'''</tt> | | | <tt>'''split(String,Feld,[Seperator])'''</tt> | ||
| | Zerlegt String in einzelne Elemente und legt diese in Feld ab. | | | Zerlegt String in einzelne Elemente und legt diese in Feld ab. | ||
* Als Trennzeichen dient Seperator oder FS, falls dieser nicht angegeben wurde. | |||
Als Trennzeichen dient Seperator oder FS, falls dieser nicht angegeben wurde. | |||
* Rückgabewert ist die Anzahl der Elemente. | * Rückgabewert ist die Anzahl der Elemente. | ||
|- | |- | ||
Zeile 1.744: | Zeile 1.354: | ||
| | <tt>'''toupper(string)'''</tt> | | | <tt>'''toupper(string)'''</tt> | ||
| | Wandelt Klein- in Großbuchstaben um, Rückgabewert ist die neue Zeichenkette | | | Wandelt Klein- in Großbuchstaben um, Rückgabewert ist die neue Zeichenkette | ||
|- | |- | ||
|} | |} | ||
Zeile 1.750: | Zeile 1.359: | ||
$ '''expand default.htm | awk '{x = (x < length()) ? length():x;} END {print x}'''' | $ '''expand default.htm | awk '{x = (x < length()) ? length():x;} END {print x}'''' | ||
566 | 566 | ||
==== In welcher Zeile und welcher Position erscheint ein Muster ==== | ==== In welcher Zeile und welcher Position erscheint ein Muster ==== | ||
$ '''awk '/Partitionstabelle/ {print "Zeile:", NR, "Spalte", index($0,"Partitionstabelle")}'''' Linuxfibel/installbefore.htm | $ '''awk '/Partitionstabelle/ {print "Zeile:", NR, "Spalte", index($0,"Partitionstabelle")}'''' Linuxfibel/installbefore.htm | ||
Zeile 1.759: | Zeile 1.367: | ||
Zeile: 843 Spalte 41 | Zeile: 843 Spalte 41 | ||
Zeile: 972 Spalte 12 | Zeile: 972 Spalte 12 | ||
=== Sonstige Funktionen === | === Sonstige Funktionen === | ||
{| | {| | ||
Zeile 1.768: | Zeile 1.375: | ||
| | <tt>'''fflush(Datei)'''</tt> | | | <tt>'''fflush(Datei)'''</tt> | ||
| | Leert unverzüglich den Puffer einer »gepufferten« Ausgabe. | | | Leert unverzüglich den Puffer einer »gepufferten« Ausgabe. | ||
* D.h. | |||
D.h. | |||
* veränderte Daten einer Datei werden sofort zurückgeschrieben oder die in eine Pipe geleitete Ausgabe eines Kommandos wird sofort aus dieser entnommen. | * veränderte Daten einer Datei werden sofort zurückgeschrieben oder die in eine Pipe geleitete Ausgabe eines Kommandos wird sofort aus dieser entnommen. | ||
* Bei fehlendem Argument entleert Gawk die Standardausgabe; bei Angabe der leeren Zeichenkette ("") werden die Puffer sämtlicher offener Ausgabedateien und Pips entleert. | |||
Bei fehlendem Argument entleert Gawk die Standardausgabe; bei Angabe der leeren Zeichenkette ("") werden die Puffer sämtlicher offener Ausgabedateien und Pips entleert. | |||
|- | |- | ||
| | <tt>'''system(Kommando) '''</tt> | | | <tt>'''system(Kommando) '''</tt> | ||
| | Gestattet die Ausführung von beliebigen Kommandos. | | | Gestattet die Ausführung von beliebigen Kommandos. | ||
* Nach Ende eines solchen wird im Awk-Programm fortgefahren. | |||
Nach Ende eines solchen wird im Awk-Programm fortgefahren. | * system liefert den Status des letzten gestarteten Kommandos zurück. | ||
system liefert den Status des letzten gestarteten Kommandos zurück. | |||
|- | |- | ||
| | <tt>'''systime()'''</tt> | | | <tt>'''systime()'''</tt> | ||
Zeile 1.786: | Zeile 1.389: | ||
| | <tt>'''strftime([Format [,Zeitstempel]])'''</tt> | | | <tt>'''strftime([Format [,Zeitstempel]])'''</tt> | ||
| | Liefert eine Datumszeichenkette. | | | Liefert eine Datumszeichenkette. | ||
* Ohne Zeitstempel wird die aktuelle Systemzeit verwendet, das Format entspricht dem der gleichnamigen C-Funktion. | |||
Ohne Zeitstempel wird die aktuelle Systemzeit verwendet, das Format entspricht dem der gleichnamigen C-Funktion. | |||
|- | |- | ||
|} | |} | ||
Beispiele zur Anwendung von '''close() '''finden sich im Text genügend. | * Beispiele zur Anwendung von '''close() '''finden sich im Text genügend. | ||
* Hier soll eine unnütze Anwendung von '''systime() '''den Abschnitt beschließen. | * Hier soll eine unnütze Anwendung von '''systime() '''den Abschnitt beschließen. | ||
$ '''awk 'BEGIN { start = systime(); | $ '''awk 'BEGIN { start = systime(); | ||
> for (i = 1; i < 10000000; i++) ; | > for (i = 1; i < 10000000; i++) ; | ||
Zeile 1.799: | Zeile 1.400: | ||
}'''' | }'''' | ||
Gesamtzeit betrug 6 Sekunden. | Gesamtzeit betrug 6 Sekunden. | ||
== Eigene Funktionen == | == Eigene Funktionen == | ||
Die eingebauten Funktionen decken einen weiten, aber doch nicht jeden Anwendungsbereich ab. | * Die eingebauten Funktionen decken einen weiten, aber doch nicht jeden Anwendungsbereich ab. | ||
* Erst eigene Funktionen eröffnen den Weg zu effizienten Programmen, vor allem dann, wenn identische Abläufe wiederholt im Programm auftreten. | * Erst eigene Funktionen eröffnen den Weg zu effizienten Programmen, vor allem dann, wenn identische Abläufe wiederholt im Programm auftreten. | ||
=== Funktionsdefinition === | === Funktionsdefinition === | ||
Eine Funktion muss vor ihrer ersten Verwendung definiert sein. | * Eine Funktion muss vor ihrer ersten Verwendung definiert sein. | ||
* Ihre Definition erfolgt zweckmäßig zu Beginn eines Awk-Skripts, erlaubt ist sie auch zwischen BEGIN-Block und Hauptprogramm bzw. | |||
Ihre Definition erfolgt zweckmäßig zu Beginn eines Awk-Skripts, erlaubt ist sie auch zwischen BEGIN-Block und Hauptprogramm bzw. | |||
* zwischen Hauptprogramm und END-Block. | * zwischen Hauptprogramm und END-Block. | ||
* Der allgemeine Aufbau einer Funktion ist: | |||
Der allgemeine Aufbau einer Funktion ist: | * function name([Parameter [, Parameter] ]) { | ||
# Anweisungen... | |||
function name([Parameter [, Parameter] ]) { | |||
} | } | ||
* Als Funktionsname sind alle Kombinationen aus Buchstaben, Ziffern und dem Unterstrich zulässig, wobei zu Anfang keine Ziffer stehen darf. | |||
Als Funktionsname sind alle Kombinationen aus Buchstaben, Ziffern und dem Unterstrich zulässig, wobei zu Anfang keine Ziffer stehen darf. | |||
* In einem Programm darf eine Funktion nicht gleich lauten wie der Bezeichner einer Variable. | * In einem Programm darf eine Funktion nicht gleich lauten wie der Bezeichner einer Variable. | ||
* Als Argumente können beliebig viele Parameter an eine Funktion übergeben werden, wobei das Komma als Trennzeichen dient. | |||
Als Argumente können beliebig viele Parameter an eine Funktion übergeben werden, wobei das Komma als Trennzeichen dient. | |||
* Als erstes Beispiel berechnet eine Funktion '''facultiy '''die Fakultät zu einer Zahl x: | * Als erstes Beispiel berechnet eine Funktion '''facultiy '''die Fakultät zu einer Zahl x: | ||
* function faculty(x) { | |||
function faculty(x) { | |||
if (x == 0) return 1; | if (x == 0) return 1; | ||
return x*faculty(x-1) | return x*faculty(x-1) | ||
} | } | ||
* Unsere Version von '''faculty '''ist rekursiv realisiert, was Awk problemlos akzeptiert. | |||
Unsere Version von '''faculty '''ist rekursiv realisiert, was Awk problemlos akzeptiert. | * Zur Rückehr aus einer Funktion kann '''return '''mit optionalem Rückgabewert verwendet werden. | ||
Zur Rückehr aus einer Funktion kann '''return '''mit optionalem Rückgabewert verwendet werden. | |||
* Fehlt '''return, '''kehrt die Funktion nach Ausführung der letzten Anweisung zurück. | * Fehlt '''return, '''kehrt die Funktion nach Ausführung der letzten Anweisung zurück. | ||
=== Funktionsaufruf === | === Funktionsaufruf === | ||
Zur Verwendung der Funktion rufen Sie diese einfach auf. | * Zur Verwendung der Funktion rufen Sie diese einfach auf. | ||
* Im Gegensatz zu den eingebauten Funktionen darf bei nutzerdefinierten Funktionen kein Leerzeichen zwischen Funktionsname und der öffnenden runden Klammer stehen! | |||
Im Gegensatz zu den eingebauten Funktionen darf bei nutzerdefinierten Funktionen kein Leerzeichen zwischen Funktionsname und der öffnenden runden Klammer stehen! | * Die Angabe von weniger Parametern als definiert ist zulässig; abhängig vom Kontext werden diese als leere Zeichenkette oder als 0 interpretiert. | ||
* Im folgenden Programm wird der zu berechnende Wert per Kommandozeilenargument übergeben: | |||
Die Angabe von weniger Parametern als definiert ist zulässig; abhängig vom Kontext werden diese als leere Zeichenkette oder als 0 interpretiert. | #!/usr/bin/awk -f | ||
Im folgenden Programm wird der zu berechnende Wert per Kommandozeilenargument übergeben: | |||
function faculty(x) { | function faculty(x) { | ||
Zeile 1.853: | Zeile 1.439: | ||
exit 1; | exit 1; | ||
} | } | ||
if (ARGV[1] !~ /^[[:digit:]]+$/) { | |||
print "Unzulaessiges Argument!"; | print "Unzulaessiges Argument!"; | ||
exit 2; | exit 2; | ||
Zeile 1.859: | Zeile 1.445: | ||
print faculty(ARGV[1]); | print faculty(ARGV[1]); | ||
} | } | ||
=== Lokale und globale Variablen, Parameterübergabe === | === Lokale und globale Variablen, Parameterübergabe === | ||
Damit haben Sie fast das Rüstzeug beisammen, um eigene komplexe Funktionen zu verfassen. | * Damit haben Sie fast das Rüstzeug beisammen, um eigene komplexe Funktionen zu verfassen. | ||
* Doch die eigenwillige Semantik Semantik der Verwendung von Variablen kann zumindest für den Programmierneuling schnell zu Stolperfalle werden. | |||
Doch die eigenwillige Semantik Semantik der Verwendung von Variablen kann zumindest für den Programmierneuling schnell zu Stolperfalle werden. | * Mit einer Ausnahme hat eine Funktion grundsätzlichen Zugriff auf alle Variablen, die im Programm bis zum Aufruf der Funktion eingeführt wurden. | ||
* Solche '''globalen '''Variablen können in der Funktion verändert, gelöscht (nur Array-Elemente [delete]) oder sogar neu eingeführt werden. | |||
Mit einer Ausnahme hat eine Funktion grundsätzlichen Zugriff auf alle Variablen, die im Programm bis zum Aufruf der Funktion eingeführt wurden. | |||
Solche '''globalen '''Variablen können in der Funktion verändert, gelöscht (nur Array-Elemente [delete]) oder sogar neu eingeführt werden. | |||
'''Lokale '''Variablen sind einzig jene, die in der Parameterliste benannt wurden. | '''Lokale '''Variablen sind einzig jene, die in der Parameterliste benannt wurden. | ||
* Diese Variablen verdecken ggf. | |||
Diese Variablen verdecken ggf. | |||
* vorhandene gleichnamige Variablen des Programms, sodass Änderungen an diesen in der Funktion »außen« nicht sichtbar werden. | * vorhandene gleichnamige Variablen des Programms, sodass Änderungen an diesen in der Funktion »außen« nicht sichtbar werden. | ||
* Das folgende Programm demonstriert die Verwendung von globalen und lokalen Variablen | |||
Das folgende Programm demonstriert die Verwendung von globalen und lokalen Variablen | $ '''cat context.awk''' | ||
#!/usr/bin/awk -f | |||
$ '''cat context.awk''' | |||
function context(x, a) | function context(x, a) | ||
Zeile 1.896: | Zeile 1.474: | ||
printf("\tx = %d\n\ta = %d\n\tb = %d\n", x, a, b); | printf("\tx = %d\n\ta = %d\n\tb = %d\n", x, a, b); | ||
} | } | ||
$ '''./context.awk''' | $ '''./context.awk''' | ||
Vor Aufruf der Funktion... | Vor Aufruf der Funktion... | ||
x = 100 | x = 100 | ||
a = | a = 10 | ||
b = 0 | b = 0 | ||
In der Funktion... | In der Funktion... | ||
Zeile 1.910: | Zeile 1.488: | ||
b = 99 | b = 99 | ||
* Das abschließende Beispiel dreht einem das Wort im Munde um... | |||
Das abschließende Beispiel dreht einem das Wort im Munde um... | function reverse (x) { | ||
if (length(x) == 0) | |||
function reverse (x) { | return ""; | ||
return substr(x, length(x), 1) reverse(substr(x,1, length(x)-1)); | |||
} | |||
== Weitere Informationen == | == Weitere Informationen == | ||
=== Links === | === Links === | ||
# http://de.wikibooks.org/wiki/Awk | # http://de.wikibooks.org/wiki/Awk | ||
# http://www.gnu.org/software/gawk/manual/ | # http://www.gnu.org/software/gawk/manual/ | ||
=== Quellen === | === Quellen === | ||
# http://de.linwiki.org/wiki/Linuxfibel_-_Unix-Werkzeuge_-_Awk | # http://de.linwiki.org/wiki/Linuxfibel_-_Unix-Werkzeuge_-_Awk |
Version vom 10. Oktober 2020, 20:27 Uhr
Einführung
awkward heißt im Englischen soviel wie schwierig, ungünstig.
- Ganz so problematisch erweist sich das Erlernen der Programmiersprache Awk dann doch nicht, auch wenn ihre Handhabung von perfektionierter Einfachheit weit entfernt ist.
- Das Gleichnis der Wortstämme ist Produkt des Zufalls, denn der Begriff Awk gründet sich auf den Initialen seiner Erfinder * Alfred V. Aho,
- Peter J. Weinberger,
- Brian Kernighan
- und erweiterte erstmals 1978 den Werkzeugfundus von Unix Version 7.
- Jener Kernighan prägte übrigens gemeinsam mit Dennies Ritchie maßgeblich die Entstehung der Programmiersprache C - wen wundern da noch die Analogien beider Sprachen?
1987 wartete Awk mit einer komplett überarbeiteten Fassung auf.
- Wesentliche Bestandteile fanden im später definierten POSIX-Standard Einzug.
- Der freie Awk-Abkömmling der GNU-Gemeinde gawk zeigt sich zu POSIX konform und wird in den folgenden Abschnitten behandelt.
- Der wesentliche Unterschied von Awk zu anderen Programmiersprachen wie den Skriptsprachen der UNIX Shells, C oder Tcl/Tk besteht in der datenorientierten Arbeitsweise, während die typischen Vertreter prozeduraler Programmiersprachen funktionsorientiert wirken.
- Ein awk-Programm wirkt implizit wie eine endlose Schleife, die fortwährend durchlaufen wird, bis keine Daten mehr in der Eingabe stehen oder das Programm »bewusst« verlassen wird, d. h.
- der Steuerfluss ist maßgeblich durch die Daten gegeben.
- In den meisten anderen Programmiersprachen wird das Hauptprogramm aber einmalig initiiert und Funktionen beeinflussen den Fortschritt der Berechnung.
- Awk ähnelt somit eher dem Streameditor (sed); er vermag allerdings bedeutend mehr als die »bloße« Modifikation von Textdateien, denn Awk kennt Variablen, Vergleiche, Funktionen, Schleifen u.a.m.
- und ermöglicht eine Interaktion mit dem System.
- Da in Linux-Installationen nahezu ausschließlich die GNU-Implementierung des Werkzeugs vorzufinden ist, werden wir nachfolgend immer die Bezeichnung »awk« verwenden und meinen damit eigentlich »gawk«.
- Zeigen die Beispiele auf Ihrem System abweichende oder fehlerhafte Reaktionen, überprüfen Sie, ob »awk« in ihrem System ein Link auf »gawk« ist.
Allgemeiner Programmaufbau
"Abbildung 1: Die 3 Bestandteile eines Awk-Programms"
- Ein Awk-Programm lässt sich in drei wesentliche Bestandteile zerlegen:
- Eine optionale Anweisung, die einmalig vor der Verarbeitung der Eingabedaten durchlaufen wird
- Ein Hauptprogramm, bestehend aus beliebig vielen Anweisungen, das für jede Eingabezeile erneut ausgeführt wird, es sei denn, eine bestimmte Bedingung führt zum vorzeitigen Abbruch
- Eine optionale Anweisung, die einmalig nach der Verarbeitung der Eingabedaten durchlaufen wird
- Eine Anweisung besteht wiederum aus einem optionalen Muster, gefolgt von einem Kommandoblock, der in geschweifte Klammern eingeschlossen wird.
/Muster/ { Kommando [; Kommando] }
- Der Kommandoblock darf durchaus auf mehrere Zeilen verteilt werden.
- Er endet mit der abschließenden geschweiften Klammer, sodass die folgende Aufteilung legitim und aus Gründen der Übersichtlichkeit bei größeren Kommandoblöcken zu empfehlen ist:
/Muster/ { Kommando; Kommando; ... }
- Semikola und Zeilenumbrüche trennen Kommandos, sodass dem letzten Kommando auf einer Zeile kein Semikolon folgen muss.
- Da es aber auch nichts schadet, werden wir es in den weiteren Beispielen verwenden.
- Die Muster können reguläre Ausdrücke oder Variablenvergleiche sein.* Sie werden mit der aktuellen Eingabezeile verglichen, woraufhin bei Übereinstimmung der Kommandoblock betreten wird.
- Fehlt das Muster, wird der Kommandoblock auf jeden Fall ausgeführt.
- Zwei spezielle Muster kennzeichnen die anfänglich bzw.
- abschließend auszuführenden Anweisungen.* Die Kommandos hinter dem Muster BEGIN werden zumeist zu Variableninitialisierungen oder einführenden Ausgaben genutzt.
- Dementsprechend ermöglicht das Muster END finale Aktionen, wie die Ausgabe von Ergebnissen.
- 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; }
- Wie Sie das Programm starten können? Nach Studium des folgenden Abschnitts werden Sie es wissen...
Programmstart
Kurze Awk-Programme
- Die Möglichkeiten zum Aufruf von Awk sind vielfältig.
- Für kurze und einmalig verwendete Programme bietet es sich an, diese unmittelbar auf der Kommandozeile anzugeben:
awk 'Programm' <Datei> [<Datei>]
- Das eigentliche Awk-Programm muss vor der Auswertung durch die Shell geschützt werden, deshalb die Einbettung in einfache Hochkommata.
- Alle folgenden Elemente der Kommandozeile werden von awk als Dateinamen interpretiert.
- Fehlt ein solcher Name, erwartet Awk die Daten von der Standardeingabe.
- Mit Hilfe dieses Schemas lassen sich auf einfache Art und Weise Felder aus den Zeilen der Eingabe extrahieren.
- Zur Demonstration lassen wir das erste Feld der Passwortdatei ausgeben, wobei die Zeilen nummeriert werden (der Feldseparator ist der Doppelpunkt und ist durch die eingebaute Variable FS festgelegt):
$ awk 'BEGIN {FS = ":"} {print NR,$1}' /etc/passwd
1 root 2 bin 3 daemon 4 lp 5 news ...
- Möchte man ein awk-Programm auf die Ausgabe eines Kommandos anwenden, lässt sich dies über eine Pipe realisieren:
- Kommando | awk 'Programm'
- Zur Demonstration »verschönert« nachfolgende Kommandofolge die Ausgabe von date :
$ date | awk '{print "Der " $2,$3".", "des Jahres", $6}'
Der 26.
- Jun.
- des Jahres 2013
Umfangreiche Awk-Programme
- Einfache Programme erledigen meist nur einfachste Aufgaben, aber die Anforderungen sind oft komplexer.
- So wird man umfangreiche awk-Programme in separate Dateien schreiben, wie man in der Shell-Programmierung komplexere Konstrukte in Shell-Skripten formuliert.
- Awk bezieht seine Instruktionen aus einer Steuerdatei, wenn deren Namen explizit mit der Option -f angegeben wurde:
awk -f <Programm.awk> <Datei> [<Datei>]
- Bei Verwendung einer Pipe:
Kommando | awk -f <Programm.awk>
- Awk-Anwendungen, die man immer wieder benötigt, wird man bevorzugt in Dateien fassen; Beispiele werden uns im weiteren noch reichlich begegnen.
- Noch einfacher gestaltet sich der awk-Programmaufruf bei Verwendung von awk-Skripten.
- Man bedient sich der Aufrufsyntax der entsprechenden UNIX Shell und weist die Shell an, die nachfolgenden Anweisungen dem Awk-Interpreter zuzuführen.
- Zuoberst muss einem awk-Skript nur die Zeile #!/usr/bin/awk -f (bzw.
- der korrekte Zugriffspfad zum awk-Programm) stehen.
- Versieht man eine solche Datei noch mit den entsprechenden Ausführungsrechten (»chmod u+x Programm.awk«), genügt ein simpler Aufruf:
<Programm.awk> <Datei>
- Bei Verwendung einer Pipe:
- Kommando | <Programm.awk>
Tipp
- Wenn Sie beim Editieren eines awk-Skripts vim oder kate verwenden, so stellt dieser Editor den Text mit Syntax-Highligthing dar, wenn der Skriptname auf ».awk« endet.
- Davon abgesehen, ist die Namensgebung des Skripts Ihnen überlassen.
Kommandozeilenoptionen
- Einige Optionen wurden schon verwendet, ohne ihre konkrete Bedeutung zu erläutern.
- Dies soll hier nachgeholt werden:
-F Feldtrenner | awk arbeitet auf Feldern einer Eingabezeile.
$ awk -F : '{print NR,$1}' /etc/passwd 1 root 2 bin 3 daemon 4 lp 5 news … |
-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. |
-c / - -traditional | GNU awk verhält sich wie UNIX awk, d.h.
|
-h / --help | Eine Kurzhilfe erscheint. |
-P / - -posix | Schaltet den compatibility mode mit folgenden zusätzlichen Einschränkungen an:* \x escape sequences werden nicht erkannt
|
Kommandozeilenparameter
- Awk' s Umgang mit Kommandozeilenparametern ist etwas eigenwillig.
- C-Programmierern sind sicherlich die Variablennamen argc und argv geläufig, die bevorzugt gewählt werden, um Kommandozeilenargumente an das Hauptprogramm zu übergeben.
- Awk verfügt über zwei builtin-Variablen ARGC und ARGV, die die Anzahl auf der Kommandozeile stehenden Parameter (ARGC) angeben und den Zugriff darauf über ein Feld (ARGV) ermöglichen.
- Das verwirrende daran ist, dass Awk seine eigenen Kommandozeilenoptionen nicht mitzählt.
Dazu ein Beispiel (arguments.awk):
- !/usr/bin/awk -f
BEGIN { print "Anzahl Argumente: ", ARGC; for (i=0; i < ARGC; i++) print i, ".Argument: ", ARGV[i]; }
- Wir verzichten jetzt auf die Beschreibung der verwendeter Kontrollkonstrukte und eingebauter Variablen und kommen später darauf zurück.
Achten Sie in den folgenden Testläufen auf die Reihenfolge der Argumente:
$ ./arguments.awk -F : --posix n=3 "Berlin"
Anzahl Argumente: 3 0.
- Argument: awk
1.
- Argument: n=3
2.
- Argument: Berlin
$ ./arguments.awk n=3 -F : --posix "Berlin"
Anzahl Argumente: 7 0 .Argument: awk 1 .Argument: n=3 2 .Argument: -F : 3 .Argument: --posix 4 .Argument: Berlin
Aus dem Beispiel sind mehrere Regeln abzuleiten:
- ARGC ist mindestens 1 (da der Programmname immer als erstes Argument übergeben wird)
- Die Indizierung der Argumente in ARGV beginnt bei 0
- Awk übernimmt die eigenen Aufrufoptionen nicht in die Argumentenliste
- Sobald Awk ein Argument als »nicht-eigene Option« erkennt, behandelt es alle weiteren Argumente als »fremde Optionen« (diese verlieren damit auch ihre übliche Wirkung)
- Für Awk ist jedes Argument zunächst der Name einer Datei
- Die letzte Eigenschaft ist sofort anhand von Fehlermeldungen ersichtlich, sobald das Beispielprogramm »normale Anweisungen« (außer BEGIN und END) umfasst.
- Argumente würden allerdings jeglichen Nutzen entbehren, ließen sie sich nicht doch vor Awk's Interpretation schützen.
- Da Awk die so übergebenen Dateien erst mit dem Eintritt in die Hauptschleife zu öffnen versucht, bleibt das BEGIN-Muster als der Ort der Einflussnahme übrig.
- Ein Argument sollte hier ausgewertet und anschließend aus dem Array ARGV gelöscht werden.
- Das folgende Beispiel nutzt das erste Kommandozeilenargument, um die eingebaute Variable FS neu zu belegen:
- !/usr/bin/awk -f
BEGIN { if (ARGC > 1) { FS=ARGV[1]; delete ARGV[1] } } ...
- Nehmen Sie das Beispiel nicht zu ernst...
- den Field Separator FS würde jeder erfahrene Awk-Programmierer mittels der Option -F setzen.
Überhaupt ist im Beispiel die Annahme einer festen Position des Arguments eine unsaubere Methode und sollte vermieden werden.
- Besser wäre eine positionsunabhängige Behandlung aller Argumente.
Initiale Werte
- Dienen Argumente einzig dazu, im Programm verwendeten Variablen initiale Werte zuzuweisen, so kann dies vorteilhaft erfolgen, indem dem Variablennamen auf der Kommandozeile der Startwert per Gleichheitszeichen zugewiesen wird.
- Allerdings stehen derartige Variablen erst in der Hauptschleife und nicht im BEGIN-Block zur Verfügung (es sei denn, auf sie wird über ARGV zugegriffen).
- Als Beispiel dient eine awk-basierte Variante des Kommandos »head«, das die ersten n Zeilen der Eingabe (10 in der Voreinstellung) ausgibt:
- !/usr/bin/awk -f
BEGIN { n=10; } { if (n < FNR) exit; } { print $0; }
- Speicherten wir nun das Programm unter dem Namen »head.awk« (chmod nicht vergessen!) und bringen es ohne weitere Argumente zur Ausführung, so werden - gemäß der Voreinstellung »n=10« im BEGIN-Muster - die ersten 10 Zeilen der Eingabedatei ausgegeben:
$ ./head.awk /etc/services
# # Network services, Internet style # # Note that it is presently the policy of IANA to assign a single well-known # port number for both TCP and UDP; hence, most entries here have two entries # even if the protocol doesn't support UDP operations. # # This list could be found on: # http://www.iana.org/assignments/port-numbers #
- Um eine abweichende Anzahl Zeilen zur Ausgabe zu bringen, muss der Variablen n beim Kommandoaufruf der entsprechenden Wert mitgegeben werden:
$ ./head.awk n=2 /etc/services
# # Network services, Internet style
- Die Zuweisung an die Variable muss vor dem Dateinamen stehen; im anderen Fall wäre n noch nicht bekannt, wenn Awk die Datei betrachtet.
- Mehrere Variablen lassen sich durch Leerzeichen getrennt angeben.
- Zwischen Variablennamen und Wert darf sich nur das Gleichheitszeichen befinden (auch keine Leerzeichen!):
Syntaxfehler
$ ./head.awk n = 2 /etc/services
awk: ./head.awk:4: Fatal: Die Datei »n« kann nicht zum Lesen geöffnet werden (Datei oder Verzeichnis nicht gefunden)
Reguläre Ausdrücke in Mustern
- Ein Muster ist die maßgebliche Methode, um den Programmfluss von Awk zu steuern.
- Nur wenn der Inhalt des aktuell bearbeiteten Datensatzes mit dem angegebenen Muster übereinstimmt, wird der zugehörige Kommandoblock ausgeführt.
- Um dieses Schema flexibel zu gestalten, verhelfen reguläre Ausdrücke zur Formulierung der Muster.
- Anstelle starrer Vergleichsmuster treten Platzhalter, sodass von der Eingabe quasi nur noch eine »Ähnlichkeit« mit dem Muster gefordert wird, um die Kommandofolge anzuwenden.
- Zulässig sind die erweiterten Reguläre Ausdrücke wie sie von egrep verwendet werden:
c | matches the non-metacharacter c. |
\c | matches the literal character c. |
. | matches any character including newline. |
^ | matches the beginning of a string. |
$ | matches the end of a string. |
[abc. . .] | character list, matches any of the characters abc. . .. |
[^abc. . .] | negated character list, matches any character except abc. . .. |
r1|r2 | alternation: matches either r1 or r2. |
r1r2 | concatenation: matches r1, and then r2. |
r+ | matches one or more r 's. |
r* | matches zero or more r 's. |
r ? | matches zero or one r 's. |
(r) | grouping: matches r. |
r{n}r{n,}r{n,m} | One or two numbers inside braces denote an interval expression.
|
\y | matches the empty string at either the beginning or the end of a word. |
\B | matches the empty string within a word. |
\< | matches the empty string at the beginning of a word. |
\> | matches the empty string at the end of a word. |
\s | matches any whitespace character. |
\S | matches any nonwhitespace character. |
\w | matches any word-constituent character (letter, digit, or underscore). |
\W | matches any character that is not word-constituent. |
\` | matches the empty string at the beginning of a buffer (string). |
\' | matches the empty string at the end of a buffer. |
Beispiel
- Um alle Leerzeilen der Datei /etc/inittab auszugeben, hilft:
$ awk '/^$/ {print "Zeile" FNR "ist leer"}' /etc/fstab
Zeile9ist leer Zeile11ist leer Zeile13ist leer Zeile17ist leer
- Die interne Variable FNR enthält die Nummer der aktuell bearbeiteten Zeile.
Beispiel
- Steht eine gültige Zahl (Integer) in der Eingabe?
$ awk '/^digit:+$/ {print "Ja!"}'
123457900 Ja! 12A [Ctrl]-[D]
- Die enthaltene Anwendung von Zeichenklassen ([:digit:]) wird später behandelt.
Metazeichen / regulärer Ausdruck
- Verwechseln Sie die regulären Ausdrücke nicht mit den Metazeichen der Shells! Zwar ist das Funktionsprinzip identisch, selten aber die konkrete Semantik der Zeichen (bspw. * oder ?):
- Liste alle mit "a" beginnenden Dateien (Shell):
$ ls -1 a*
a.out ascii2short.c awk.txt
- Suche in der Ausgabe von "ls" nach mit "a" beginnenden Dateien (awk)
$ ls | awk '/^a+/ {print}'
a.out ascii2short.c awk.txt
- Das Beispiel verdeutlicht die unterschiedliche Syntax zwischen Shell und awk bei der Realisierung derselben Aufgabe.
Übung
- Ersetzen Sie einmal das awk-Muster durch »/a*/« und vergleichen die Resultate.
- Was ist da passiert?
Muster und Zeichenketten
- Bislang arbeitete die Mustererkennung stets über die gesamte Eingabezeile.
"Zeichenkette ~ /Muster/" … dehnt sich dieses Schema auf beliebige Zeichenketten aus.
- So extrahiert nachfolgendes Awk-Programm alle Benutzernamen aus der Datei /etc/passwd, deren Heimatverzeichnisse unterhalb von /home liegen:
$ awk -F ':' '$6 ~ /^\/home/ {print $1}' /etc/passwd
tux user
- Die Zeichenkette ist hier das 6.Feld ($6) der Passwortdatei (Heimatverzeichnis); die Syntax des Musters sollte sich anhand der einleitenden Tabelle der regulären Ausdrücke erklären.
- Felder und deren Indizierung sind Gegenstand des folgenden Abschnitts.
Flexible Muster
- Der Zeichenkettenvergleich ist auch hilfreich, wenn das zu betrachtende Muster flexibel gestaltet werden soll.
- Die Zeichenkette ist dann die aktuell bearbeitete Zeile ($0, wird später behandelt); als Muster dient der Inhalt einer Variablen (muster im Beispiel):
$ cat sinnlos.awk
#!/usr/bin/awk -f $0 ~ muster { print $0; }
- Die Variable muster wird dann als Kommandozeilenargument geeignet belegt:
$ ./sinnlos.awk muster='.+' <Datei>
Zeichenklassen
- Der Begriff »Wort« ließe sich einfach als eine Folge von Buchstaben umschreiben; eine »ganze Zahl« demzufolge als Ansammlung von Ziffern.
- Ein simples awk-Programm, das Zeichenketten aus der Eingabe in die Kategorien »Wort« oder »Zahl« eingliedert, könnte wie folgt verfasst sein:
- Testet die Eingabe auf Wort, Zahl oder Sonstiges.
/[0-9]+/ { print "Eine Zahl" } /[A-Za-z]+/ { print "Ein Wort" }
- Auf den ersten Blick ist kein Fallstrick zu erkennen, aber* Wer garantiert, dass ein Skript tatsächlich nur in Umgebungen eingesetzt wird, die denselben Zeichensatz verwenden wie der Skriptentwickler?
- Nicht in jedem Zeichensatz bilden Ziffern bzw.
- Buchstaben eine zusammenhängende Folge.
- Und Angaben »[von-bis]« beruhen genau auf jenem Prinzip.
- Um Skripte portabel (POSIX-Standard) zu halten - und mitunter auch kürzer - sollte daher Zeichenklassen der Vorzug vor Bereichsangaben gegeben werden.
- Es ist dann Aufgabe der konkreten Awk-Implementierung, eine Zeichenklasse auf den verwendeten Zeichensatz abzubilden.
- Unser Beispiel mit Zeichenklassen schreibt sich dann so:
- Testet die Eingabe auf Wort, Zahl oder Sonstiges.
/digit:+/ { print "Eine Zahl" } /alpha:+/ { print "Ein Wort" }
- Vorausgesetzt Ihr System wurde sauber konfiguriert (Belegung der Shellvariablen $LANG), sollten nun auch die deutschen Umlaute korrekt erfasst werden:
$ echo Überprüfung | awk '/alpha:+/ {print "Ein Wort";}'
Ein Wort
Weitere Zeichenklassen:
[:alnum:] | Alphanumerische Zeichen |
[:alpha:] | Alphabetische Zeichen.Um zu testen, ob in einer Variablen eine gültige Zahl (ganzzahlig) gespeichert ist, bietet sich folgendes Konstrukt an:
echo "0815" | awk '/^digit:+$/ {print "eine Zahl"}' Eine Zahl |
[:blank:] | Leerzeichen und Tabulatoren |
[:cntrl:] | Steuerzeichen |
[:digit:] | Nummerische Zeichen |
[:graph:] | Druck- und sichtbare Zeichen (Ein Leerzeichen ist druckbar aber nicht sichtbar, wogegen ein »a« beides ist) |
[:lower:] | Kleingeschriebene alphabetische Zeichen |
[:print:] | Druckbare Zeichen (also keine Steuerzeichen) |
[:punct:] | Punktierungszeichen/Punctuation characters (Zeichen die keine Buchstaben, Zahlen, Steuerzeichen oder Leerzeichen sind) (".", "," ":") |
[:space:] | Druck- aber keine sichtbaren Zeichen (Leerzeichen, Tabulatoren, Zeichenende ..) |
[:upper:] | Großbuchstaben |
[:xdigit:] | Hexadezimale Zeichen |
Datenfelder und Variablen
Wichtige eingebaute Variablen
- Awk arbeitet unter der Annahme, dass die Eingabe strukturiert ist.
- Im einfachsten Fall interpretiert awk jede Eingabezeile als Datensatz und jedes enthaltene Wort als Feld. Ein Wort ist dabei jede von Feldseparatoren begrenzte Zeichenfolge.
- In der Voreinstellung trennen Leerzeichen und Tabulatoren Wörter; durch Belegung der builtin-Variablen FS lassen sich beliebige Separatoren definieren.
Über den Feldoperator $ gestattet Awk den Zugriff auf die Felder der zuletzt eingelesenen Eingabezeile.
- Im Unterschied zur Programmiersprache C beginnt die Nummerierung der Felder bei 1 und - im Gegensatz zu den Positionsparametern der Shell - endet sie nicht bei 9 (implementierungsabhängig werden 100 Felder garantiert; gawk auf x86-Architektur kann vermutlich 232 Felder indizieren).
- Um z.B.
- das 1.
- und 11.
- Feld einer Eingabezeile auszugeben, hilft Folgendes:
$ echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print $1,$11;}'
0 10
- Der Feldoperator $0 steht für die gesamte Zeile; die Anzahl der Felder der aktuell bearbeiteten Zeile ist in der Variablen NF gespeichert.
$ echo "0 1 2 3 4 5 6 7 8 9 10 11" | awk '{print NF,$0;}'
12 0 1 2 3 4 5 6 7 8 9 10 11
- Die aktuelle Zeilennummer hält Awk in der Variablen NR.
- Um die Zeilen einer Datei zu nummerieren, könnte somit folgender Aufruf dienen:
$ awk '{print NR,$0;}' <zu_nummerierende_Datei>
- Bei aufmerksamer Betrachtung des einführenden Beispiels »head.awk« ist Ihnen vermutlich aufgefallen, dass wir dort FNR anstatt NR zur Nummerierung verwendeten.
- Der Unterschied ist, dass Letzteres (NR) fortlaufend die Anzahl der Durchläufe der Hauptschleife zählt, während FNR im Falle mehrerer Eingabedateien die Zählung für jede Datei von vorn beginnt.
Beispiel
»wc -l« mit Mitteln von Awk, wobei außerdem die Variable FILENAME verwendet wird, die den Namen der aktuell bearbeiteten Datei enthält :
- !/usr/bin/awk -f
BEGIN { zeile=0; } { if ( zeile > FNR ) { print zeile,FILENAME; zeile=0; } else zeile++; } END { print FNR, FILENAME if ( FNR != NR ) { print NR, "insgesamt"; } }
- Als Testfall wenden wir das kleine Programm auf sich selbst an und vergleichen die Ausgabe mit der von »wc -l«:
$ ./wc-l.awk /etc/fstab /etc/passwd
17 /etc/passwd 46 /etc/passwd 63 insgesamt
$ wc -l /etc/fstab /etc/passwd
17 /etc/fstab 46 /etc/passwd 63 insgesamt
- Abgesehen von der Formatierung (wir später noch behoben) verhält sich unser Programm exakt wie das Vorbild.
- Eher unüblich ist die Manipulation der Variablen RS, die den Zeilenseparator spezifiziert.
- Sinnvoll ist es für Datensätze, die über mehrere Zeilen verteilt stehen und ein anderes Zeichen (bspw.
- eine Leerzeile) eine eindeutige Strukturierung erlaubt.
- Das die Ausgabe von Awk betreffende Pedand zu RS ist der Output Record Selector (ORS).
- In der Voreinstellung dient der Zeilenumbruch zur Separierung der Ausgaben; durch Neubelegung von ORS kann dies geändert werden.
- Nachfolgender Kommandoaufruf ersetzt die Unix-Dateiendung (Newline) durch das Windows-Äquivalent (Carriage Return, Newline):
$ awk 'BEGIN {ORS="\r\n";}{print $0;}' <Datei>
- Natürlich hätte es auch »recode« getan...
- Neben ORS zum Ändern des Zeilenseparators existiert mit OFS eine Variable, die das Trennzeichen für einzelne Datenfelder (Voreinstellung: Leerzeichen) festlegt.
Weitere eingebaute Variablen
- Die weiteren von Awk verwendeten internen Variablen sollen an dieser Stelle nur kurz benannt werden.
- Einigen Vertretern werden wir später erneut begegnen.
CONVFMT | Steuert die Konvertierung von Zahlen in Zeichenketten.
$ awk 'BEGIN { print "Pi = " 3.1415926532; }' Pi = 3.14159 $ awk 'BEGIN { CONVFMT="%.10g"; print "Pi = " 3.1415926532; }' Pi = 3.141592653 |
ENVIRON | Die Feldvariable ENVIRON enthält alle Umgebungsvariablen.
$ awk 'BEGIN { print ENVIRON["HOME"]; }' /home/user |
IGNORECASE | Steuert die Unterscheidung von Klein- und Großschreibung beim Mustervergleich.
|
OFMT | OFMT steuert das Ausgabeformat von Zahlen durch das Kommando print. Die Verwendung erfolgt analog zu CONVFMT |
RLENGTH | Länge der übereinstimmenden Teilzeichenkette der Zeichenkettenfunktion match() |
RSTART | Index des Beginns der übereinstimmenden Teilzeichenkette mit der Zeichenkettenfunktion match() |
SUBSEP | Das Zeichen, das in Feldvariablen die einzelnen Elemente trennt (\034). |
Eigene Variablen
Datentypen
- Awk unterscheidet einzig (Gleitkomma-)Zahlen und Zeichenketten.
- Von welchem Typ eine Variable ist, hängt vom aktuellen Kontext ab.
- Findet eine numerische Berechnung statt, werden die beteiligten Variablen als Zahlen interpretiert und bei Zeichenkettenoperationen eben als Zeichenketten.
- Notfalls lässt sich ein konkreter Kontext erzwingen, indem bspw.
- der Wert '0' zu einer Variable addiert oder die leere Zeichenkette "" an eine solche angehangen wird.
Internen Konvertierungen
- Für den Awk-Programmierer von Interesse sind die internen Konvertierungen, die beim Vergleich von Variablen stattfinden.
- Zwei numerische Variablen werden als Zahlen verglichen, genauso wie zwei Zeichenkettenvariablen als Zeichenketten verglichen werden.
- Ist eine Variable eine Zahl und die andere eine numerische Zeichenkette, so wird die Zeichenkette in eine Zahl konvertiert.
- Handelt es sich um keine numerische Zeichenkette, wird die Variable mit der Zahl in eine Zeichenkette gewandelt und nachfolgend ein Vergleich der Zeichenketten vorgenommen.
Variablennamen
- Variablennamen in Awk dürfen aus Buchstaben, Ziffern und dem Unterstrich bestehen, wobei zum Beginn keine Ziffer stehen darf.
- Klein- und Großschreibung werden unterschieden, sodass bspw.
- Variable, variable, und VARIABLE unterschiedliche Bezeichner sind.
- Eine Variable wird definiert, indem sie benannt und ihr ein Wert zugewiesen wird.
- Die in anderen Programmiersprachen üblichen Typbezeichner kennt Awk nicht.
- Eine Variable kann auch nur deklariert werden, awk initialisiert sie dann selbsttätig als leeren Zeichenkette.
Operatoren
- Awk kennt nahezu das komplette Repertoire an Operatoren, die die Programmiersprachen zur Manipulation von Ausdrücken zur Verfügung stellen.
Arithmetische Operatoren
- Als arithmetische Operatoren beinhaltet Awk:
+ | Addition |
- | Subtraktion |
* | Multiplikation |
/ | Division |
% | Modulo |
^ | Potenzieren (Posix) |
** | Potenzieren (gawk u.a.) |
- Da letzterer Potenzoperator (**) nicht im POSIX-Standard enthalten ist, muss eine konkrete Awk-Implementierung diesen nicht zwangläufig verstehen.
- Verwenden Sie daher stets »^«, um Ihre Awk-Skripte portabel zu halten.
- Im Falle der Division sollten Sie prüfen, dass der Divisor nicht 0 wird, da Awk sonst mit einem Ausnahmefehler das Programm abbricht:
$ awk 'BEGIN { x=5; y; print x/y; }'
awk: Kommandozeile:1: Fatal: Division durch Null wurde versucht
- Für arithmetische Operationen gelten die üblichen Vorrangregeln.
$ awk 'BEGIN { print 5*4+20, 5*(4+20); }'
40 120
Zuweisungsoperatoren
- Das Gleichheitszeichen zur Zuweisung eines Wertes an eine Variable sollte hinreichend bekannt sein; dem C erfahrenen Programmierer sollten auch die folgenden Operatoren vertraut erscheinen:
Operator | Kurzschreibweise für |
x++ bzw. ++x | x = x + 1 |
x-- bzw. --x | x = x - 1 |
X += y | x = x + y |
X -=y | x = x – y |
X *= y | x = x * x |
X /= y | x = x / y |
X %= y | x = x % y |
X ^= y | x = x ^ y |
X **= y | x = x ** y |
- Mit Ausnahme von Inkrement- und Dekrement-Operatoren bleibt es Ihnen überlassen, ob Sie die Kurzform der ausführlichen Schreibweise vorziehen.
- Für Inkrement bzw.
- Dekrement entsprechen genau genommen nur ++x und --x (Prefix-Operatoren) der Semantik der langen Darstellung, denn nur hier werden die Zuweisungen zuerst ausgeführt und erst anschließend der Wert von x evaluiert.
- Als Postfix-Operatoren (x++, x--) verwendet, geht der alte Wert der Variable x in einem Ausdruck ein und erst nach Auswertung erfolgt die Zuweisung des neuen Wertes an x.
Beispiele
$ awk 'BEGIN { x=5; print x = x + 1, x; }'
6 6 $ awk 'BEGIN { x=5; print x++, x; }' 5 6 $ awk 'BEGIN { x=5; print ++x, x; }' 6 6
- Das folgende Beispiel demonstriert, dass tatsächlich in scheinbar gleichwertigen Ausdrücken abweichende Resultate erzielt werden:
$ awk 'BEGIN { while ( ++x < 5 ) print x }'
1 2 3 4
$ awk 'BEGIN { while ( x++ < 5 ) print x }'
1 2 3 4 5
- Das nächste Beispiel zählt die Dateien im aktuellen Verzeichnis und berechnet ihren Speicherbedarf:
$ ls -l | awk '{ ++files; sum+=$5 } END { print sum, "Bytes in", files, "Dateien"; }'
Vergleichsoperatoren
< | Kleiner als |
> | Größer als |
<= | Kleiner als oder gleich |
>= | Größer als oder gleich |
== | Gleich |
!= | Ungleich |
~ | Übereinstimmung |
!~ | Keine Übereinstimmung |
- Der Zweck der ersten 6 Operatoren sollte leicht erkenntlich sein.
- Beachten Sie, dass der Test auf Gleichheit »==« anstatt »=« verwendet.
- Eine Verwechslung wäre kein Syntaxfehler (und ist daher als Fehler schwer zu erkennen), führt aber zu abweichenden Ergebnissen!
- Bei den Operatoren ~ und !~ dürfen als rechtsseitige Operanden auch reguläre Ausdrücke stehen, während bei allen anderen Operatoren einzig Variablen und Konstanten zulässig sind.
Beispiel
- Finde alle Benutzer, deren Heimatverzeichnis unter /home liegt:
$ awk '$6 ~ /^\/home.*/ { print $1; }' /etc/passwd
user tux
Logische Operatoren
&& | Logisches UND | |
Logisches ODER | ||
! | Negation |
- Logische oder boolesche Operatoren dienen maßgeblich zur Verknüpfung mehrerer Vergleichsoperatoren oder mehrerer regulärer Ausdrücke.
- Das Beispiel listet alle Dateien des aktuellen Verzeichnisses auf, die zwischen 4097 und 8192 Bytes groß sind:
$ ls -l /etc/ | awk '$5 >= 4097 && $5 <= 8192 {print $NF; }'
bogofilter.cf csh.cshrc csh.login dhcpd.conf enscript.cfg freshclam.conf my.cnf ...
- Ein weiteres Beispiel veranschaulicht die Verwendung der Negation, indem im aktuellen Verzeichnis alle Dateien gesucht werden, die nicht dem aktuell angemeldeten Benutzer gehören:
$ ls -l | awk 'FNR > 1 && !($3 == ENVIRON["USER"]) {print $NF; }'
- Der Vergleich ist auch mit »!=« möglich.
Priorität der Auswertung
- Verknüpfen Sie mehrere Ausdrücke per logischer Operatoren, so kann die Priorität der Auswertung eine Rolle spielen:* Eine Negation wird vor dem logischen UND und dieses vor dem logischen ODER betrachtet.
- Nutzen Sie die Klammerung, um ggf.
- eine andere Bearbeitungsreihenfolge zu erzwingen.
Kontrollstrukturen
- Der Mustervergleich arbeitet über die Eingabedaten und regelte, ob für den aktuellen Datensatz Aktionen durchzuführen ist.
- Die im weiteren vorgestellten Konstrukte gestatten den Kontrollfluss innerhalb einer solchen Aktionsfolge.
Bedingte Ausführung
if-else
- Wohl jede Programmiersprache verfügt über ein if-else-Konstrukt, die Realisierung von awk orientiert sich an der C-Syntax:
- if (Bedingung) {
# diese Aktionen } [ else { # jene Aktionen } ]
- Evaluiert die Bedingung zu wahr (!=0), so werden die Aktionen des if -Zweigs ausgeführt, anderenfalls wird der optionale else -Zweig betreten.
- Folgt einem Zweig nur eine einzelne Aktion, können die geschweiften Klammern entfallen.
- Das nachfolgende Beispiel widerspricht eigentlich dem Einsatzzweck von Awk, bedarf es doch keinerlei Eingabedaten.
- Es testet, ob das Skript mit (mind.) einem Argument aufgerufen wurde.
- Fehlt dieses, endet das Skript mit einer Fehlerausgabe, anderenfalls wird der Wert des ersten Arguments potenziert:
$ cat potenz.awk
#!/usr/bin/awk -f BEGIN { if (ARGV[1] == "") { print "Benutze: ./potenz.awk 'beliebiger_Wert'"; exit 1; } else { print "x^x =", ARGV[1]**ARGV[1]; } }
Anmerkung: Während der Abhandlung zu Variablen wurde erwähnt, dass nicht initialisierte Variablen intern mit »0« (numerischer Kontext) bzw.
- mit »""« (Zeichenkettenkontext) belegt werden.
- Da »0« eine erlaubte Eingabe des Skripts darstellt, erzwingen wir mit dem Vergleich mit der leeren Zeichenkette letzteren Kontext.
- Awk kennt kein zu case äquivalentes Konstrukt, sodass das zur Bewertung mehrerer Bedingungen verschachtelte if-else-Anweisungen verwendet werden:
- if (Bedingung 1) {
# Aktionen } else if (Bedingung 2) { # Aktionen } [...] else { # Aktionen }
- Bei mehreren erfüllten Bedingungen werden einzig die Aktionen der ersten zutreffenden ausgeführt.
- Wird keine Bedingung wahr, werden alle Aktionen des optionalen else-Zweigs abgearbeitet.
Conditional-if
- Manchmal soll einer Variable in Abhängigkeit vom Ergebnis einer Bedingung ein Wert zugewiesen werden.
- Mit »if-else« wäre das folgende Fragment denkbar:
- if (Bedingung)
variable = Wert_1 else variable = Wert_2
- Prägnanter ist die Schreibweise des Conditional-if:
- variable = Bedingung ? Wert_1 :Wert_2
- Es bleibt Ihnen überlassen, welche Varianten Sie wählen.
- Dir letztere Schreibweise unterstreicht deutlicher das Ansinnen, einer Variable einen Wert zuzuweisen.
- Da conditional-if im Gegensatz zu if-else einen Wert liefert, finden sich auch hin und wieder Situationen, wo eine Umschreibung per if-else umständlich, wenn nicht gar unmöglich ist:
$ awk 'BEGIN { x=1; while (x <= 3) printf("%d Osterei%s\n", x, x++>1 ?"er":"") }'
1 Osterei 2 Ostereier 3 Ostereier
printf wird im Abschnitt Ein/Ausgabe besprochen.
Schleifen
for
- Die for-Schleife wird bevorzugt, wenn eine bekannte Anzahl Durchläufe der Anweisungen im Schleifenrumpf erfolgen soll:
- for (Zähler initialisieren; Zähler testen; Zähler verändern) {
# Aktionen }
- Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört.
- Die mit Zähler initialisieren, Zähler testen und Zähler verändern bezeichneten Ausdrücke beschreiben den »gebräuchlichen« Verwendungszweck.
- Wie auch in C wird der erste Ausdruck (Zähler initialisieren) einmalig vor Eintritt in die Schleife ausgeführt.
- Der zweite Ausdruck (Zähler testen) wird jeweils vor dem nächsten Durchlauf der Schleife betrachtet und der dritte Ausdruck (Zähler verändern) wird nach Bearbeitung der Anweisung des Schleifenrumpfes berechnet:
$ awk 'BEGIN {for ( i=0; i<3; i++) print i;}'
0 1 2
$ awk 'BEGIN {for ( i=0; i++<3; ) print i;}'
1 2 3
- Die Beispiele zeigen auch, dass die Angabe der Ausdrücke optional ist.
- Analog zu C kann eine Endlosschleife wie folgt angegeben werden:
$ awk 'BEGIN {for (;;) printf(".") }'
- Was Awk im Gegensatz zu C allerdings nicht gestattet, ist die Angabe einer kommaseparierten Liste von Ausdrücken (»for (i=0,j=0;...;...)«).
while
while -Schleifen werden bevorzugt, wenn die Anzahl der Schleifendurchläufe vom Ergebnis eines Ausdrucks abhängt.
- while (Bedingung) {
# Aktionen }
- Die geschweiften Klammern dürfen entfallen, wenn nur eine einzelne Aktion zum Schleifenrumpf gehört.
- Das folgende Beispiel berechnet die Fakultät zu einer per Argument übergebenen Zahl:
$ cat fakultaet.awk
#!/usr/bin/awk -f BEGIN { if (ARGC < 2 || (ARGV[1] !~ /^[0-9]+$/)){ print "Aufruf: ./fakultaet.awk <Zahl>"; exit 1; } fakultaet=1; zahl=ARGV[1]; while (zahl > 1) fakultaet*=zahl--; print "Fakultaet von", ARGV[1], "ist", fakultaet; }
$ ./fakultaet.awk 5
Fakultaet von 5 ist 120
do-while
- In Situationen, in denen ein Programmabschnitt mindestens einmal - und in Abhängigkeit von einer Bedingung weitere Male - zu durchlaufen ist, bietet sich die do-while -Schleife an:
- do {
# Aktionen } while (Bedingung)
- Eine Anwendung könnte der Test einer Nutzereingabe sein, die solange zu wiederholen ist, bis sie dem geforderten Format entspricht:
- do {
printf("Geben Sie eine Zahl ein: "); getline zahl < "-"; } while ( zahl !~ /^digit:+$/ )
getline wird im Abschnitt Ein/Ausgabe behandelt.
Weitere Kontrollanweisungen
- Betrachtet man das Hauptprogramm von Awk als eine Schleife (die über alle Zeilen der Eingabe läuft), so besitzen die nachfolgend vorgestellten Anweisungen eine Gemeinsamkeit: Sie verändern den Kontrollfluss von Schleifen.* break und continue wirken mit for und [do-]while zusammen
- exit, next und nextfile betreffen die Hauptschleife.
break
- Mit break kann eine for-, while- oder do-while-Schleife vorzeitig verlassen werden.
- Die Programmausführung fährt mit der der Schleife unmittelbar folgenden Anweisung fort.
$ awk 'BEGIN { for (;;) if (x++ == 5) break; print x}'
6
continue
- Wird continue innerhalb einer for-, while- oder do-while-Schleife ausgeführt, wird die Bearbeitung des aktuellen Schleifendurchlaufs abgebrochen und der nächste Durchlauf begonnen.
$ awk 'BEGIN { for (i=1;i<10;i++) {if (i%2) continue; print i } }'
2 4 6 8
exit
- Der exit -Befehl dient zum (vorzeitigen) Beenden der Hauptschleife von Awk.
- Das Programm wird unverzüglich mit Ausführung des optionalen END-Statements fortgeführt.
- Als Argument kann exit ein Ausdruck mitgegeben werden.
- Der Wert des Ausdrucks ist der Rückgabewert von Awk.
- Ohne Angabe des Arguments wird implizit 0 angenommen.
- Eine diesbezügliche Ausnahme bildet die Verwendung von exit innerhalb der END-Anweisungen.
- Fehlt hier das Argument, gilt ein in der Hauptschleife (bzw.
- innerhalb von BEGIN) gesetzter Wert:
$ awk 'BEGIN { exit 3 }; END { exit }'; echo $?
3 $ awk 'BEGIN { exit 3 }; END { exit 0 }'; echo $? 0
next
next wirkt wie continue, nur dass jetzt der aktuelle Durchlauf des Hauptprogramms unterbrochen und mit dem nächsten Durchlauf - sprich: mit der nächsten Zeile der Eingabe - fortgefahren wird.
nextfile
- Diese Erweiterung von GNU-Awk (nicht POSIX) wirkt wie next, d.h.
- es wird zum Beginn des Hauptprogramms gesprungen.
- Die aktuelle Datei wird geschlossen und als Datensatz die erste Zeile der nächsten Eingabedatei geladen.
Ein- und Ausgabe
Eingabe
- Das Erfassen von Nutzereingaben ist nur eine Anwendungsvariante der Funktion getline.
- Und genau genommen vermag sie das auch nur, weil ihr die Standardeingabe als Quelldatei untergeschoben werden kann.
getline [variable] [< "<Datei>"]
<Kommando> | getline [variable]
- Im einfachsten Fall des Funktionsaufrufs ohne Argumente lädt getline einfach die nächste Eingabezeile.
- Alle im Skript folgenden Anweisungen arbeiten folglich mit diesem neuen Datensatz.
- In dieser Form manipuliert getline die Awk-Variablen $0, NR, NF und FNR.
- Folgt dem Funktionsaufruf eine Variable, so landet der Inhalt der Eingabe in dieser.
- Der Arbeitspuffer mit dem aktuellen Datensatz ($0) wird hierbei nicht verändert, jedoch werden die gelesenen Daten aus der Eingabe entfernt.
- Das folgende Beispiel nutzt das Verhalten, um jeweils zwei Zeilen der Eingabe zu einer Zeile in der Ausgabe zusammenzufassen:
$ cat 2to1.awk
#!/usr/bin/awk -f { if ( (getline nextLine) < 1) nextLine=""; print $0, nextLine; }
getline liefert bei erfolgreich gelesener Eingabe eine »1« zurück.
- Das Beispiel nutzt den Rückgabewert, um die Variable zurückzusetzen, falls in der Eingabe eine ungerade Anzahl Datensätze steht.
- Eine »-1« liefert getline im Fehlerfall; eine »0« bei Erreichen des Dateiendes.
getline in Verbindung mit < "<Datei>" versucht den nächsten Datensatz aus der Datei zu lesen.
- Dieser steht in $0 zur Verfügung, FN enthält die Anzahl der Felder dieses Dateisatzes.
- Mit jedem Aufruf wird ein weiterer Datensatz aus der Datei geliefert.
- Um von der Standardeingabe zu lesen, ist als Dateiname »-« anzugeben:
$ awk 'BEGIN {getline < "-"; print "Die Eingabe war ", $0; }' Anmerkung: Speziell in gawk steht der Dateiname »/dev/stdin« zur Verfügung, der anstatt dem Minus verwendet werden kann.
- Als weitere Quelle kann getline seine Eingaben auch aus einer Pipe beziehen.
- Auf der linken Seite der Pipe muss ein Kommando stehen, das in doppelte Anführungszeichen einzuschließen ist:
$ awk 'BEGIN {"date" | getline datum; close("date"); print "Aktuelles Datum: ", datum;}'
Aktuelles Datum: Mit Dez 12 21:42:35 CET 2001
close wird im Anschluss behandelt.
Escape-Sequenzen
- Escape-Sequenzen sind nicht auf die Verwendung in Ausgaben beschränkt, werden aber in diesem Zusammenhang häufig genutzt.
\a | Alert (Piepton) |
\b | Backspace (Cursor ein Zeichen nach links) |
\f | Formfeed (Cursor auf nächste Zeile, eine Position weiter) |
\n | Zeilenumbruch |
\r | Carriage return (Cursor an Anfang der aktuellen Zeile) |
\t | Horizontaler Tabulator |
\v | Vertikaler Tabulator |
\ddd | 1-3 oktale Ziffern |
\xHEX | Hexadezimale Zahl |
\c | Das Zeichen c selbst (i.A.
|
$ awk 'BEGIN {print "\\f\f führt\f zu\f einem\f Treppeneffekt:)";}'
\f führt zu einem Treppeneffekt:)
Ausgabe mit print
print [Parameter [, Parameter] ]
print ([Parameter [, Parameter] ])
print ist für die Ausgabe zu bevorzugen, wenn Sie keine Anforderungen an die Formatierung stellen.
- Geben Sie hierzu einfach die Liste der auszugebenden Ausdrücke, jeweils getrennt durch ein Komma, an.
- Jedes Element wird in der Ausgabe vom Folgenden durch ein Leerzeichen getrennt werden.
- Dem letzten Element lässt print einen Zeilenumbruch folgen (das voreingestellte Verhalten kann mittels der Variablen OFS und ORS geändert werden)
- Sie können gar auf jegliche Elemente verzichten.
- In dem Fall gibt print die aktuell bearbeitet Zeile aus, d.h.
- zwischen »print« und »print $0« besteht bez.
- des Ergebnisses kein Unterschied.
- Die an print zu übergebenden Argumente können wahlweise in runde Klammern eingeschlossen werden.
- Notwendig ist dies, wenn die Ausdrücke den Vergleichsoperator »>« enthalten, da dieser sonst als Ausgabeumleitung verstanden wird.
- Es ist kein Syntaxfehler, wenn Sie auf die Kommata zwischen den auszugebenden Ausdrücken verzichten.
- Awk interpretiert dies als das Zusammenhängen von Zeichenketten, sodass als ersichtlicher Unterschied zumeist das trennende Leerzeichen fehlt:
$ awk 'BEGIN { print "foo", "bar";}'
foo bar $ awk 'BEGIN { print "foo" "bar";}' foobar
- Eine begrenzte Formatierung lässt sich für numerische Werte erzwingen, indem das in OFMT gespeicherte Ausgabeformat (Voreinstellung "%.6g") geändert wird:
$ awk 'BEGIN { print rand();}'
0.487477
$ awk 'BEGIN { OFMT="%.8f"; print rand();}'
0.48747681
$ awk 'BEGIN { OFMT="%.1f"; print rand();}'
0.5
- Beachten Sie die enthaltene mathematisch korrekte Rundung der Werte!
Formatierte Ausgabe mit printf
- Die Syntax von printf kann ihre Anlehnung an die verbreitete Sprache nicht verbergen.
printf (Formatzeichenkette[, Parameter(liste)])
- Zwar dürfen in Awk die runden Klammern auch entfallen, aber das ist fast schon der einzige Unterschied zu C.
- Die Formatzeichenkette kann jedes ASCII-Zeichen, Escape-Sequenzen oder einen der folgenden Platzhalter umfassen:
%c | Ein einzelnes ASCII-Zeichen |
%d | Eine ganze Zahl |
%i | Eine ganze Zahl (wie d, aber konform zu POSIX) |
%e | Gleitkommazahl in Exponentendarstellung (2.e7) |
%E | Gleitkommazahl in Exponentendarstellung (2.E7) |
%f | Gleitkommazahl in Fließdarstellung |
%g | e oder f, je nachdem, was kürzer ist |
%G | E oder f, je nachdem, was kürzer ist |
%o | Oktale Zahl |
%s | Zeichenkette |
%x | Hexadezimale Zahl mit a-e |
%X | Hexadezimale Zahl mit A-E |
%% | % selbst |
- Die Anzahl der Elemente der Parameterliste muss mindestens die Anzahl Platzhalter in der Formatzeichenkette umfassen. Überschüssige Angaben werden schlicht ignoriert.
- Der Typ des i-ten Parameters sollte zum i-ten Platzhalter »passen«, allerdings reagiert Awk recht großzügig und wandelt die Typen ggf.
- ineinander um.
- Meist weicht das Resultat aber vom Gewünschten ab.
$ awk 'BEGIN { printf("%i\n", 11.100); }'
11
$ awk 'BEGIN { printf("%e\v%E\n", 11.100, 11.100); }'
1.110000e+01 1.110000E+01
$ awk 'BEGIN { printf("%f\n", 11.100); }'
11.100000
$ awk 'BEGIN { printf("%x\n", 11.100); }'
b
$ awk 'BEGIN { printf("%s\t%s\n", 11.100, "11.100"); }'
11.1 11.100
- Eine exakte Positionierung und angepasstes Format der auszugebenden Parameter wäre mit den Platzhaltern allein nicht immer möglich, daher gestattet printf - C lässt grüßen - die Angabe von Breite und Ausrichtung eines Parameters sowie eines Modifizierers.
- Für Gleitkommaangaben kommt noch die Genauigkeit der Nachkommastellen hinzu.
- Die optionalen Angaben stehen zwischen Prozentzeichen und Formatidentifikator:
%ModifiziererBreite.GenauigkeitFormatidentifikator
- Der wohl gebräuchlichste Modifizierer beeinflusst die horizontale Anordnung eines Parameters.
- In der Voreinstellung rechtsbündig, erzwingt ein dem Prozentzeichen folgendes Minus die linksbündige Ausrichtung:
$ awk 'BEGIN { for (i=0; i<6; i++) printf("%4i\n", i**i) }'
1 1 4 27 256 3125
$ awk 'BEGIN { for (i=0; i<6; i++) printf("%-4i\n", i**i) }'
1 1 4 27 256 3125
- Als Modifizierer stehen zur Verfügung:
- | Linksbündige Ausrichtung |
+ | Das Vorzeichen bei nummerischen Werten wird stets angegeben |
Leerzeichen | Bei positiven nummerischen Werten wird ein Leerzeichen vorangestellt |
0 | Nummerische Werte werden von links her mit Nullen aufgefüllt |
# | Druckt bspw. |
- bei "%x" ein "0x" vor den Wert
$ awk 'BEGIN { printf ("%#x %04i %+4i\n", 12, 12, 12)}'
0xc 0012 +12
- Von C's printf hat Awk ebenso die variable Angabe der Breiten- und Genauigkeitswerte übernommen.
- Anstatt des Wertes steht im Formatierer nun ein Stern.
- Der zu wählende Wert erscheint in der Argumentenliste vor dem jeweiligen Argument (Reihenfolge und Anzahl sollte übereinstimmen!):
Die »übliche« Form:
$ awk 'BEGIN { printf("%12.11f\n", 11/13 ) }'
Die »dynamische« Form:
$ awk 'BEGIN {width=12; precision=11; printf("%*.*f\n", width, precision, 53/17 ) }'
0.84615384615
Umleitung der Ausgabe
- Die nachfolgend am Beispiel von print demonstrierten Mechanismen funktionieren ebenso mit printf.
- Normalerweise landen die Ausgaben auf dem Terminal (Standardausgabe).
- Ihr Ziel kann mittels der schon von den Shells bekannten Umleitungen ebenso eine Datei oder eine Pipe sein.
print Argument > Ausgabedatei
print Argument >> Ausgabedatei
print Argument | Kommando
- Anwendungsbeispiele für den Nutzen der Umleitung von Ausgaben in einer Datei finden sich reichlich in der alltäglichen Administration, z.B.
- bei der Auswertung der Logdateien.
- Gerade bei Servern häufen sich die Meldungen in der Datei /var/log/messages nur allzu schnell.
- Per Hand regelmäßig nach verdächtigen Zeilen Ausschau zu halten, wird rasch zur Last.
- So könnte Awk eine vorherige Selektion treffen und thematische Logdateien anlegen.
- Das folgende Beispiel zeigt eine Anwendung, die die Nachrichten des sshd und des telnetd filtert:
- !/usr/bin/awk -f
# 'filter.awk' filtert Meldungen des sshd und telnetd # Aufruf: filter.awk /var/log/messages /.*sshd.*/ { print >> ENVIRON["HOME"]"/sshd.log"} /.*in.telnetd.*/ { print >> ENVIRON["HOME"]"/telnet.log"}
- Verwenden Sie in einem der Argumente von print(f) den Vergleichsoperator »>«, so müssen Sie die Argumente klammern, da der Operator ansonsten als Umleitung verstanden wird!
- Die Ausgabe von print(f) in eine Pipe zu speisen, bietet sich an, falls das Ergebnis durch ein Kommando weiter bearbeitet werden soll.
- Schließen Sie dazu das Kommando inklusive seiner Argumente in doppelte Anführungszeichen ein.
- Als Beispiel werden Funde von telnet-Nachrichten sofort per Mail an den lokalen Administrator gemeldet:
- !/usr/bin/awk -f
# 'mailer.awk' meldet Nachrichten des telnetd an Root # Aufruf: mailer.awk /var/log/messages /.*in.telnetd.*/ { print | "mail -s 'Telnet-Kontakt' root" }
- Derartige Kommandoaufrufe lassen sich auch in einer Variable speichern und darüber referenzieren:
...
BEFEHL="mail -s 'Telnet-Kontakt' root" ... /.*in.telnetd.*/ { print | BEFEHL }
Spezielle Dateinamen
- Analog zu den Dateideskriptoren der Shells kennt GNU-Awk (nicht POSIX!) spezielle Dateinamen, um die Umleitung in konkrete Kanäle zu realisieren.
- Wenn Sie den letzten Befehl mit der Pipe als Muster hernehmen, sollte Ihnen zur Ausgabe über den Standardfehler-Deskriptor zumindest eine trickreiche Variante einfallen:
{ print | "cat 1>&2" }
- Eleganter geht es mit der »Datei« /dev/stderr:
{ print | "/dev/stderr" }
- Awk kennt folgende Dateien:
/dev/stdin Standardeingabe
/dev/stdout Standardausgabe /dev/stderr Standardfehlerausgabe /dev/fd/x Die mit dem Dateideskriptor x verbundene Datei
- Ein Zugriff auf bspw. /dev/fd/5 bedingt, dass zuvor eine Datei mit dem Deskriptor verbunden wurde.
Beispiel
$ exec 5>test.log
$ awk 'BEGIN {print "Testausgabe" > "/dev/fd/5"} '
- Nun raten Sie einmal, was in der Datei »test.log« drin steht? Vollständigkeitshalber sei noch erwähnt, dass Awk einige Dateien kennt, um Informationen über die Prozessumgebung auszulesen.
- Dies sind:
/dev/pid Prozess-ID
/dev/ppid Prozess-ID des Eltern-Prozesses /dev/pgrpid Prozess-Gruppen-ID /dev/user 4 Werte zum Eigentümer des Prozesses
$ awk 'BEGIN{ getline < "/dev/user";
> print "UID", $1 > print "EUID", $2 > print "GID", $3 > print "EGID", $4 > } ' UID 500 EUID 500 GID 100 EGID 100
Schließen von Dateien und Pipes
- Wenn Sie im Laufe eines Awk-Programms mehrfach via getline Daten aus ein und derselben Datei lesen, so liefert ein Aufruf die jeweils nächste Zeile dieser.
- Bezieht getline seine Daten aus einer Pipe und erfolgt auch hierbei der mehrfache Zugriff auf ein und denselben Befehl, so wird der Befehl nur einmalig ausgeführt und jeder getline -Aufruf bringt den nächsten Datensatz aus der Ausgabe des Befehls hervor.
- Nicht immer jedoch ist dies das gewünschte Verhalten, bspw.
- wenn ein Befehl nur eine einzige Ausgabezeile liefert oder wir im Laufe der Berechnung an der ersten Zeile einer Datei interessiert sind:
$ awk 'BEGIN {
> for (i=0; i<2; i++) { > "date" | getline x; > print x; > system("sleep 65")} > }' Don Dez 13 16:00:30 CET 2001 Don Dez 13 16:00:30 CET 2001
- Dass im Beispiel beide Male dieselbe Zeit ausgegeben wird, war sicherlich nicht das beabsichtigte Ergebnis.
- Abhilfe schafft das Schließen der Pipe mit close:
$ awk 'BEGIN {
> for (i=0; i<2; i++) { > "date" | getline x > print x > system("sleep 65") > close("date") } > }' Don Dez 13 16:01:10 CET 2001 Don Dez 13 16:02:15 CET 2001
- Verwenden Sie close im Zusammenhang mit Dateien, wenn Sie aus diesen »von vorn« lesen möchten.
Arrays
- Ein Array ist eine Variable, die einen Satz von Daten - Elemente genannt - aufnehmen kann.
- Der Elementzugriff erfolgt über einen Index, der eine Nummer oder eine Zeichenkette sein kann, wobei - im Falle von Zeichenketten - die Groß- und Kleinschreibung stets keine Rolle spielt, unabhängig vom Wert der internen Variablen IGNORECASE! Der Name eines Arrays darf nicht mit dem Namen einer »einfachen« Variablen kollidieren.
- Die Größe eines Arrays muss nicht vorab angegeben werden.
- Jederzeit lassen sich weitere Elemente zum Array hinzufügen, löschen oder der Wert eines Elements verändern.
Einfügen / Löschen von Elementen
- Mit jeder Zuweisung eines Wertes an einen neuen Index wächst das Array um ein weiteres Element.
- Im Falle eines existierenden Indizes wird der alte Wert durch den neuen ersetzt:
- Array [Index] = Wert
- Neben eindimensionalen Arrays gestattet Awk auch die Verwendung mehrdimensionaler Felder.
- Im zweidimensionalen Fall sind zwei Indizes zur Adressierung eines Elements notwendig:
- ZweiDimensionalesArray [Index1, Index2] = Wert
- Intern bildet Awk mehrdimensionale Felder auf ein eindimensionales ab, indem die einzelnen Indizes implizit zu einer einzelnen Zeichenkette verkettet werden.
- Um die Indizes identifizieren zu können, wird der durch SUBSEP definierte Separator als Trennzeichen zwischen diese gesetzt.
- In der Voreinstellung von SUBSEP wird bspw.
- aus »f[1,2]« intern ein »f["1@2"]«.
- Beide Angaben sind äquivalent.
- Zum Entfernen eines Elements dient der delete -Operator.
- Erforderlich ist die Angabe des Indexes des zu löschenden Elements:
delete array [Index]
- Wird delete einzig der Name eines Array übergeben - also ohne einen Index - werden sämtliche Elemente des Feldes gelöscht.
Index-Zugriff
- Der Zugriff auf ein Element eines Feldes ist stets über seinen Index möglich.
- Dies setzt voraus, dass der Index bekannt ist.
- Erfolgt eines Referenzierung mittels eines unbekannten Indizes, wird eine leere Zeichenkette als Ergebnis geliefert und gleichzeitig diese als Element des Arrays angelegt:
$ awk 'BEGIN { f[A]="foo";
> printf("_%s_%s_\n", f[A], f[B]);}' _foo_foo_
- Eventuell haben Sie mit dem Verständnis des Beispiels so Ihre Probleme? Kein Wunder, es wurde bewusst ein Fehler eingebaut.
- Und zwar werden A und B als Namen von Variablen betrachtet und beide zur leeren Zeichenkette evaluiert.
- Hieraus resultiert, dass jeweils auf »f[""]« - also auf einundenselben Feldindex - zugegriffen wird.
- Die korrigierte Variante bringt das vorhergesagte Ergebnis:
$ awk 'BEGIN { f["A"]="foo";
> printf("_%s_%s_\n", f["A"], f["B"]);}' _foo__
- Das automatische Anlegen eines neuen Indizes im Falle dessen Nichtexistenz ist vermutlich in etlichen Fällen unerwünscht.
- Aus diesem Grund bietet Awk eine Abfrage an, um festzustellen, ob ein Index existiert:
- if ( Index in Array ) {
# tue etwas }
- Ein komplexeres Beispiel zum Indexzugriff druckt die Felder der Datei /etc/passwd in tabellarischer Form, wobei die notwendige Spaltenbreite berechnet wird:
- !/usr/bin/awk -f
BEGIN { FS=":"; while ( getline < "/etc/passwd" ) for (i=1; i<=6; i++) if (max[i] < length($i)) { max[i] = length($i)} close("/etc/passwd" ) while ( getline < "/etc/passwd" ) printf("%-*s %*s %*s %-*s %-*s\n", max[1], $1, max[3], $3, max[4], $4, max[5], $5, max[6], $6); }
Scannen eines Arrays
- Unter »Scannen eines Arrays« wollen wir den sequentiellen Zugriff auf alle enthaltenen Elemente in der Reihenfolge ihres Einfügens verstehen.
- Im Falle eindimensionaler Felder wird in Awk einfach in einer for -Schleife eine Variable der Reihe nach mit jedem Index aus dem Array verbunden.
- Im Schleifenrumpf kann nachfolgend auf das mit dem Index verbundene Elemente zugegriffen werden.
- for ( Index in Array ) {
# tue etwas (mit Array[Index]) }
- Das nachfolgende Beispiel demonstriert die Anwendung des Scannens, indem eine Liste der Häufigkeiten des Auftretens von Wörtern aus der Eingabe generiert wird:
- !/usr/bin/awk -f
BEGIN { RS="space:" } { ++wort[$0] } END { for (x in wort) printf("%-20s %d\n", x, wort[x]); }
- Ein simpler Test beweist, dass das Skript (»WorkCounter.awk« genannt) tatsächlich funktioniert:
$ echo drei eins drei zwei zwei drei | WordCounter.awk
zwei 2 drei 3 eins 1
- Der Zugriff auf die Elemente in einem mehrdimensionalen Feld kann analog erfolgen, wenn der Elementzugriff über den verketteten Index genügt.
- Werden hingegen die originalen Indizes benötigt, ist etwas mehr Aufwand zu betreiben:
- for ( VollerIndex in Array ) {
split ( VollerIndex, Hilfsfeld, SUBSEP ) # Index1 steht in Hilfsfeld[1], # Index2 steht in Hilfsfeld[2]) usw. } }
- Die Zeichenkettenfunktion split soll im folgenden Abschnitt behandelt werden.
- Sie trennt den Inhalt von VollerIndex an den durch SUBSEP vorgegebenen Positionen auf und speichert die einzelnen Bestandteile in Hilfsfeld.
- Ein sinnfreies Beispiel veranschaulicht die Methodik der Index-Aufsplittung:
$ awk 'BEGIN {
> f[1,"foo",0815] = "egal"; > for (i in f) { > split (i,hf,SUBSEP); > for (x in hf) print hf[x] > } } 1 foo 815
Eingebaute Funktionen
- Der bevorzugte Einsatzbereich von Awk ist die automatische Generierung von Reports und Statistiken.
- So existieren zahlreiche eingebaute Funktionen, die Awk zur Auswertung von Daten jeglicher Art prädestinieren.
- Eine Funktion ist gekennzeichnet durch einen Namen und der Liste der Argumente, die dem Namen, eingeschlossen in runde Klammern, folgen.
- Es ist bei einigen Funktionen zulässig, weniger Argumente anzugeben, als die Funktion eigentlich bedingt; welche voreingestellten Werte dann Awk einsetzt, unterscheidet sich von Funktion zu Funktion.
- Stets ein Syntaxfehler hingegen ist, mehr Argumente einer Funktion mitzugeben, als bei ihrer Definition vereinbart wurden.
- Finden Ausdrücke als Argumente Verwendung, so werden diese vor der Übergabe an die Funktion ausgewertet, d.h.
- bspw Funktion(i++); liefert (zumeist) ein anderes Ergebnis als Funktion(i); i++;.
- Allerdings ist die Reihenfolge der Auswertung der Argumente unspezifiziert; eine Funktion wie atan2(x++, x-1); kann in unterschiedlichen Implementierungen zur unterschiedlichen Ergebnissen führen.
Mathematische Funktionen
- Zur numerischen Berechnung stellt Awk folgende Funktionen zur Verfügung:
Liefert den Arcus-Tangens (in rad) von x/y | |
cos(x) | Liefert den Consinus von x |
exp(x) | Berechnet ex |
int(x) | Ganzzahliger Wert von x, wobei »in Richtung 0« gerundet wird |
log(x) | Liefert den natürlichen Logarithmus von x |
rand() | Liefert eine Zufallszahl im Bereich von [0,1] |
sin(x) | Liefert den Sinus von x |
sqrt(x) | Liefert die Quadratwurzel von x |
srand([x]) | Setzt den Startwert für die Zufallszahlen-Generierung [mittels rand()].
|
- Die Funktion zur Erzeugung von Zufallszahl liefert nicht wirklich zufällige Zahlen, sondern eine Folge relativ gleichverteilter Zahlen im Intervall [0,1].
- Mit jedem Start eines Awk-Programms wird somit stets dieselbe Folge von Zufallszahlen generiert.
- Genügt dieser »Zufall« nicht aus, muss mit srand() ein neuer Bezugspunkt für rand() gesetzt werden. srand() ohne Argument erzeugt diesen Startwert aus aktuellem Datum und Uhrzeit, womit rand() tatsächlich den Eindruck zufälliger Werte erweckt.
- Einige Anwendungsbeispiele sollen die Verwendung der Funktionen demonstrieren:
- Berechnung von π
$ awk 'BEGIN { printf("π=%.50f\n", 4*atan2(1,1)); }' π=3.14159265358979311599796346854418516159057617187500 # Ganzzahliger Anteil eines Wertes $ awk 'BEGIN { print int(-7), int(-7.5), int (7), int(7.5)}' -7 -7 7 7 # Der natürliche Logarithmus von e1 $ awk 'BEGIN { print log(exp(1))}' 1
Anmerkung: Das Beispiel der Berechnung von π zeigt die Genauigkeitsschranke der verwendeten Awk-Implementierung; ab der 48.
- Nachkommastelle ist Schluss.
- Allerdings genügt die interne Darstellung, um bei Umkehrrechnungen auf den Ausgangswert zu kommen (log(exp(1))).
Zeichenkettenfunktionen
- Die nachfolgend diskutierten Funktionen durchsuchen oder manipulieren Zeichenketten.
gsub(Regex,Ersatz,[String]) | Jedes Vorkommen des Regulären Ausdrucks Regex in String wird durch Ersatz ersetzt.
|
index(Suchstring,Muster) | Liefert die Position, an der Muster in Suchstring erstmals vorkommt oder 0 |
length([String]) | Liefert die Länge von String; fehlt String, wird die länge von $0 zurückgegeben |
match(String,Regex) | Liefert die Position des ersten Auftretens des Regulären Ausdrucks Regex in String; setzt RSTART und RLENGTH |
split(String,Feld,[Seperator]) | Zerlegt String in einzelne Elemente und legt diese in Feld ab.
|
sprintf("Format",Ausdruck) | Anwendung wie printf, anstatt einer Ausgabe gibt die Funktion die resultierende Zeichenkette zurück |
sub(Regex,Ersatz,[String]) | Arbeitsweise wie gsub, es wird aber nur das erste Muster ersetzt |
substr(string,p,[l]) | Gibt eine Teilzeichenkette von string der Länge l, beginnend an Position p zurück |
tolower(string) | Wandelt Groß- in Kleinbuchstaben um, Rückgabewert ist die neue Zeichenkette |
toupper(string) | Wandelt Klein- in Großbuchstaben um, Rückgabewert ist die neue Zeichenkette |
Ermitteln der längsten Zeile einer Datei
$ expand default.htm | awk '{x = (x < length()) ? length():x;} END {print x}' 566
In welcher Zeile und welcher Position erscheint ein Muster
$ awk '/Partitionstabelle/ {print "Zeile:", NR, "Spalte", index($0,"Partitionstabelle")}' Linuxfibel/installbefore.htm
Zeile: 804 Spalte 14 Zeile: 818 Spalte 81 Zeile: 837 Spalte 57 Zeile: 838 Spalte 41 Zeile: 843 Spalte 41 Zeile: 972 Spalte 12
Sonstige Funktionen
close(Datei) | Schließt eine zuvor zum Lesen oder Schreiben geöffnete Datei oder die Pipe, die als Eingabe oder Ausgabe eines Kommandos diente (dann muss anstatt eines Dateinamens der Kommandoaufruf angegeben werden) |
fflush(Datei) | Leert unverzüglich den Puffer einer »gepufferten« Ausgabe.
|
system(Kommando) | Gestattet die Ausführung von beliebigen Kommandos.
|
systime() | Liefert die Systemzeit in Sekunden (seit 1.1.1970) |
strftime([Format [,Zeitstempel]]) | Liefert eine Datumszeichenkette.
|
- Beispiele zur Anwendung von close() finden sich im Text genügend.
- Hier soll eine unnütze Anwendung von systime() den Abschnitt beschließen.
$ awk 'BEGIN { start = systime();
> for (i = 1; i < 10000000; i++) ; > end = systime(); > print "Gesamtzeit betrug", end - start, "Sekunden."; }' Gesamtzeit betrug 6 Sekunden.
Eigene Funktionen
- Die eingebauten Funktionen decken einen weiten, aber doch nicht jeden Anwendungsbereich ab.
- Erst eigene Funktionen eröffnen den Weg zu effizienten Programmen, vor allem dann, wenn identische Abläufe wiederholt im Programm auftreten.
Funktionsdefinition
- Eine Funktion muss vor ihrer ersten Verwendung definiert sein.
- Ihre Definition erfolgt zweckmäßig zu Beginn eines Awk-Skripts, erlaubt ist sie auch zwischen BEGIN-Block und Hauptprogramm bzw.
- zwischen Hauptprogramm und END-Block.
- Der allgemeine Aufbau einer Funktion ist:
- function name([Parameter [, Parameter] ]) {
# Anweisungen... }
- Als Funktionsname sind alle Kombinationen aus Buchstaben, Ziffern und dem Unterstrich zulässig, wobei zu Anfang keine Ziffer stehen darf.
- In einem Programm darf eine Funktion nicht gleich lauten wie der Bezeichner einer Variable.
- Als Argumente können beliebig viele Parameter an eine Funktion übergeben werden, wobei das Komma als Trennzeichen dient.
- Als erstes Beispiel berechnet eine Funktion facultiy die Fakultät zu einer Zahl x:
- function faculty(x) {
if (x == 0) return 1; return x*faculty(x-1) }
- Unsere Version von faculty ist rekursiv realisiert, was Awk problemlos akzeptiert.
- Zur Rückehr aus einer Funktion kann return mit optionalem Rückgabewert verwendet werden.
- Fehlt return, kehrt die Funktion nach Ausführung der letzten Anweisung zurück.
Funktionsaufruf
- Zur Verwendung der Funktion rufen Sie diese einfach auf.
- Im Gegensatz zu den eingebauten Funktionen darf bei nutzerdefinierten Funktionen kein Leerzeichen zwischen Funktionsname und der öffnenden runden Klammer stehen!
- Die Angabe von weniger Parametern als definiert ist zulässig; abhängig vom Kontext werden diese als leere Zeichenkette oder als 0 interpretiert.
- Im folgenden Programm wird der zu berechnende Wert per Kommandozeilenargument übergeben:
#!/usr/bin/awk -f function faculty(x) { if (x == 0) return 1; return x*faculty(x-1) } BEGIN { if (ARGC < 2) { print "Fehlendes Argument!"; exit 1; } if (ARGV[1] !~ /^digit:+$/) { print "Unzulaessiges Argument!"; exit 2; } print faculty(ARGV[1]); }
Lokale und globale Variablen, Parameterübergabe
- Damit haben Sie fast das Rüstzeug beisammen, um eigene komplexe Funktionen zu verfassen.
- Doch die eigenwillige Semantik Semantik der Verwendung von Variablen kann zumindest für den Programmierneuling schnell zu Stolperfalle werden.
- Mit einer Ausnahme hat eine Funktion grundsätzlichen Zugriff auf alle Variablen, die im Programm bis zum Aufruf der Funktion eingeführt wurden.
- Solche globalen Variablen können in der Funktion verändert, gelöscht (nur Array-Elemente [delete]) oder sogar neu eingeführt werden.
Lokale Variablen sind einzig jene, die in der Parameterliste benannt wurden.
- Diese Variablen verdecken ggf.
- vorhandene gleichnamige Variablen des Programms, sodass Änderungen an diesen in der Funktion »außen« nicht sichtbar werden.
- Das folgende Programm demonstriert die Verwendung von globalen und lokalen Variablen
$ cat context.awk #!/usr/bin/awk -f function context(x, a) { printf("In der Funktion...\n") printf("\tx = %d\n\ta = %d\n\tb = %d\n", x, a, b); x = a = b = 99; } BEGIN { x = a = 100; printf("Vor Aufruf der Funktion...\n") printf("\tx = %d\n\ta = %d\n\tb = %d\n", x, a, b); context(10); printf("Nach Aufruf der Funktion...\n") printf("\tx = %d\n\ta = %d\n\tb = %d\n", x, a, b); } $ ./context.awk Vor Aufruf der Funktion... x = 100 a = 10 b = 0 In der Funktion... x = 10 a = 0 b = 0 Nach Aufruf der Funktion... x = 100 a = 100 b = 99
- Das abschließende Beispiel dreht einem das Wort im Munde um...
function reverse (x) { if (length(x) == 0) return ""; return substr(x, length(x), 1) reverse(substr(x,1, length(x)-1)); }
Weitere Informationen
Links
Quellen
Seiten in der Kategorie „Gawk“
Folgende 12 Seiten sind in dieser Kategorie, von 12 insgesamt.