AWK: Textfilterung per Skript


* Einführung * Installation * Paketmanager * Grundlagen * Shells * Entwicklung * KDE * Skriptsprachen * AWK * PHP * Perl * Apache * Veranstaltungen * Schulungen * Bücher * Netzwerk * OpenOffice * OpenSource * Samba

AWK


© <a href="http://www.fotolia.de/p/29003">Ljupco Smokovski</a> - FOTOLIA
Home
AWK-Skripte kompilieren
Einführung
Mit freundlicher Unterstützung von:

Linux-Kurse und Seminare Linux Kurse
Veranstalter des
Linux-Tag
am Bodensee
2007 und 2008

Kontakt
Haftung
Impressum
Problem Hilfe Startseite


Allgemeine Arbeitsweise des AWK


Die allgemeine Arbeitsweise des AWK verdeutlicht das folgende Diagramm:

Für jede Zeile der Eingabedatei werden alle Anweisungen des AWK-Skriptes ausgeführt (mit Ausnahme der unter BEGIN und END stehenden Anweisungen, die wir jedoch erst später behandeln) Das einfachste "sinnvolle" AWK-Skript ist

{ print }

Der Aufruf dieses Skriptes mit einer Textdatei, also zum Beispiel
awk '{ print }' interessantes.txt

gibt jede Zeile der Datei interessantes.txt aus. Damit ist dieser Aufruf identisch mit dem Shell-Skript "cat"

Reguläre Ausdrücke

Reguläre Ausdrücke sind für die meisten Linux/Unix Tools und Skript-Sprachen essentiell,  deshalb sollte sich jeder, der ernsthaft mit Linux arbeiten will, die Mühe machen, sie zu verstehen. Auch wenn einem am Anfang ein Ausdruck wie

^[0-9]+\.[0-9][0-9] *EUR

recht kryptisch vorkommen mag, so kann man es bald mit ein wenig Übung  wie eine Überschrift in der Tageszeit lesen.

Damit die folgenden Beispiele nicht allzu trocken sind, werden wir Heinrich Heines "Lorelei" als zu filternde Beispieldatei wenn möglich verwenden:
 
Heinrich Heine

Lorelei

Ich weiß nicht, was soll es bedeuten,
Daß ich so traurig bin;
Ein Märchen aus alten Zeiten,
Das kommt mir nicht aus dem Sinn.

Die Luft ist kühl und es dunkelt,
Und ruhig fließt der Rhein;
Der Gipfel des Berges funkelt
Im Abendsonnenschein.

Die schönste Jungfrau sitzet
Dort oben wunderbar,
Ihr goldenes Geschmeide blitzet,
Sie kämmt ihr goldnes Haar.

Sie kämmt es mit goldenem Kamme
Und singt ein Lied dabei;
Das hat eine wundersame,
Gewaltige Melodei.

Den Schiffer im kleinen Schiffe
Ergreift es mit wildem Weh;
Er schaut nicht die Felsenriffe,
Er schaut nur hinauf in die Höh.

Ich glaube, die Wellen verschlingen
Am Ende Schiffer und Kahn;
Und das hat mit ihrem Singen
Die Lorelei getan.

Speichern Sie das Gedicht bitte als heine.txt, wenn Sie die folgenden Beispiele nachvollziehen wollen.

Ein regulärer Ausdruck in Linux (ebenso wie in Unix) beschreibt ein Textmuster (text pattern), welches eine Folge von Zeichen definiert.

Ein recht einfaches aber dennoch mächtiges Tool, welches reguläre Ausdrücke benutzt, ist das Kommando grep, bzw. fgrep und egrep.
grep "Schiff" heine.txt
liefert als Ausgabe alle Zeilen, die das Wort "Schiff" enthalten:
Den Schiffer im kleinen Schiffe
Am Ende Schiffer und Kahn;
Stellen wir uns nun vor, dass wir eine Zeile suchen, von der wir wissen, dass das Wort oder "kämmt" drin vorkommt und außerdem das Wort "Kamme". Diese Aufgabe kann man elegant mit einem einfachen regulären Ausdruck lösen. Zunächst muss man wissen, dass eine Punkt "." innerhalb eines regulären Ausdrucks für ein beliebiges Zeichen steht. Ein Stern "*" heißt, dass das vor ihm stehende Zeichen beliebig oft vorkommen kann (also auch überhaupt nicht!). Der Ausdruck ".*" passt also auf eine beliegige Zeichenkette.
grep "kämmt.*Kamme" heine.txt
liefert die gewünschte Zeile:
Sie kämmt es mit goldenem Kamme
Möchten wir nun alle Zeilen herausfiltern, in denen entweder das Wort "Jungfrau" oder das Wort "Lorelei" vorkommt, so lässt sich dies mit grep nicht mehr elegant bewerkstellen. Um diese Aufgabe zu lösen benötigen wir egrep, was jedoch im Prinzip das gleiche ist, als würde man grep mit der Option "-E" aufrufen. Egrep kann erweiterte reguläre Ausdrücke verarbeiten.
egrep "Jungfrau|Lorelei" heine.txt
liefert die beiden Zeilen, die jeweils eines der beiden Suchwörter enthalten:
Die schönste Jungfrau sitzet
Die Lorelei getan.
Sonderzeichen in regulären Ausdrücken und ihre Bedeutung:
  • Der Backslash \ dient als Escape-Zeichen:
    • Steht er vor einem Sonderzeichen, so verleirt es seine Sonderfunktion und wird wie das entsprechende Alphazeichen betrachtet.
    • Für bestimmte alphabetische Zeichen für es zu einer Sonderfunktion, wie z.B. \r für ein Return also ASCII 13 und \n Linefeed ASCII 10
  • ^ markiert einen Stringanfang: Das Suchmuster ^Gewaltige.* paßt auf alle Zeichenketten, die mit dem Wort Gewaltig beginnen.
  • $ ist analog fürs Stringende.
  • Der Punkt . paßt auf genau ein beliebiges Zeichen außer dem Zeilenendezeichen \n. Beispiele siehe oben.
  • Die öffnende eckige Klammer [ leitet eine Zeichenmenge ein.
  • | trennt zwei Alternativen, wie wir oben mit "Jungfrau|Lorelei" gesehen haben.
  • ? steht für null oder eine Wiederholung des vorhergehenden Ausdrucks.
  • Der Stern * steht für für Null oder mehr Wiederholungen des vorhergehenden Zeichens oder Ausdrucks.
  • Das Pluszeichen steht für für mindestens eine Wiederholung des vorhergehenden Ausdrucks.
Eckige Klammern leiten eine Menge von Zeichen ein. Genau ein Zeichen aus dieser Menge, kann im String an dieser Stelle vorkommen. Innerhalb einer soclen Menge, haben einige Sonderzeichen wieder Sonderbedeutungen: ^ als erstes Zeichen einer Zeichenmenge negiert die Zeichenmenge. Dies gilt jedoch wirklich nur, wenn ^ wirklich als ersten Zeichen steht. Mit - kann man einen Bereich definieren, zB. [0-9] definiert alle Ziffern von 0 bis 9.

Verzweigungen

Verzweigungen sind in Awk analog zu anderen Programmiersprachen, wie z.B. C, gelöst, d.h. sie sind wie folgt realisiert:

if {Bedingung}
 {Anweisung1}
else
{Anweisung2}
Der else-Zweig ist nicht zwingend notwendig, also optional. Verwzeigungen können selbstverständlich auch verschachtelt werden.

Schleifen

Auch die Schleifen sind in Awk analog zu anderen Programmiersprachen designed.

While

Die while-Schleife gibt es in zwei Varianten, zum einen als annehmende (Schleife wird mindestens einmal durchlaufen) und als abweisende (Schleife wird nur bearbeitet, wenn die Precondition erfüllt ist) Schleife

annnehmend:
do 
{ Anweisung1 ; Anweisung2 ; ... }
while
 ( Bedingung )


abweisend:
while ( Bedingung )
{ Anweisung1; Anweisung2 ; ...

For

Der for-Schleife müssen drei Argumente übergeben werden:

for ( Beginn-Anweisung ; Bedingung ; Endanweisung )
  { Anweisung1 ; Anweisung2 ; ... }


  • Die Beginn-Anweisung wird vor der ersten Ausführung  der Schleife einmalig ausgeführt.
  • Die Bedingung wird vor jedem Schleifendurchlauf auf ihren Wahrheitsgehalt geprüft.
  • Die Endanweisung wird am Ende eines jeden Schleifendurchlaufs überprüft.

Records und Fields

Awk arbeitet unter der Annahme, dass die Eingabe strukturiert ist. Im einfachsten Fall kann dies zum Beispiel bedeuten, dass jede Eingabezeile einem Rekord entspricht und die einzelnen Wörter (Fields oder Felder) durch Tabulatoren oder Leerzeichen (Blanks) getrennt sind. Die Zeichen, die die Felder voneinander trennen werden auch häufig als Delimiter bezeichnet.

Hat man z.B. eine Datei mit folgendem Inhalt (Name und Telefonnummer):

Peter 2543
Frank 5498
Eva 9878
Jane 3254

Nehmen wir an, dass diese Datei unter dem namen phone.txt gespeichert ist.

awk '{ print $2, $1 }'  phone.txt

Obiges Kommando-Zeilen-Skript liefert z.B. folgende Ausgabe:

2543 Peter
5498 Frank
9878 Eva
3254 Jane
Mit der Variablen $1 spricht man innerhalb des Awk-Skriptes das erste Feld an, entsprechend mit $2 das zweite und so weiter.
$0 ist ein Bezeichner für die ganze Zeile. Das folgende Skript "echoe"t deshalb lediglich die Datei:

awk '{ print $0 }'  phone.txt

Arrays

Die Namen von AWK-Arrays unterliegen der gleichen Syntax wie andere Variable. Auch wenn die Arrays in AWK denjenigen in C sehr ähnlich sind, gibt es wesentliche Unterschiede:
  • Wie alle anderen Variablen auch, müssen Arrays in AWK nicht initialisiert werden, insbesondere muss die Größe nicht vorher festgelegt werden.
  • Als Indezes können beliebige Strings verwendet werden und Integer als Indizes müssen nicht aufeinanderfolgend sein!
  • Es handelt sich um assoziative Arrays, d.h. jedes Array ist eine Menge von Paaren bestehend aus Index und zugehörigem Wert.

Im folgenden Beispiel werden verschiedene Temperaturen dem Array "Temperatur" zugewiesen.

Temperatur["Singen"] = 24
Temperatur["Konstanz"] = 22
Temperatur["Redmond"] = -24

Will man sich das gesamte Array ausgeben lassen, so geht dies wie folgt:

for (x in Temperatur) {
print "Temperatur in "x": "Temperatur[x]
}

Worthäufigkeiten in einem Text


Das folgende Skript ermittelt die Worthäufigkeiten in einer Datei. Jedes Wertepaar einer Ausgabezeile entspricht: (Länge der Worte, Häufigkeit)

Anmerkung: NF ist eine vordefinierte Variable, die die Anzahl der Felder pro Zeile angibt. Jedes Feld (field) ist standardmäßig durch Leerzeichen, Zeilenanfang oder Zeilenende begrenzt. Man kann den Feld-Seperator durch folgende Anweisung von einem Leerzeichen z.B. zu einem Komma ändern: FS = ","

{
for (i=1; i <= NF; i++) {
wlen[length($i)]++
}
}

END {
for (x in wlen) {
print x" "wlen[x]
}
}

Praxisbeispiele

Jede zweite Zeile, gerade Felder

In der Praxis kommt es häufig vor, dass man die geraden oder ungeraden Zeilen einer Datei ausgeben bzw. zur Weiterverarbeitung ansprechen will.

Dazu genügt ein erstaunlich kurzes Skript:

Für die Ausgabe der Zielennummern mit geradem Index (Achtung: Zählung beginnt bei 0):
 awk 'NR%2' Dateiname
Für die Ausgabe der Zielennummern mit ungeradem Index:
 awk '(NR+1)%2' Dateiname
Einem Anfänger mit AWK fällt wohl folgende auch funktionierende aber weniger elegante Lösung zuerst ein:
awk 'NR % 2 == 0 { print }' FILENAME
Wie sieht es aber aus, wenn man nur die geraden Feldelemente einer Zeile ausgeben will. Das folgende Skript löst dieses Problem. NF ist eine Systemvariable, die die Anzahl der Feldelemente enthält. Man beachte auch, dass die Zählung der Feldelemente mit 1 beginnt, also $1, $2, $3, ..:
{
(NR+1)%2
for (i=1; i<=NF; i++) {
if (i%2) printf $i" ";
}
printf "\n"
}



© Copyright 2007 - 2010, Bernd Klein mit freundlicher Unterstützung von Bodenseo, Linux-Kurse und Schulungen,
Foto linke Seite (Mann mit Strick und Colt): Foto: © Ljupco Smokovski, fotolia 984022