Home / LinuxUser / 2002 / 10 / Praktisch und nützlich: AWK

Newsletter abonnieren

Lies uns auf...

Folge LinuxCommunity auf Twitter

Top-Beiträge

Mandriva gibt Distribution in die Hände der Community
(268 Punkte bei 24 Stimmen)
Neues vom Systemd
(161 Punkte bei 4 Stimmen)
Mandriva in Nöten
(161 Punkte bei 4 Stimmen)

Heftarchiv

LinuxUser Heftarchiv

EasyLinux Heftarchiv

Ubuntu User Heftarchiv

Ubuntu User Heftarchiv

Partner-Links:

Shopping
Topsuche
 
Yatego Deutschlands größte Shoppingmall. 10000 Shops,
3.5 Mio Artikel. Alle Bestseller, Servertechnik und Technik Themenwelten.

Notebooks und Netzwerkhardware bei Mercateo günstig kaufen.
Internet Telefonie mit VoIP Telefonen von Gigaset
Das B2B Portal www.Linx.de informiert über Produkte und Dienstleistungen.
Günstige Digitalkameras finden Sie im Preisvergleich.

Programmierhappen für Zwischendurch

Praktisch und nützlich: AWK

Vergleichsausdrücke systematisch

Neben den Sonderfällen BEGIN und END hatten wir bereits Regexps in Vergleichsausdrücken eines AWK-Programms besichtigt. Das ist eine sehr leistungsstarke Methode! Nur muss man sie gut beherrschen …

Leider sind logische Operatoren wie UND, ODER und NICHT in Regexps bestenfalls unübersichtlich. Daher erlaubt AWK eine wichtige Vereinfachung: Statt den logischen Operator irgendwie innerhalb des regulären Ausdrucks unterzubringen, dürfen Sie mehrere Regexps angeben und diese miteinander kombinieren.

Angenommen, Sie wollen einen bestimmten Befehlsblock nur auf solche Zeilen einer Datei anwenden, die sowohl das Wort "blassblau" als auch "Rose" enthalten. Sie möchten sich aber nicht festlegen, in welcher Reihenfolge die Wörter stehen sollen. Mit einer reinen Regexp müssten Sie beide Alternativen – "blassblau" zuerst oder "Rose" zuerst – berücksichtigen:

awk '/(blassblau.*Rose)|(Rose.*blassblau)/ { print $1 }' datei

Wenn mehrere Bedingungen zusammenkommen, wird das schnell richtig kompliziert. Mit logischen Operatoren außerhalb der Regexps geht es einfacher:

awk '/blassblau/ && /Rose/ { print $1 }' datei

Tabelle 1: Vergleichsausdrücke für AWK

BEGIN Befehlsblock wird bei Programmstart ausgeführt.
END Befehlsblock wird vor Programmende abgearbeitet.
/regexp/ Befehlsblock kommt nur dann zum Zuge, wenn der aktuelle Datensatz den regulären Ausdruck regexp enthält.
! /regexp/ Befehlsblock wird nur ausgeführt, wenn der aktuelle Datensatz den regulären Ausdruck regexp nicht enthält.
/regexp1/ && /regexp2/ UND-Verknüpfung der beiden regulären Ausdrücke.
/regexp1/ || /regexp2/ ODER-Verknüpfung der beiden regulären Ausdrücke.
/regexp1/,/regexp2/ /regexp1/ und /regexp2/ kennzeichnen einen ersten und einen letzten Datensatz. Der Befehlsblock wird für alle Datensätze innerhalb dieses Bereichs ausgeführt.
/regexp1/ ? /regexp2/ : /regexp3/ Wenn regexp1 passt, dann gilt regexp2 als Vergleichsausdruck, sonst regexp3. Beispiel: /Rose/ ? /rot/ : /blau/. Enthält ein Datensatz das Wort "Rose", muss zusätzlich rot darin vorkommen, in den übrigen Datensätzen hingegen blau. Der Befehlsausdruck wird also für alle roten Rosen und zusätzlich für alle blauen Nicht-Rosen ausgeführt.
(...) Alle logischen Operatoren sind nicht nur auf reguläre Ausdrücke anwendbar, sondern auch auf komplexe Ausdrücke, die ihrerseits durch logische (oder andere) Operatoren gebildet wurden. Dabei empfiehlt sich zur Verdeutlichung der Rangfolge der Einsatz von runden Klammern.

AWK nach Maß

Bislang haben wir immer nur einfache Textdateien bearbeitet. Die einzelnen Datensätze waren dabei durch Zeilenenden voneinander getrennt, d. h., jede Zeile enthielt genau einen Datensatz. Zwischen den Feldern eines Datensatzes befand sich eine beliebige Menge Whitespace, also eine Abfolge von Leerzeichen und/oder Tabstopps. Dies ist die häufigste Variante und damit auch die Voreinstellung. Sie können diese Prämissen jedoch nach Belieben verändern.

Angenommen, Sie bearbeiten eine Textdatei. In bester LaTeX-Manier haben Sie nach jedem Satzende zwei Leerzeichen gelassen. Nun können Sie ganze Sätze, unabhängig von Zeilenumbrüchen, als "Datensatz" behandeln. Dazu definieren Sie die Trennung zwischen Datensätzen (den so genannten Record Separator) als Folge zweier Leerzeichen.

Den Record Separator speichert AWK – wie fast alle Konfigurationsoptionen – in einer besonderen Variable, in diesem Fall in RS. Wenn Sie diese verändern möchten, haben Sie mehrere Möglichkeiten: Zum einen lassen sich beim Aufruf des Interpreters Variablen über die Kommandozeilenoption -v (identisch mit --assign) setzen:

awk -v RS="  " intelligent.awk < mein_toller_roman.tex

Innerhalb eines AWK-Programms können Sie die Variable natürlich direkt modifizieren, ebenso wie Sie es oben bei den Zählvariablen gesehen haben. Für die meisten Anwendungen ist der BEGIN-Block die ideale Stelle, um derartige Konfigurationsoptionen einzustellen:

BEGIN{
  RS = "  ";
  maxcount = 10000;
}

Eine zweite, häufig genutzte Konfigurationsmöglichkeit betrifft die Trennung der einzelnen Felder innerhalb des Datensatzes. Dieser Field Separator steht – Sie ahnen es – in der Variablen FS. Voreinstellung ist FS=" ", ein einzelnes Leerzeichen, das AWK als "beliebigen Whitespace" behandelt. Wenn Sie FS ein anderes Zeichen (z. B. ein Komma) zuweisen, kommt genau dieses Zeichen als Feldtrenner zum Einsatz. Mehrere Zeichen sind ebenso möglich, werden dann aber als regulärer Ausdruck betrachtet. Beim Sonderfall FS="" sortiert AWK jedes Zeichen des Datensatzes in ein eigenes Feld.

Natürlich dürfen Sie diese Variablen aus dem laufenden Programm heraus umkonfigurieren. Als praktisches Beispiel wäre die Bearbeitung einer E-Mail denkbar, bei der man zunächst den gesamten Header als ersten Datensatz behandelt. Dazu setzt man RS="", wodurch AWK (eine oder mehrere) Leerzeilen als Trenner zwischen Datensätzen behandelt. Anschließend definiert man RS="\n" und liest den Rest der Mail zeilenweise ein.

Tabelle 2: Nützliche Spezialvariablen

RS "Record Separator", Trennzeichen (oder regulärer Ausdruck) zwischen Datensätzen. Sonderfall: RS="" behandelt Leerzeilen als Trenner.
FS "Field Separator", Trennzeichen (oder regulärer Ausdruck) zwischen den Feldern eines Datensatzes. Sonderfälle: FS=" " steht für eine beliebige Folge von Whitespace (mindestens ein Zeichen); FS="" weist jedem Zeichen ein eigenes Feld zu.
FIELDWIDTHS Für die Bearbeitung von Dateien mit fester Feldgröße. FIELDWIDTHS enthält die Feldbreiten, getrennt durch Leerzeichen.
NF Zahl der Felder im aktuellen Datensatz.
NR Nummer des aktuellen Datensatzes: Beim ersten Datensatz ist NR==1 usw.
OFS und ORS Setzen Feld- und Satztrenner für die Ausgabe mit print.
IGNORECASE Wenn diese Variable auf einen anderen Wert als 0 gesetzt ist, ignorieren alle Suchoperatoren die Groß-/Kleinschreibung.

Kasten 1: Die AWK-Befehlssprache

AWK enthält die üblichen Operatoren, Funktionen und Konstrukte zur Flusskontrolle, die man aus Sprachen wie C zur Genüge kennt.

Operatoren

Bei den Operatoren gilt die übliche Rangfolge. Auf die Klammern, die Vorrang vor allem anderen haben, folgen die mathematischen Operatoren ++ (Addition von 1), --, der Potenzoperator in den Schreibweisen ^ oder **, die unären Operatoren +, - und !; dann kommen *, / und % (Modulo) sowie + und -. Als relationale Operatoren gibt es < und <=, > und >= sowie != und ==. Für Vergleiche mit regulären Ausdrücken ist der Operator ~ (negiert !~) zuständig. Als logische Operatoren kommen &&, || und ?: zum Einsatz. Den geringsten Vorrang haben die Zuweisungsoperatoren = sowie die C-Sonderformen += ("addiere den Wert rechts des Operators zum alten Wert der linksstehenden Variable und weise dieser das Ergebnis zu"), -=, *=, /=, %= und ^=.

Funktionen

Auch AWK bringt die obligatorischen mathematischen Funktionen wie sin, cos usw. mit. Interessanter sind meist jedoch Routinen zur Interaktion mit Dateien und anderen Programmen:

getline <konfigurationsdatei

liest einen Datensatz aus konfigurationsdatei und zerlegt ihn gemäß den aktuellen Einstellungen in Felder. Explizit öffnen muss man die Datei in AWK nicht: Entweder sie ist bereits offen, dann wird einfach der nächste Datensatz gelesen, oder sie ist noch nicht offen, dann öffnet der Interpreter sie und liest den ersten Datensatz.

Für den umgekehrten Weg, das Schreiben in eine Datei, benötigt man keinen besonderen Befehl. Hängen Sie an ein print- oder printf-Kommando wie in der Shell ein oder zwei Größer-als-Zeichen und einen Dateinamen an. Die Datei wird überschrieben (bei >) bzw. verlängert (bei >>).

Einen Unix-Prozess starten Sie nach dem Muster

system("/usr/sbin/pppd call telekom
")

Wenn Sie Daten per Pipe (|) an ein anderes Programm schreiben möchten, kümmert sich AWK für Sie um die Erzeugung des Kind-Prozesses. Ein Beispiel zum Nichtausprobieren:

printf "%s\n%s\n%s\n\n%s\n",
       "From: me@somewhere.net",
       "To: "$1,
       "Subject: Dies ist kein Spam",
       "Wirklich nicht!  Aber wenn Sie schnell zu Geld kommen wollen, folgen Sie\ndiesen einfachen Anweisungen …" \
       | "/usr/lib/sendmail -t -U"

Ein paar weitere Funktionen erlauben String-Vergleiche mit und ohne reguläre Ausdrücke. Hierzu gehören gensub und gsub (Suchen und Ersetzen mit regulären Ausdrücken) oder index und match (Suchen nach Text ohne Regexps).

Flusskontrolle

Bedingungen stellen Sie mit if-else-Konstrukten wie

if (NR==2) {
    print "Bin jetzt im zweiten Datensatz!"
} else {
    print "Bin woanders!"
}

Auch bei den Schleifen dürften Sie Bekannte vorfinden, wenn Sie bereits einmal programmiert haben:

for (count=0; count<NF; count++) {
    print "noch ein Wort, "
}
while (count>0) {
    print "tue irgendwas"; count--
}
do {
    print "tue irgendwas"; count--
} while (count>0)

Vorzeitig heraus kommen Sie aus einer Schleife zum Beispiel so:

while (count>0) {
    print "tue irgendwas";
    if (bedingung
) break
}
do {
    count--;
    if (bedingung
)
        continue;
    print "tue irgendwas"
} while (count>0)

Eigene Funktionen@KL:Eingefleischte AWK-Programmierer packen selbstdefinierte Funktionen wie

function max (a, b) {
    return a > b ? a : b;
}

angeblich richtiggehend in Bibliotheken. Da man an der Kommandozeile beliebig viele Programmdateien angeben darf, steht der Einbindung solcher Libraries in eigene Programme prinzipiell nichts im Weg:

/Ergebnis/ {
    print "Der Gewinner hat " max($2, $3) " Tore geschossen."
}

Aber ganz im Vertrauen gesprochen: Wenn ein Programm so kompliziert wird, dass man es in mehrere Module zerlegen muss, sollte man dann nicht irgendwann an eine größere Programmiersprache denken?

Nun fehlen in diesem Überflug über die Features von AWK eigentlich nur noch die Arrays. Das Wichtigste zu diesem Thema demonstriert Listing 5, ein kleines Programm zur CD-Verwaltung (selbstverständlich im Internet verfügbar unter [1]).

Es versteht nur ein paar Befehle: buy Gruppe CD-Titel (beide Argumente dürfen keinen Whitespace enthalten) nimmt eine CD als gekauft in die Datenbank auf, indem der entsprechende Eintrag im zweidimensionalen Array cds mit der Zahl 1 markiert wird. Umgekehrt löscht sell Gruppe CD-Titel die CD wieder aus dem Array heraus. check Gruppe CD-Titel überprüft, ob eine bestimmte CD vorhanden ist.

print gibt eine Liste aller CDs aus. Beachten Sie: Variablen werden in AWK erzeugt, sobald sie das erste Mal benutzt werden. Wenn check auf eine Array-Position einer bislang unbekannten CD zugreift, wird diese tatsächlich erzeugt, wenn auch mit dem Wert null! print muss in der for-Schleife daher zusätzlich prüfen, ob eine gespeicherte CD auch tatsächlich vorhanden ist.

Das Konstrukt for (i in cds) funktioniert leider nicht für einzelne Dimensionen eines Arrays. Bei AWK-Arrays handelt es sich nämlich immer um Hashes, die über eine Zeichenkette indiziert werden. Mehrdimensionale Arrays gibt es daher nur über einen Trick: Die beiden Schlüssel werden über die (ganz am Anfang gesetzte) Variable SUBSEP aneinandergehängt und bilden damit einen gemeinsamen Schlüssel. Nach Eingabe von buy Supertramp Autobiography wird also ein neuer Schlüssel Supertramp\tAutobiography gebildet und die Kennung 1 unter diesem Schlüssel gespeichert.

AWK verwendet normalerweise das Zeichen \034 für die Schlüsselgenerierung in mehrdimensionalen Arrays. Indem Sie SUBSEP auf einen Tabstopp umdefinieren, sorgen Sie für eine halbwegs schöne Ausgabe der CD-Liste.

Listing 5

Demo-Programm "CD-Verwaltung"

#!/bin/awk -f
BEGIN {
    IGNORECASE = 1;
    SUBSEP = "\t";
}
# Befehl buy Gruppe Titel
/^buy/ {
    cds[$2, $3] = 1;
}
# Befehl sell Gruppe Titel
/^sell/ {
    delete cds[$2, $3];
}
# Befehl check Gruppe Titel
/^check/ {
    if (cds[$2, $3])
        print "habe ich"
    else
        print "unbekannt";
}
# Befehl print
/^print/ {
    for (i in cds)
        if (cds[i])
            print i
}
# Befehl new (lösche alles)
/^new/ {
    delete cds;
}
# Befehl quit (Ende)
/^quit/ {
    exit;
}
Einem Freund empfehlen    Druckansicht Bookmark and Share
Kommentare

Hits
Wertung: 55 Punkte (2 Stimmen)

Schlecht Gut

Infos zur Publikation

Infos zur Publikation

LinuxUser 06/2012

Aktuelle Ausgabe kaufen:

Heft bestellen Heft als PDF kaufen

LinuxUser erscheint monatlich und kostet in der Nomedia-Ausgabe EUR 5,50 und mit DVD EUR 8,50. Weitere Informationen zum Heft finden Sie auf der LinuxUser-Homepage.

Im LinuxUser-Probeabo erhalten Sie drei Ausgaben für 3 Euro. Das Jahresabo (ab EUR 56,10) können Sie im LNM-Shop bestellen.

Tipp der Woche

Adobe AIR
Adobe-AIR-Programme installieren und (manuell) starten
Tim Schürmann, 14.05.2012 13:09, 0 Kommentare

Es gibt sie noch: neue Anwendungen, die Adobes Integrated Runtime voraussetzen. Aktuellstes und vermutlich auch größtes Beispiel ist das Adventure Botanicula

Aktuelle Fragen

gibt es ein Kommandozeilen Tool, um ein X11-Fenster in ein Anderes einzubetten?
GoaSkin , 21.05.2012 16:44, 0 Antworten
Das XEmbed-Protokoll ist u.A. dazu gedacht, dass man eine X11-Anwendung in eine andere wie ein Wi...
Apache2, Options -Indexes geht nicht
no no, 12.05.2012 19:01, 8 Antworten
Habe in apache2.conf folgendes stehen: Options -Indexes ...
LInux auf Dell LS H500
Andreas Endresl, 09.05.2012 08:54, 2 Antworten
Habe einen alten Dell Latitude LS H500 nur mit ext. Floppy und CD es geht nur immer eines von den...
Datenwiederherstellung unter Ubuntu 12.04 mit "Simple Backup" nach Umzug von Linux Mint
Christian Lottmann, 07.05.2012 13:33, 0 Antworten
Vor dem Umzug auf Ubuntu 12.04 habe ich unter Linux MInt mit "Simple Backup" voll (15.4.2012) und...
DKMS für den propritären NVIDIA-Treiber
Commander Data, 26.04.2012 22:02, 2 Antworten
Hallo an die Gemeinde. Ich habe hier ein interessantes Stück openSuSE gefunden. http://forums.op...