# AWK-Skript generieren und von einem externen Interpreter ausführen lassen BEGIN { # Variante 1: via Pipe awk = "awk -f -" print "BEGIN { print 1/3 }" | awk close(awk) # Variante 2: via File awk_file = ENVIRON["HOME"] "/awk_file.awk" print "/^root:/" > awk_file close(awk_file) system(sprintf("awk -f %s /etc/passwd", awk_file)) system("rm " awk_file) }
$1 --> Feld 1 des aktuellen Satzes (analog $2, $3, ... für die weiteren Felder) $NF --> letztes Feld des aktuellen Satzes NF --> Anzahl der Felder des aktuellen Satzes (number of fields) $0 --> der komplette Satz
# AWK liest von der Standardeingabe eine Zeile mit 4 Feldern; als OFS dient der # senkrechte Strich; durch eine Zuweisung an Feld 1 kommt der OFS bei der # Neuberechnung von $0 zur Anwendung echo 'aa bb cc dd' | # über die Option -v kann man beim Aufruf Variablen des AKW-Programms mit # Werten belegen awk -v OFS='|' ' { $1 = "33" # Zuweisung des Strings 33 an Feld 1 print $3 # Ausgabe Feld 3 print $0 # Ausgabe des kompletten Satzes; OFS trennt die Felder }' # Ausgabe: # cc # 33|bb|cc|dd
pattern { action statements } function name(parameter list) { statements }
#
eingeleitet
# Funktion, die das Argument x zur Variablen summe addiert und die Variable anz # um eins inkrementiert function summiere(x) { summe += x anz++ } # summiere und zähle die Werte der Spalte 2 derjenigen Zeilen einer Textdatei, # die mit dem String "daten" beginnen oder dem String "wert" enden /^daten|wert$/ { summiere($2) } # Summe und Durchschnitt formatiert ausgeben END { printf("Summe : %6.2f\n", summe) if (anz) { # anz ist ungleich Null printf("Durchschnitt: %6.2f\n", summe / anz) } else { # anz ist Null printf("Durchschnitt: nicht ermittelbar, da 0 Werte addiert wurden\n") } }
awk -f summe_sp2.awk daten_summe_sp2.txt
(...) Gruppierung $ Feld-Referenz ++ -- Inkrement/Dekrement (+1/-1), Präfix (++a) / Postfix (a--) ++a erhöht den Wert von a vor dem Variablenzugriff, a++ danach ^ Potenzierung (** ist auch zulässig) + - ! unäres Plus/Minus und logische Negation * / % Multiplikation, Division, Modulus (Rest der Division) + - Addition, Subtraktion space String-Verkettung durch Leerzeichen, z.B. "au" "to" | |& Pipe-Ein-/Ausgabe bei getline, print, printf < > kleiner, größer <= >= kleiner gleich, größer gleich != == ungleich und gleich ~ !~ Test auf passendes/unpassendes Muster (regulärer Ausdruck); konstanter Ausdruck /abc/ alleine entspricht $0 ~ /abc/ in Test auf Array-Mitgliedschaft && logisches UND || logisches ODER ?: ternärer Operator zur Bildung eines bedingten Ausdrucks: expr1 ? expr2 : expr3 falls expr1 wahr, dann ergibt sich Wert expr2, sonst expr3 = += -= *= /= %= ^= Zuweisung eines Werten an eine Variable (a op= b entspricht a = a op b)
?:
und ^
sind alle Operatoren linksassoziativ
~
und !~
sinnvoll, da /xy/ ~ z
eine Kurzform von ($0 ~ /xy/) ~ z
darstellt
~
und !~
darf ein beliebiger Ausdruck
stehen; dieser wird bei Bedarf in einen String konvertiert und als regulärer
Ausdruck interpretiert, wodurch Suchmuster auch dynamisch im Programm erzeugt
werden können, z.B. $0 ~ "^r" ".*t"
statt
/^r.*t/
BEGIN END /regular expression/ relational expression pattern && pattern pattern || pattern pattern ? pattern : pattern (pattern) ! pattern pattern1, pattern2
exit
ausgeführt
egrep
entspricht, dient
als Suchmuster und selektiert alle Sätze, die dem Muster entsprechen
. ein beliebiges Zeichen ^ Anfang des Strings $ Ende des Strings () Klammerung zur Bildung von Teilmustern [abc] alle Zeichen der genannten Menge [^ab] alle Zeichen außerhalb der genannten Menge * beliebige (0..n-malige) Wiederholung des vorangehenden Elements + vorangehendes Element mindestens 1 Mal vorhanden ? vorangehendes Element 0 oder 1 Mal vorhanden a|b Alternative: Muster a oder Muster b a{2,6} vorangehendes Element ist 2 bis 6 Mal vorhanden \... Escape-Sequenzen wie bei Strings (\n, \t, ...)
# alle Bereiche eingerückt ausgeben, die jeweils von einem Satz mit Feld 1 # gleich 20 bis zum ersten Folgesatz reichen, der "ende" enthält $1 == 20, /ende/ { print " " $0 }
getline [var] # liest Folgesatz der Einleseschleife nach $0 bzw. var getline [var] <file # liest nächsten Satz der Datei file nach $0 bzw. var cmd | getline [var] # liest die Ausgabe der Pipe "cmd" nach $0 bzw. var print # schreibt den aktuellen Satz auf die Standardausgabe print expr-list # analoge Ausgabe der Ausdrücke der Liste print expr-list >file # Ausgabe in die Datei "file" printf fmt, expr-list # formatierte Ausgabe printf fmt, expr-list >file # dito in eine Datei system(cmd-line) # externes Kommando ausführen close(file), close(command) # schließt Datei oder Pipe
if (condition) statement [ else statement ] # vollständige/unvollständige Alternative while (condition) statement # Abweisschleife do statement while (condition) # Nichtabweisschleife for (expr1; expr2; expr3) statement # Zählschleife; entspricht: expr1; # while (expr2) { statement; expr3 } for (var in array) statement # Arraydurchlauf break # Schleifenabbruch continue # starte neue Schleifeniteration delete array[index] # Löschung eines Array-Elements delete array # Löschung eines Arrays exit [ expression ] # Sprung zum END-Block / Programmende { statements } # Anweisungsblock (Gruppierung von Anweisungen) next # starte neue Iteration der Haupteinleseschleife nextfile # Übergang zur nächsten Eingabedatei
Anmerkungen:
# die Datei "getopt.awk" einbinden
@include getopt.awk
\n
für eine Zeilenschaltung, \t
für einen Tabulator
und \\
für den Backslash selbst
# Wirkung der Locale auf AWK-Zahlen awk_command='BEGIN { print 1.5, a, a + 0, a + 1.2, a + 1/3, a "", a == 1.3 }' # Locales: de_DE.UTF-8, de_DE@euro # # Bei numerischen AWK-Konstanten auf der Kommandozeile (Option -v) sowie in # Programm-Quelltexten wird laut Handbuch generell der Dezimalpunkt akzeptiert. LC_ALL=de_DE.UTF-8 awk -v a=1.3 "$awk_command" LC_ALL=de_DE@euro awk -v a=1.3 "$awk_command" # Ausgabe: # 1.5 1.3 1.3 2.5 1.63333 1.3 1 # Durch die Initalisierung einer Variablen (hier konkret "a") auf der # Kommandozeile in den obigen beiden Locales verlor der Dezimalpunkt innerhalb # des AWK-Programms bei Gawk 3.1.3 und 3.1.5 unter Scientific Linux 4.3 seine # Wirkung, so dass Floating-Point-Konstanten nur noch als ganzzahlige # Konstanten gewertet wurden. Die Ausgabe sah dort so aus: # # 1 1.3 1,3 2,3 1,63333 1.3 0 LC_ALL=de_DE.UTF-8 awk "$awk_command" # Ausgabe: # 1.5 0 1.2 0.333333 0 # # Bei den älteren AWK-Versionen sah die Ausgabe so aus: # 1,5 0 1,2 0,333333 0 # Locale: C # Hier trat obiges Problem auch früher nicht auf. LC_ALL=C awk -v a=1.3 "$awk_command" # Ausgabe: # 1.5 1.3 1.3 2.5 1.63333 1.3 1
Typ | STRING | NUMERIC | STRNUM |
STRING | String | String | String |
NUMERIC | String | numerisch | numerisch |
STRNUM | String | numerisch | numerisch |
Beispiel:
# automatische Datenkonvertierung BEGIN { print "String", 12 "" # durch Verkettung mit dem leeren String print "Zahl", "23" + 0 # durch Addition einer Null }
for
kann man alle Indexe eines Arrays durchlaufen, wobei die
Reihenfolge als undefiniert zu betrachten ist
# Zeilen einlesen, die einem Vornamen in Feld 1 und einem Namen in Feld 2 # enthalten, und Ablage der Daten im 2-dimensionalen Array personen, das für # jeden kompletten Namen die Anzahl seines Auftretens angibt; # Index 1 ist der Name, Index 2 der Vorname { vorname = $1 name = $2 personen[name, vorname]++ } END { # der Feld durchlaufen for (pers in personen) { # den kombinierten Index-String in seine durch SUBSEP getrennten # Bestandteile zerlegen: Name und Vorname werden als Elemente des Arrays # indexes gespeichert split(pers, indexes, SUBSEP) # Ausgabe der Indexe sowie des zugehörigen Wertes printf("%s, %s: %d\n", indexes[1], indexes[2], personen[pers]) } print "======" # Test, ob "Meier, Max" im Array vorkommt if (("Meier", "Max") in personen) { # 2 identische Zugriffe auf das Array-Element für "Meier, Max" print "Meier, Max", personen["Meier", "Max"] print "Meier, Max", personen["Meier" SUBSEP "Max"] } }
Beispiel:
# Demo zu lokalen/globalen Variablen # benutzerdefinierte Funktion, die den Inhalt der Variablen "str" an den # globalen Puffer "buffer" anhängt, wobei sie vorher alle Teilstrings löscht, # die aus mindestens 3 aufeinanderfolgenden Zeichen "x" sowie optional # folgenden Leerzeichen und Tabulatoren besteht function add_to_buffer(str, diff, temp) { # diff und temp sind hier lokal temp = str # alle Teilstrings "xxx" löschen gsub(/xxxx*[ \t]*/, "", temp) # den modifizierten String an den Puffer anhängen buffer = buffer temp # Längendifferenz zwischen Orginal und modifizierter Form errechnen diff = length(str) - length(temp) # die maximale Differenz aktualisieren if (diff > max_diff) { max_diff = diff } } # Zeilen, die "xxx" enthalten, an die Funktion add_to_buffer übergeben /xxx/ { add_to_buffer($0) } # Ausgabe des Puffers sowie der maximalen Längendifferenz END { print "max_diff:", max_diff print "Puffer:", buffer }Aufruf-Möglichkeit:
awk -f lokale_vars.awk daten_lokale_vars.txt
# das letzte Feld jeder Eingabezeile ausgeben awk '{ print $NF }' /etc/hosts # die 10. Eingabezeile ausgeben awk 'NR == 10' /etc/passwd # alle Zeilen ausgeben, die entweder "root" oder "false" enthalten # (also Suche wie bei egrep) awk '/root|false/' /etc/passwd # Gesamtzahl der Eingabe-Zeilen ausgeben awk 'END { print NR }' /etc/passwd # das erste und letzte Feld der Eingabezeilen 5 bis 10 ausgeben awk -F: 'NR == 5, NR == 10 { print $1, $NF }' /etc/passwd # das erste und vorletzte Feld der letzten Eingabezeile ausgeben awk -F: 'END { print $1, $(NF-1) }' /etc/passwd # passwd-Eintrag für die aktuelle UID ausgeben awk -F: "\$3 == $UID" /etc/passwd awk -F: -v uid=$UID '$3 == uid' /etc/passwd # Ausgabe von Zeilen mit mehr als 2 Feldern awk 'NF > 2' /etc/hosts # Ausgabe von Zeilen, deren Feld 3 kleiner als 50 ist awk -F: '$3 < 50' /etc/passwd # Ausgabe von Zeilen, deren letztes Feld "false" enthält awk -F: '$NF ~ /false/' /etc/passwd # Ausgabe der Anzahl von Zeilen, deren letztes Feld "false" enthält; # Anmerkung: das Semikolon vor END ist nicht nötig awk -F: '$NF ~ /false/ { anz++ } ; END { print anz }' /etc/passwd # Ausgabe von nicht leeren Zeilen; # NF wird hier im Booleschen Kontext ausgewertet (0 ist FALSE, der Rest TRUE) awk NF /etc/hosts # ermittle, wie oft jeder Wert des letzten Feldes der Eingabezeilen vorkommt awk -F: ' { ANZ[$NF]++ } END { for (w in ANZ) printf("%s: %d\n", w, ANZ[w]) } ' /etc/passwd # tausche Feld 1 und 2 der ersten 10 Eingabezeilen; gib die restlichen Zeilen # unverändert aus; für die Ausgabe nutzen wir eine Regel, die nur aus dem # Muster 1 (also TRUE) besteht und deren Aktion dann print lautet awk -F: ' BEGIN { OFS = FS } NR < 11 { t = $1; $1 = $2; $2 = t } 1 ' /etc/passwd # ermittle die längste Eingabe-Zeile awk ' length($0) > max { max = length($0) ; fn = FILENAME ; fnr = FNR ; line = $0 } END { if (fnr) printf("%d Zeichen in Zeile %d in Datei %s\n%s\n", max, fnr, fn, line) } ' /etc/{hosts,passwd} # gib die UIDs der Nutzer, die die bash als Login-Shell haben, fallend sortiert # aus; anschließend wird noch die Anzahl dieser Nutzer ausgegeben awk -F: ' BEGIN { sort = "sort -rn" } $7 ~ /bash/ { print $3 | sort ; anz++ } END { close(sort) ; printf("Anzahl: %d\n", anz) } ' /etc/passwd # Einrücken der Inhalte von HTML-Absätzen; # Annahme: <p> und </p> stehen jeweils auf getrennten Zeilen und sollen selbst # nicht mit eingerückt werden awk '/<p>/, /<\/p>/ {if ($0 !~ /<\/?p>/) {print " " $0; next}}; 1' p.html # alternativ funktioniert das auch ohne "next", indem die erste Regel, deren # Muster einen Bereich spezifiziert, nur 2 Leerzeichen als Präfix der # Einrückung ausgibt, wogegen der Zeileninhalt dann von der zweiten Regel # ausgegeben wird, die auch der Ausgabe der nicht eingerückten Zeilen außerhalb # der Bereiche dient awk '/<p>/, /<\/p>/ {if ($0 !~ /<\/?p>/) {printf(" ")}}; 1' p.html # die Auswahl von Bereichen kann auch durch eigene Statusvariablen erfolgen awk ' /<p>/ { in_p = 1; print; next } /<\/p>/ { in_p = 0; print; next } in_p { print " " $0; next} 1 ' p.html # obige Regeln kann man kürzer formulieren, wenn man sie geeignet anordnet, so # dass je nach Datenkonstellation die richtige Aktion erfolgt awk ' /<\/p>/ { in_p = 0 } in_p { printf(" ") } /<p>/ { in_p = 1 } 1 ' p.html
Hier findet sich neben der Software auch eine sehr umfangreiche AWK/Gawk-Dokumentation. Die Hinweise unter CAUTION machen auf "Fallen" aufmerksam, z.B. bei den ab Gawk 5 neu eingeführten Namespaces:
CAUTION: This feature described in this chapter is new. It is entirely possible, and even likely, that there are dark corners (if not bugs) still lurking within the implementation. If you find any such, please report them (See section Reporting Problems and Bugs).
This is the version of awk described in The AWK Programming Language, Second Edition, by Al Aho, Brian Kernighan, and Peter Weinberger (Addison-Wesley, 2024, ISBN-13 978-0138269722, ISBN-10 0138269726).
man awk man gawk info awk info gawkDie Info-Seite ist deutlich umfangreicher als die Manual-Seite und enthält u.a. Beispiele.
diverse Gawk-Erweiterungen, u.a. für XML-Handling, PostgreSQL-Zugriff und Nutzung der Bibliothek MPFR (GNU Multiple Precision Floating-Point Reliable Library)
Hinweis: Über eine Suche nach
The_AWK_Programming_Language.pdf
sollten sich auch
PDFs mit dem Volltext des Buches finden lassen.
Stephan Thesing
SED & AWK ge-packt
Editiervorgänge vereinfachen und automatisieren ; Textdateien manipulieren und filtern
zahlreiche Rezepte für Standardaufgaben
mitp-Verlag, 1. Aufl., 2004, 312 S.
ISBN: 3-8266-1427-5