3 awk und sed awk..........................................................................................................66 sed ...........................................................................................................73 Übersicht über die regulären Ausdrücke .......................................76 66 awk: Aufrufsyntax, Optionen, Struktur eines awk-Programms, Reguläre Ausdrücke awk Aufrufsyntax Folgende awk-Aufrufe sind möglich: (1) awk [optionen] 'awk-programm' d1 d2 ... (2) awk [optionen] -f datei d1 d2 ... Bei (1) wird das awk-Programm direkt auf der Kommandozeile (in ´...´) angegeben und bei (2) befindet sich das awk-Programm in datei. d1, d2 usw. sind die Namen der zu bearbeitend. DatenDateien oder Zuweisungen der Form var=text. Eine Zuweisung wird immer erst dann vorgenommen, wenn alle vorherigen Argumente abgearbeitet sind. Somit erlaubt diese Art der Zuweisung, Variablen zu verändern, bevor und nachdem eine Datei durch das awk-Programm bearbeitet wurde. Optionen -f datei awk-Programm befindet sich in datei. -Fregausdr setzt die builtin-Variable FS und legt somit über den regulären Ausdruck regausdr Trennzeichen für Eingabefelder fest. -- zeigt explizit das Ende der Optionen-Liste an; muss nicht angegeben werden. -v var=text setzt die awk-Variable var auf wert, noch bevor die erste Aktion des awk-Programms ausgeführt wird. Struktur eines awk-Programms Ein awk-Programm ist eine Folge von pattern { Aktion } pattern { Aktion } :::: und Funktionsdefinitionen der Form: function fname(param-liste) { anweisung } Fehlt das pattern zu einer { Aktion }, so wird die entsprechende Aktion für alle Eingabezeilen ausgeführt. Ist nur ein pattern, aber keine { Aktion } angegeben, so wird jede durch pattern abgedeckte Eingabezeile einfach ausgegeben. pattern { Aktion }-Angaben und Funktionsdefinitionen werden durch neue Zeilen oder Semikolons voneinander getrennt. Pattern Abhängig davon, ob eine pattern-Vorgabe für eine Eingabezeile paßt, wird die zugehörige {Aktion} (falls angegeben) ausgeführt. Es werden 6 Typen von pattern-Vorgaben unterschieden: BEGIN { anweisungen } anweisungen werden einmal ausgeführt, bevor die erste Eingabezeile verarbeitet wird. END { anweisungen } anweisungen werden einmal ausgeführt, nachdem die letzte Eingabezeile verarbeitet wurde. ausdruck { anweisungen } anweisungen werden für jede Eingabezeile ausgeführt, für die der ausdruck WAHR (verschieden von 0) ist. /regulärer ausdruck/ { anweisungen } anweisungen werden für jede Eingabezeile ausgeführt, welche einen String enthält, für den der reguläre ausdruck zutrifft. zusammengesetztes pattern { anweisungen } Ein zusammengesetztes pattern verknüpft Ausdrücke mit && (AND), || (OR), ! (Negation) oder (..) (runde Klammer). anweisungen werden für jede Eingabezeile ausgeführt, für die das zusammengesetzte pattern paßt. pattern1, pattern2 { anweisungen } pattern1,pattern2 definiert einen Bereich von der ersten Zeile, auf die pattern1 paßt, bis einschließlich der Zeile, auf die pattern2 paßt; die anweisungen werden für alle Zeilen aus diesem Bereich ausgeführt. BEGIN und END dürfen nicht mit anderen patternTypen kombiniert werden und zu ihnen muss immer { Aktion } angegeben sein. Ein Bereichspattern (pattern1,pattern2) darf nicht Teil eines anderen pattern sein. Reguläre Ausdrücke siehe "Übersicht über reguläre Ausdrücke" auf Seite 76; dort nicht erwähnt sind die möglichen Escape-Sequenzen in regulären Audrücken: \a Alert, Klingelton \b Backspace, Zurücksetz-Zeichen \f Formfeed, Seitenvorschub \n Newline, Zeilenvorschub \r Return; Wagenrücklauf \t Tabulator \v Vertikaltabulator \xhh Zeichen mit hexadez. ASCII-Wert hh \ddd Zeichen mit oktalem ASCII-Wert ddd \z anderes Zeichen z, welches hier nicht vorkommt; z.B. \\ für \ und \" für " Aktionen, Eingabe mit getline, Ausgabeanweisungen, printf, sprintf Aktionen pattern legen fest, wann etwas für Eingabezeilen zu tun ist, und Aktionen, die immer mit {..} zu klammern sind, legen fest, was für Eingabezeilen zu tun ist. Sind in einer Aktion mehrere Anweisungen angegeben, so müssen diese durch neue Zeilen oder Semikolons voneinander getrennt sein. Anweisungen in Aktionen können sein: ausdrücke, mit Konstanten, Variablen, Zuweisungen, Funktionsaufrufen usw. break continue delete array-Element do anweisung while (ausdruck) Ein-/Ausgabe-Anweisungen exit bzw. exit ausdruck for (ausdruck ; ausdruck ; ausdruck) anweisung for (variable in array) anweisung if (ausdruck) anweisung if (ausdruck) anweisung else anweisung next [awk-Progr. sofort mit nächster Eingabezeile von Beginn an wieder starten] while (ausdruck) anweisung return bzw. return ausdruck { anweisungen } In einer if-else Anweisung muss erste anweisung mit Semikolon abgeschlossen oder mit { } geklammert sein, wenn sich folgendes else in der gleichen Zeile befindet. Ebenso muss für die do-while-Anweisung die Schleifen-anweisung mit Semikolon abgeschlossen oder mit { } geklammert sein, wenn sich das abschließende while in der gleichen Zeile befindet. Eingabe mit Funktion getline getline liest das nächste Eingabe-Record. Als Rückgabewert liefert getline: 1, wenn ein Record gelesen werden konnte, 0, wenn das Dateiende erreicht wurde, oder -1, wenn beim Lesen ein Fehler auftrat. Ausdruck Setzt die Variable getline getline var getline <dateiname getline var <dateiname kommando | getline kommando | getline var $0, NF, NR, FNR var, NR, FNR $0, NF var $0, NF var Direktes Lesen von der Standardeingabe ist mit getline ......< "-" möglich. 67 Ausgabeanweisungen print gibt $0 auf die Standardausgabe aus (entspricht print $0) print ausdr1, ausdr2, ... gibt Ausdrücke ausdr1, ausdr2, ... durch Inhalt von OFS getrennt auf Standardausgabe aus; abschließend wird noch der Inhalt von ORS ausgegeben. Die print-Ausgabe kann durch Angabe von >, >> oder | auch in eine Datei oder eine Pipe umgelenkt werden. printf(format, ausdruck1, ausdruck2, ...) printf verhält sich ähnlich wie print, außer dass bei der Ausgabe entspr. format-Angabe Formatierung und kein automatischer Zeilenvorschub stattfindet. close(dateiname) close(kommando) schließt Verbindung zwischen einer Ausgabe und einem dateinamen bzw. kommando. Die Argument-Liste für printf muss nicht wie in C mit (..) geklammert sein. Klammern sind bei print und printf nur nötig, wenn ein Ausdruck in Arg.-Liste einen Vergleichsoperator enthält, da sonst awk nicht erkennen kann, ob es sich bei < oder > um Umlenkungs- od. Vergleichsoperator handelt. Formatieren mit printf / sprintf printf und sprintf werden zur Formatierung benötigt. String format kann sich dabei zusammensetzen aus: - Zeichen (nicht %); werden so ausgegeben - Umwandlungsvorgaben (beginnen mit %) und beziehen sich auf die folgenden Argumente Eine Umwandlungsvorgabe setzt sich wie folgt zusammen: % F W G U wobei nur Umwandlungszeichen U anzugeben ist; F, W und G sind optional. F = [-]; - bedeutet linksbündige Justierung. W = [zahl] Mindestanzahl der auszugebenden Zeichen. Wenn Wert des entspr. Arguments weniger Stellen als zahl hat, wird mit Leerzeich. oder mit 0 (wenn zahl mit 0 beginnt) aufgefüllt. G = [.zahl] zahl legt für Strings die maximale Anzahl von auszugebenden Zeichen und für Zahlen die auszugebenden Nachkommastellen fest. U = Umwandlungszeichen c d e f g o s x % ASCII-Zeichen vorzeichenbehaftete ganze Dezimalzahl Gleitpunktzahl in Form [-]d.ddddddE[+-]dd Gleitpunktzahl in Form [-]ddd.dddddd e- oder f-Form, abh. davon, welche kürzer vorzeichenlose Oktalzahl String vorzeichenlose Hexadezimalzahl für das Zeichen % 68 awk: Konstanten, Variablen Konstanten String-Konstanten Eine String-Konstante ist eine Zeichenkette, die mit " .. " geklammert ist, z.B.: "Hans". String-Konstanten können auch Escape-Sequenzen enthalten, z.B. "\b\n\"Hallo\"!\a". Numerische Konstanten Diese Konstantengruppe umfaßt ganze Zahlen (wie z.B. 4700) und Gleitpunktzahlen (wie z.B. 3.14, 0.12e-4 oder 413.5E3). Verschiedene Darstellungen der gleichen Zahl (wie z.B. 1e4 und 10000) besitzen den gleichen Wert. Alle Zahlen werden in Gleitpunktdarstellung gespeichert, wobei die Genauigkeit von der entsprechenden Maschine abhängt. Variablen Eine Variable hat einen String-Wert oder einen numerischen Wert oder auch beides. Da awkVariablen nicht deklariert werden, wird der Datentyp implizit bestimmt. Wenn notwendig, konvertiert awk einen String in einen numerischen Wert und umgekehrt. Eine nicht explizit initialisierte Variable hat den Wert "" (Null-String) bzw. den numerischen Wert 0. Es existieren 3 Arten von Variablen: Benutzerdefinierte Variablen Der Name einer solchen Variablen muss mit einem Buchstaben oder Unterstrich beginnen. Die weiteren Zeichen dürfen Buchstaben, Unterstriche oder Ziffern sein. Feld-Variablen Feld-Variablen enthalten die einzelnen Felder der jeweiligen Eingabezeile (Record). Sie können genau wie andere Variablen verwendet werden, insbesondere können ihnen auch neue Werte zugewiesen werden. Die Trennzeichen für die einzelnen Felder einer Eingabezeile sind in der builtin-Variable FS angegeben. Ein Zugriff auf den Wert einer Feld-Variablen erfolgt durch $ gefolgt von der Feld-Nummer: $1, $2, ..., $NF. $0 steht immer für die ganze Zeile (Record). Builtin-Variablen Variable ARGC Bedeutung default Anzahl der Argumente in der Kommandozeile ARGV Array, das Argumente aus Kommandozeile enthält ENVIRON Array, das Werte der Environment-Variablen enthält; Indizes dieses Arrays sind die Namen der Environment-Variablen FILENAME Name der akt. Eingabedatei FNR (File Number of Records) Anzahl der gelesenen Eingabezeilen in aktueller Eingabedatei "" FS (Field Separator) Trennzeichen für Eingabefelder NF (Number of Fields) Anzahl der Felder in der momentanen Eingabezeile NR (Number of Records) Anzahl der bisher gelesenen Eingabezeilen (über mehrere Eingabedateien weitergezählt) "%.6g" OFMT (Output ForMaT) Ausgabe-Format für Zahlen "" OFS (Output Field Separator) Trennzeichen für Ausgabefelder "\n" ORS (Output Record Separator) Trennzeichen für Ausgabezeilen RLENGTH Länge eines Strings, der durch Aufruf von match ermittelt wurde "\n" RS (input Record Separator) Trennzeichen für die Eingabezeilen RSTART Start des Strings, der durch Aufruf von match ermittelt wurde "\034" SUPSEP (SUPscript SEParator) Trennzeichen für die Indizes bei mehrdimensionalen Arrays FILENAME wird jedesmal neu gesetzt, wenn eine neue Eingabedatei eröffnet wird. FNR, NF und NR werden jedesmal neu gesetzt, wenn eine neue Eingabezeile gelesen wird. NF wird jedesmal neu gesetzt, wenn der Inhalt von $0 sich ändert oder wenn ein neues zusätzliches Feld erzeugt wird. RLENGTH und RSTART werden jedesmal neu gesetzt, wenn die Funktion match aufgerufen wird. Diese Builtin-Variablen können innerhalb von Ausdrücken verwendet werden, und dürfen vom Benutzer auch mit anderen Werten vorbesetzt werden. Operatoren für Ausdrücke, Vergleiche, Arrays Operatoren für Ausdrücke Die Operatoren in aufsteigender Priorität sind: = += -= *= /= %= ^= ?: || && in ~ !~ < <= == != >= > Konkatenation + * / % + ! ^ ** ++ -++ -$ () Zuweisung Bedingungsoperator logisches OR logisches AND Array-Mitglieds-Operator Beinhaltungsoperator relationale Operatoren Addition, Subtraktion Multiplikation, Division, Modulo Vorzeichen logisches NOT Potenz-Operator Präfix-Operator Postfix-Operator Feld-Operator Gruppierung Bei gleicher Priorität werden fast alle Operatoren "von links nach rechts" abgearbeitet: 3*4/2 entspricht (3*4)/2 und nicht 3*(4/2). "Von rechts nach links" werden bei gleicher Priorität nur die Zuweisungsoperatoren, die Potenz-Operatoren und der Bedingungsoperator abgearbeitet: 2^2^4 entspricht 2^(2^4) und nicht (2^2)^4. Vergleiche Wenn beide Operanden eines Vergleichsoperators numerisch sind, wird ein numerischer Vergleich durchgeführt (z.B. 13 > 5). Sind beide Operanden eines Vergleichsoperators Strings, so wird ein String-Vergleich durchgeführt (z.B."Franz" < "Fritz" und "Hans" < "Hansi"). Ist einer der Operanden numerisch und der andere ein String, so wird der numerische Operand in einen String umgewandelt und ein String-Vergleich durchgeführt (z.B. 3 > "13"). Vorsicht ist geboten bei Werten, welche zu groß sind, um numerisch dargestellt zu werden; so kann z.B. 10 < 1e5000 einen String-Vergleich nach sich ziehen. Umwandlungen können durch einen kleinen "Trick" erzwungen werden: zahl "" (Anhängen eines Null-Strings an eine numerische Zahl; somit wird eine Umwandlung der Zahl in einen String erzwungen) string+0 (Aufaddieren von 0 auf einen String, um so die Umwandlung eines Strings in eine Zahl zu erzwingen) Der numerische Wert eines Strings ist der Wert des längsten Präfixes, das numerisch interpretierbar ist. 69 Arrays Eindimensionale Arrays Strings und Zahlen können in eindimensionalen Arrays gespeichert werden. Arrays müssen weder explizit deklariert werden noch muss ihre Größe im voraus angegeben werden. Wie Variable so werden auch Array-Elemente eingeführt, indem man einfach auf sie mit arrayname[index] zugreift. Sie sind mit 0 bzw. "" initialisiert. Assoziative Arrays Im Unterschied zu Arrays in Programmiersprachen sind bei awk Strings als Indizes erlaubt; solche Arrays mit String-Indizierung werden assoziative Arrays genannt. for-in-Schleife awk bietet eine for-Schleife an, um alle Indizes eines Arrays mit einer variable zu durchlaufen: for (variable in array) anweisung Die Reihenfolge, in der die Indizes durchlaufen werden, ist implementationsabhängig. in-Operator Der in-Operator kann auch dazu verwendet werden, um festzustellen, ob ein bestimmter Index in einem Array vorkommt oder nicht: index in array Dieser Ausdruck liefert WAHR (Wert 1), wenn array[index] existiert, sonst FALSCH (Wert 0). Array-Elemente mit delete löschen Ein Array-Element kann mit delete array[index] aus einem Array entfernt werden. Mehrdimensionale Arrays awk unterstützt nicht direkt mehrdim. Arrays, aber es ist möglich, mehrdim. Indizes wie i,j oder x,y,z anzugeben. awk konkateniert zuerst diese Indizes (mit Trennzeichen dazwischen) und faßt dann solche mehrdim. Index-Angaben als einen eindim. Index auf. Wenn geprüft werden soll, ob ein mehrdim. Index in einem Array vorkommt, so ist der mehrdim. Index in Klammern anzugeben: if ((i,j) in array) Um ein "mehrdim. Array" mit einer Schleife zu durchlaufen, können die Konstruktionen (1) for ((i,j) in array) oder (2) for (m in array) verwendet werden. Werden bei (2) in Schleife einzelne Indizes benötigt, erhält man sie mit split(m, a, SUPSEP) Der 1. Index liegt dann in a[1], der 2. in a[2] usw. Array-Elemente können nie wieder Arrays sein. 70 Funktionen Es existieren folgende Arten von Funktionen: Benutzerdefinierte Funktionen Hierfür gilt die folgende Syntax: function funktionsname(parameter-liste) { anweisungen } Eine Funktionsdefinition kann überall dort angegeben werden, wo pattern { aktion }-Konstrukte erlaubt sind. In einer Funktionsdef. sind Zeilenvorschübe nach { und vor } nicht unbedingt gefordert. Die Parameterliste ist eine Folge von Variablennamen, welche durch Kommas getrennt sind. Werden diese Namen in der Funktion verwendet, so beziehen sie sich auf die Parameter und nicht auf gleichnamige globale Variablen. Benutzerdef. Funktionen sind "typenlos"; die gleiche Funktion kann sowohl ganze Zahlen, Strings als auch Gleitpunktzahlen an den aufrufenden Programmteil zurückgeben. Die Angabe einer return-Anweisung in einem Funktionskörper bewirkt das unmittelbare Verlassen der Funktion. Soll die Funktion einen Wert an den Aufrufer zurückgeben, dann ist return ausdruck anzugeben. Bei der Übergabe von einfachen Variablen als Parameter an eine Funktion wird nur eine Kopie des Variablenwerts übergeben und nicht die Variable selbst (call-by-value). Werden Arrays als Parameter übergeben, so findet jedoch ein call-by-reference statt. Eine benutzereigene Funktion kann in jedem beliebigen Ausdruck in einer pattern { aktion }Anweisung aufgerufen werden. Eine Funktion kann auch im Funktionskörper oder rekursiv aufgerufen werden. Werden in einer Funktionsdefinition neue Variablen eingeführt, dann sind diese global, d.h. sie sind im ganzen Programm verfügbar. Benötigt man funktionslokale Variablen, so sind diese am Ende der Parameterliste bei der Funktionsdef. anzugeben, da gilt: Wird eine Funktion mit weniger Parametern aufgerufen, als bei der Funktionsdefinition vereinbart wurden, so werden die restlichen Parameter als funktionslokale Variablen eingeführt, welche mit "" initialisiert werden. awk: Funktionen Arithmetische Builtin-Funktionen Funktion Rückgabewert atan2(y,x) cos(x) exp(x) int(x) log(x) rand() sin(x) sqrt(x) srand(x) Arcustangens von y/x in [-π,π] Cosinus von x (x im Bogenmaß) Exponentialfunktion: ex ganzzahliger Anteil von x natürlicher Logarithmus von x: ln x Zufallszahl r, für die gilt: 0 ≤ r < 1 Sinus von x (x im Bogenmaß) Quadratwurzel von x: √x neuen Startwert x für Zufallszahlenfolge setzen; Aufruf von srand() setzt aktuelle Zeit als Startwert Builtin-Stringfunktionen gsub(r,s) ersetzt überall in $0 r durch s; liefert die Anzahl der Ersetzungen gsub(r,s,t) ersetzt überall im String t r durch s; liefert die Anzahl der Ersetzungen index(s,t) sucht 1.Vorkommen des Strings t im String s; liefert, wenn gefunden, Anfangsposition von t, sonst 0 length(s) bestimmt Länge des Strings s; liefert Anzahl der Zeichen im String s match(s,r) prüft, ob s einen Teilstring r enthält, und liefert Anfangspos. des Teilstrings, der r entspricht, sonst 0; zusätzl. werden builtinVar. RSTART und RLENGTH gesetzt. split(s,a) zerlegt s entspr. Trennzeichen in FS in einzelne Felder und legt diese im Array a ab; liefert Anzahl der Felder split(s,a,tr) zerlegt s entspr. Trennzeichen in tr in einzelne Felder und legt diese im Array a ab; liefert Anzahl der Felder sprintf(fmt, ausdr-liste) formatiert Werte der in ausdr-liste angegeb. Ausdrücke entspr. fmt; liefert formatierten String sub(r,s) ersetzt in $0 den am weitesten links stehenden Teilstring r durch s; liefert Anzahl der Ersetzungen sub(r,s,t) ersetzt in t den am weitesten links stehenden Teilstring r durch s; liefert Anzahl der Ersetzungen substr(s,p) liefert ab Pos. p Rest von String s substr(s,p,n) liefert ab Pos. p n Zeichen aus s tolower(s) liefert String s, wobei alle Groß- durch Kleinbuchstaben ersetzt sind toupper(s) liefert String s, wobei alle Klein- durch Großbuchstaben ersetzt sind r reg. Ausdr. (als String oder in / .. / angegeb.) s,t für einen String-Ausdruck n,p für eine ganze Zahl Wird bei gsub und sub ein & in s angegeben, so wird es durch von r abgedeckten String ersetzt. Mehrzeilen-Records, system, Kommandozeilen-Argumente, awk-Programme in Shell-Skripten Mehrzeilen-Records Der voreingestellte Wert der Builtin-Variablen RS (Trennzeichen für Eingabe-Records) ist "\n". Sollen jedoch mehrere Eingabezeilen als ein Record eingelesen werden, so ist RS mit dem Null-String zu besetzen: BEGIN { RS = "" } Dies bewirkt, dass Eingabe-Records durch eine oder mehrere Leerzeilen getrennt werden und somit ein Record aus mehreren Zeilen bestehen kann. Unabhängig davon, wie FS besetzt ist, werden neue Zeilen in diesem Fall immer als Feld-Trennzeichen betrachtet. Die Funktion system Die Builtin-Funktion system(ausdruck) führt das Kommando aus, welches durch den aus ausdruck resultierenden String bezeichnet wird. system liefert als Rückgabewert den Status-Wert des ausgeführten Kommandos. Kommandozeilen-Argumente Die Kommandozeilen-Argumente für ein awkProgramm werden in dem builtin-Array ARGV hinterlegt. Der Wert der builtin-Variablen ARGC liefert die Zahl von übergebenen KommandozeilenArgumenten, wobei das erste Argument in ARGV[0], das zweite in ARGV[1] usw. abgelegt wird, so dass die übergebenen Argumente in ARGV[0] bis ARGV[ARGC-1] vorliegen. Der awk-Programmname "awk" wird immer in ARGV[0] abgelegt, und weder die Option -f noch der Programmname werden als Argument betrachtet. Dasselbe gilt auch für eine evtl. Option -F oder -V und die danach angegebenen Argumente. Ebenso wird ein direkt auf der Kommandozeile in ´....´ angegebenes awk-Programm nicht als Argument interpretiert. Die Argumente in ARGV dürfen modifiziert werden, und es ist auch möglich, ARGV zu erweitern und neue Argumente an ARGV anzuhängen. Ebenso darf ARGC verändert werden. Wenn eine Eingabedatei vollständig abgearbeitet ist, dann nimmt awk den nächsten Nicht-Null-String aus ARGV (bis ARGC-1) als nächste Eingabedatei. So kann ein Array-Element in ARGV auf leer gesetzt werden, um zu verhindern, dass es als Eingabedatei behandelt wird. Der Name "-" in ARGV steht für Standardeingabe. 71 awk-Programme in Shellskripten awk-Programme können direkt in Shellskripten eingebettet werden. Die einfachste Möglichkeit, Werte aus einem Shellskript an ein awk-Programm zu übergeben, ist die Übergabe der Werte über ARGV. In einem Shellskript kann awk 'awk-prog' kdo_zeilen_argumente angegeben werden. Eine andere Möglichkeit, Werte aus einem Shellskript an ein awk-Programm zu übergeben, ist die Übergabe dieser Werte über eine Pipe. Zugriff auf Shell-Variable in awk Es gibt mehrere Möglichkeiten, in einem awkProgramm auf Shell-Variablen zuzugreifen: 1. Shell-Parameter mit ' .. ' klammern In diesem Fall ist nur zu unterscheiden, ob der Wert der Variable im awk-Programm als numerischer Wert (genügt die alleinige Klammerung ' .. ') oder als String (muss dann in "' .. '" eingebettet sein) zu interpretieren ist. 2. awk-Programm mit " .. " klammern Auf Shell-Variablen innerhalb eines awk-Programms kann dann mit $0, $1, ... zugegriffen werden. Auf die awk-Felder muss dann mit \\$0, \\$1, ... zugegriffen werden. 3. Shell-Variablen über Zuweisungen in awkKommandozeile übergeben 4. Shell-Variablen mit "' .. '" klammern Kommandosubstitution in awk Um Kommandosubstitution in einem awk-Programm durchzuführen, ist '"`kommando`"' anzugeben. Direkte Ausführung von awk Seit System V.4 können awk-Programme selbst ausführbar gemacht werden, indem im awkProgramm in der ersten Zeile folgendes angegeben wird: #! awk -f oder #! /usr/bin/awk -f Ist diese Angabe in einem awk-Programm enthalten, so kann man anstelle von awk -f awk-programm auch nur awk-programm aufrufen. 72 awk: Beispiel Beispiel für ein awk-Programm Das folgende awk-Programm scheck.awk druckt automatisch Schecks aus: BEGIN { FS="\t+" striche = "------------------------------------------------------------" split("Januar Februar Maerz April Mai Juni " \ "Juli August September Oktober November Dezember", monat, " ") split("ein zwei drei vier fuenf sechs sieben acht neun zehn " \ "elf zwoelf dreizehn vierzehn fuenfzehn " \ "sechzehn siebzehn achtzehn neunzehn ", biszwanzig, " ") split("zehn zwanzig dreissig vierzig fuenfzig " \ "sechzig siebzig achtzig neunzig ", zehner, " ") "date +%D" | getline datum } NF != 3 { printf("Zeile %d, 3 Felder pro Zeile erforderlich: %s\n", NR, $0); f++; next; } $2 < 0 { printf("Zeile %d, Betrag muss positiv sein: %s\n", NR, $0); f++; next; } $1 !~ /[0-9]+/ { printf("Zeile %d, Fuer Schecknummer nur Ziffer erlaubt: %s\n", NR, $0) f++; next; } $2 !~ /[0-9]*[,][0-9]*/ { printf("Zeile %d, Unerlaubter Betrag: %s\n", NR, $0) f++; next; } $2 ~ /[,][0-9][0-9][0-9]+/ { printf("Zeile %d, Warnung: Nur 2 Nachkommast. relevant: %s\n", NR, $0) } { komma = index($2, ",") nachkomma = sprintf("%-2.2s", substr($2, komma+1) "00") ganz = substr($2, 1, komma-1) split(datum, dat, "/") printf("\n%s\n", striche) printf("%40s.%s.%s\n\n", dat[2], monat[dat[1]+0], dat[3]) printf("\tZahlen Sie gegen diesen Scheck --DM%s%s---\n\n", substr(striche, 1, 12-length(ganz)-3), ganz "," nachkomma) inworte = in_woerter(ganz) if (substr(inworte, length(inworte)-3) == "ein ") inworte = substr(inworte, 1, length(inworte)-4) "eine " inworte = inworte "Mark und " nachkomma " Pfennig" printf("In Worten: --") while (length(inworte)>45) { pos = rindex(substr(inworte,1,45), " "); printf("%s\n", substr(inworte, 1, pos-1)) inworte = substr(inworte, pos+1); printf("%13s", " ") } printf("%s--\n\n", inworte); printf("an: %s\n\n\n", $3) printf("\t\tScheck-Nr.: %s\n\n%s\n\n", $1, striche) } END { if (f) print "Fehler in Eingabe:" print f } function in_woerter(zahl) { zahl = int(zahl) if (zahl>=1000) return in_woerter(zahl/1000) "Tausend " in_woerter(zahl%1000) if (zahl>=100) return in_woerter(zahl/100) "Hundert " in_woerter(zahl%100) if (zahl>=20) if (zahl%10) return biszwanzig[zahl%10] " und " zehner[int(zahl/10)] " " else return zehner[int(zahl/10)] " " return biszwanzig[zahl] " " } function rindex(string, such) { for (i=length(string) ; i>=1 && substr(string,i,1)!=such ; i--) ; return i } Wenn diesem awk-Programm folgende Eingabedatei scheck.ein vorgelegt würde: 1234567890 5462165365 12,7 854765,21 Fa. Holzwurm Fa. Gross in der je Zeile folgendes angegeben ist: Scheck-Nummer Betrag Empfaenger und ruft man es mit folgender Kommandozeile auf: awk -f scheck.awk scheck.ein, so gibt es folgendes aus: -----------------------------------------------------------01.Juli.99 Zahlen Sie gegen diesen Scheck --DM-------12,70--In Worten: --zwoelf Mark und 70 Pfennig-an: Fa. Holzwurm Scheck-Nr.: 1234567890 ----------------------------------------------------------------------------------------------------------------------01.Juli.99 Zahlen Sie gegen diesen Scheck --DM---854765,21--In Worten: --acht Hundert vier und fuenfzig Tausend sieben Hundert fuenf und sechzig Mark und 21 Pfennig-an: Fa. Gross Scheck-Nr.: 5462165365 ------------------------------------------------------------ sed 73 sed Aufrufsyntax Die vollständige Aufrufsyntax für den Stream-Editor sed ist: sed [-n] [-e skript] [-f skript-datei ] [datei(en)] sed bearbeitet die angegebenen Eingabe-dateien oder, falls keine Eingabedatei angegeben ist, die Daten der Standardeingabe und schreibt das Ergebnis auf die Standardausgabe. Die Editieranweisungen für die Eingabedateien werden sed durch ein sed-Skript vorgegeben. Optionen -e skript skript sind die durchzuführenden Editieranweisungen. Wenn skript Shell-Sonderzeichen enthält, so ist -e 'skript' anzugeben. Wenn nur ein -e skript und keine -f skript-datei-Angabe in der Kommandozeile vorkommt, so kann -e auch weggelassen werden. -f skript-datei Die durchzuführenden Editieranweisungen befinden sich in der Datei skript-datei. -n bewirkt, dass die automatische Ausgabe von bearbeiteten Eingabezeilen auf stdout unterdrückt wird. Es wird nur noch das auf Standardausg. geschrieben, was mittels expliziter Ausgabeanweisung (wie z.B. print) in den sed-Anweisungen vorgegeben wird. Beim Aufruf dürfen mehrere Skripten mit -e und -f (auch kombiniert) angegeben werden. sed-Editieranweisungen Die allgemeine Form einer sed-Editieranweisung ist: [adresse1 [,adresse2]] funktion [argumente] Durch adresse1 und adresse2 wird ein bestimmter Bereich von Zeilen spezifiziert. Allerdings ist die Angabe der Adressen nicht gefordert. Folgendes gilt: keine Adressen angegeben: alle Eingabezeilen Nur eine Adresse angegeben: alle Eingabezeilen, auf die die angegebene Adresse zutrifft Beide Adressen angegeben: Bereich (von, bis) von Eingabezeilen Auf diese so ausgewählten Zeilen wird dann die Editieraktion funktion ausgeführt. Diese Funktion kann dann weitere argumente besitzen. Adressen Die Adreßangaben adresse1 und adresse2 wählen aus den Eingabedateien bestimmte Zeilen aus. Folgende Angaben sind für adresse möglich: ganze Zahl n adressiert die Zeile n; es ist zu beachten, dass die Zeilennumerierung bei mehreren Eingabedateien nicht ständig neu beginnt, sondern weitergezählt wird. $ adressiert die letzte Eingabezeile. /regulärer Ausdruck/ adressiert Eingabezeilen, welche einen String beinhalten, der durch den vorgegebenen regulären Ausdruck abgedeckt ist. Reguläre Ausdrücke siehe "Übersicht über reguläre Ausdrücke" auf Seite 76; dort nicht erwähnt ist die Angabe von: \n für Neuezeile-Zeichen. sed-Skripten in Shellskripten sed-Skripten können in Shellskripten eingebettet werden. Die einfachste Möglichkeit, Werte aus einem Shellskript an ein sed-Skript zu übergeben, ist die Übergabe der Werte über die Kommandozeile. In einem Shellskript kann sed 'sed-Skript' kdo_zeilen_argumente angegeben werden. Eine andere Möglichkeit, Werte von einem Programm an ein sed-Skript weiterzureichen, ist die Übergabe dieser Werte über eine Pipe. Zugriff auf Shell-Variable in sed Es gibt mehrere Möglichkeiten, innerhalb eines sedSkripts auf Shell-Variablen zuzugreifen: 1. Shell-Variable mit '.. ' klammern In einem sed-Skript bewirkt die Klammerung einer Shell-Variablen mit Apostroph ('.. '$variable' .. '), dass die verschiedenen Sonderzeichen, die für sed gelten, vor einer Interpretation durch die Shell geschützt werden; andererseits bleibt $variable ungeschützt und wird durch die Shell ausgewertet. 2. sed-Skript mit ".. " klammern Eine Klammerung mit " .. " bewirkt, dass Zugriff auf eine Shell-Variable mit $ durch die Shell interpretiert wird, da innerh. von "..." die Sonderbedeutung von $ für die Shell nicht ausgeschaltet wird. Bei dieser Möglichkeit ist zu beachten, dass den Sonderzeichen (wie $ für Zeilenende), die auch in der Shell vorhanden sind, ein \ vorangestellt werden muss, um sie vor der Interpretation durch die Shell zu schützen. 74 sed: Funktionen Funktionen In einer sed-Anweisung muss immer eine Funktion angegeben werden. Folgende Tabelle enthält alle Funktionen; dabei sind am Ende jeder Funktionsbeschreibung in Klammern die maximal erlaubten Adressen angegeben: a\ text (append) text nach Eingabezeile auf stdout schreiben. Besteht text aus mehreren Zeilen, so ist das Fortsetzungszeichen \ am Ende vor CR anzugeben (1) b [marke] (branch) zu marke springen. Fehlt marke, so wird neue Eingabezeile gelesen und sedSkript von Beginn an wieder ausgeführt (2) c\ text (change) Inhalt des Eingabepuffers durch text ersetzen. Besteht text aus mehreren Zeilen, so ist das Fortsetzungszeichen \ am Ende vor CR anzugeben (2) d (delete) Eingabepuffer löschen, nächste Eingabezeile lesen und sed-Skript wieder von Beginn an ausführen (2) D (Delete first part of pattern space) Den 1. Teil des Eingabepuffers (bis erstes \n) löschen und sed-Skript von Beginn an wieder mit restlichem Eingabepuffer ausführen (2) g (get contents of hold area) Eingabepuffer durch Haltepuffer-Inhalt überschreiben (2) G (Get contents of hold area) Haltepuffer am Eingabepuffer (mit \n getrennt) anhängen (2) h (hold pattern space) Haltepuffer durch Eingabepuffer-Inhalt überschreiben (2) H (Hold pattern space) Eingabepuffer am Haltepuffer (mit \n getrennt ) anhängen (2) i\ text (insert lines) text vor Eingabezeile auf stdout schreiben. Besteht text aus mehreren Zeilen, so ist das Fortsetzungszeichen \ am Ende vor CR anzugeben (1) l (list) Eingabepuffer auf stdout schreiben; dabei nicht druckbare Zeichen als ASCII-Wert (2-Ziffer) und überlange Zeilen als mehrere einzelne Zeilen ausgeben (2) n (next line) Eingabepuffer auf stdout schreiben und nächste Eingabezeile in Eingabepuffer lesen (2) N (Next line) Nächste Eingabezeile an Eingabepuffer (mit \n getrennt) anhängen; Zeilennummer wird hier weitergezählt (2) (2) p (print) Eingabepuffer auf stdout ausgeben P (Print first part of the pattern space) Den 1. Teil des Eingabepuffers (bis einschließl. erstem \n) auf stdout schreiben (2) q (quit) Nach Ausgabe des Eingabepuffers Skriptausführung beenden (1) r datei (read file) datei auf stdout ausgeben und nächste Eingabezeile lesen (2) s/regulärer Ausdruck/text/[flags] (substitute) Im Eingabepuffer Strings, die regulärer Ausdruck abdeckt, durch text ersetzen. Für flags kann Folg. angegeben werden: n nur n-tes Textstück ersetzen g alle Textstücke ersetzen p falls Ersetzung stattfand, verändert. Eing.puffer ausgeben w datei falls Ersetzung stattfand, dann veränderten Eingabepuffer ans Ende von datei schreiben Fehlen flags, wird nur erste Textstück des Eingabepuffers ersetzt. Bei mehreren flags-Angaben muss g (falls verwendet) an 1. Stelle stehen. Wird in text das Zeichen & angegeben, so wird dafür der durch regulären Ausdruck abgedeckte String eingesetzt (2) t [marke] (test substitutions) Falls seit letztem Lesen einer Eing.zeile oder Ausführung einer t-Funktion eine Ersetzung stattfand, dann zu marke springen und dort mit Abarbeitung des Skripts fortfahren; ansonsten hat diese Funktion keine Wirkung. Fehlt marke, so wird ans Skriptende gesprungen (2) w datei (write to a file) Eingabepuffer ans Ende der Datei datei schreiben (2) x (exchange) Eingabe- und Haltepuffer vertauschen (2) y/string1/string2/ Im Eingabepuffer alle Zeichen, die in string1 vorkommen, durch die an gleicher Position in string2 stehenden Zeichen ersetzen (2) ! funktion funktion (oder Funktionsgruppe bei Klammerung mit { }) nur für die Zeilen ausführen, für die angegebene (2) Adreßangabe nicht zutrifft : marke Sprungmarke für b und t definieren (0) = Zeilennr. auf stdout schreiben (1) { .. } Klammert Gruppe von Funktionen, die nur ausgeführt werden, wenn Eingabepuffer gefüllt ist (2) # Wenn als erstes Zeichen in Zeile angegeben, leitet dieses Zeichen einen Kommentar ein. Ist #n angegeben, wird automat. Ausgabe ausgeschaltet (wie bei Option -n); Rest der Zeile nach #n wird ignoriert. Eine Skript-Datei muss mind. eine Nicht-Kommentarzeile enthalten (0) leere Funktion leere Funktion wird ignoriert (0) Beispiel zu sed 75 Beispiel zu sed Das hier vorgestellte Shellskript xref erstellt mit Hilfe des sed eine einfache Cross-Reference-Liste für C-Module. Das Shellskript xref (Statt \t muss die Tabtaste bei der Eingabe dieses Skripts gedrückt werden) besteht aus einer "großen Pipe", die sich vom Anfang bis zum Ende des Skripts erstreckt. Jedes Kommando erfüllt eine bestimmte Aufgabe, bevor es die so manipulierten Daten wieder über die "große Pipe" ans nächste Kommando weiterreicht: # xref datei1 ... # Ausgabe einer einfachen Cross-Reference-Liste fuer die als Argumente angegebenen C-Programmdateien ueberschrift="====D_NAME====" # Erzeugen einer Ueberschrift (mit "====D_NAME====" geklammerter # jeweiliger Dateiname); diese Ueberschrift wird zusammen mit dem # zugehoerigen Inhalt jeder angegebenen Datei ueber eine Pipe # an das nachfolgende sed-Skript weitergeleitet. for i in $* do echo "$ueberschrift $i $ueberschrift" cat $i done | # Das sed-Skript ersetzt dann alle einzeiligen Kommentarzeilen durch # Leerzeilen und leitet die so geaenderten Eingabedaten ueber # eine Pipe an ein awk-Programm weiter. sed 's+/\*.*\*/++' | # Das awk-Programm ersetzt mehrzeilige Kommentarzeilen durch # Leerzeilen und leitet die so geaenderten Eingabedaten ueber # eine Pipe an ein weiteres sed-Skript weiter. awk '/\/\*/,/\*\// { printf("\n"); next } { print $0 }' | # Das sed-Skript ersetzt alle fuer C-Namen nicht erlaubten Zeichen # durch ein Leerzeichen; diese Ersetzung wird nur fuer Zeilen # vorgenommen, die nicht mit "====D_NAME====" beginnen. # Die so geaenderten Eingabedaten werden ueber eine Pipe an ein # weiteres awk-Programm weitergeleitet. sed '/'$ueberschrift' /!s/[^A-Za-z_0-9]/ /g' | # Wenn dieses awk-Programm eine "Ueberschriftszeile" # (Zeile, welche als 1. und als 3. Feld $ueberschrift enthaelt) findet, # so haelt es den zugehoerigen Dateinamen in der Variablen DATEI # fest und setzt die Zeilennumerierung auf 1. # Fuer jede "echte Zeile" gibt es dann pro Feld den Feldnamen, # den Inhalt der Variable DATEI und die Zeilennummer aus; diese # Ausgabe wird wieder ueber eine Pipe an ein sed-Skript weitergeleitet. awk 'BEGIN { OFS="\t" } $1 ~ /^'$ueberschrift'$/ && $3 ~ /^'$ueberschrift'$/ { DATEI = $2 zeil_nr = 1 next } { for (i=1; i<=NF ; i++) print $i, DATEI, zeil_nr zeil_nr++ }' | # Dieses sed-Skript entfernt alle Zeilen, die nicht einen erlaubten # C-Namen besitzen; die so geaenderten Eingabedaten werden ueber # eine Pipe an ein sort-Kommando weitergeleitet. sed '/^[A-Za-z_][A-Za-z_0-9]*/!d' | # Sortieren nach den Sortierschluesseln C-Name (1. Feld), # Dateiname (2. Feld) und Zeilennummer (3. Feld), wobei doppelte # Zeilen geloescht werden (Option -u). Die sortierte Ausgabe wird # ueber eine Pipe an ein awk-Programm weitergeleitet. sort -u +0 -1 +1 -2 +2n -3 | # (1) Falls das betrachtete Wort dasselbe ist wie das vorhergehende # und falls dieses Wort in derselben Datei vorkam, dann wird # nur die neue Zeilennummer an die vorherige Ausgabe angehaengt. # (2) Falls dasselbe Wort in einer anderen Datei vorkam, wird (in # einer eigenen Zeile eingerueckt) die neue Datei mit der # entsprechenden Zeilennummer ausgegeben. # (3) Falls das betrachtete Wort nicht identisch ist mit dem vorherigen, # werden in einer neuen Zeile das Wort, der Dateiname und die # Zeilennummer ausgegeben. Der neue C-Name wird in der Variablen # name_vorher und der Dateiname in der Variablen datei_vorher # abgelegt. awk ' { if ($1 == name_vorher) { if ($2 == datei_vorher) { printf(", %d", $3) # (1) } else { printf("\n%-25s %15s %d", " ", $2, $3) # (2) datei_vorher = $2 # } } else { printf("\n%-25s %15s %d", $1, $2, $3) # (3) name_vorher = $1 # datei_vorher = $2 # } } END { printf("\n") }' Dieses Skript xref ermittelt alle Strings, die nach Definition erlaubte C-Namen sind. Dadurch werden auch Schlüsselwörter – wie char, while, do usw. – oder in printf angegebene Wörter aufgelistet. Um die Einbeziehung von Konstrukten wie diese zu unterbinden, müßte ein wesentlich intelligenteres Skript geschrieben werden. Für den Hausgebrauch sollte allerdings diese Skriptversion ausreichen. 76 Übersicht über die regulären Ausdrücke Übersicht über die regulären Ausdrücke Zusätzlich zu awk und sed sind in der folgenden Tabelle noch die UNIX-Tools angegeben, für die die entsprechenden regulären Ausdrücke auch noch erlaubt sind: deckt ab / bewirkt DateinamenExpandierung awk sed egrep ed, grep, csplit, pg ex, vi emacs ein beliebiges Zeichen ? . . . beliebige Zeichenkette * .* .* .* keine, eine oder mehrmalige Wiederholung -- * * * eine oder mehrmalige Wiederholung -- + \{1,\} + keine oder eine Wiederholung -- ? \{0,1\} ? n-malige Wiederholung -- -- \{n\} -- n- bis m-malige Wiederholung -- -- \{n,m\} -- -- -- \{n,\} -- Klasse von Zeichen [...] [...] [...] [...] Komplement-Klasse von Zeichen [!...] [^...] [^...] [^...] Zeilenanfang -- ^RA ^RA ^RA Zeilenende -- RA$ RA$ RA$ Wortanfang wort* -- -- \<RA Wortende *wort -- -- RA\> -- RA1|RA2 -- -- mindestens n-malige Wiederholung a oder b (Alternation) \runde Klammern -- -- \(RA\) \(RA\) n-ter Teilausdruck -- -- \n \n RA, RA1, RA2 stehen für reguläre Ausdrücke Zu awk und egrep sei noch folgendes angemerkt: • runde Klammern: (r) deckt den gleichen String wie r ab; um vorgegebene Prioritäten aufzuheben • Die Priorität der Operatoren (in aufsteigender Folge): | Konkatenation * + ? (besitzen untereinander gleiche Priorität) () (besitzen untereinander gleiche Priorität) • Die Operatoren *, + und ? beziehen sich immer auf das vorhergehende Zeichen; sollen sie sich auf einen längeren Ausdruck beziehen, so ist dieser mit ( .. ) zu klammern. Um runde Klammern in einem Text abzudecken, ist deren Sonderbedeutung mit \ auszuschalten: \( bzw. \). Die Alternation kann bei egrep auch durch ein Neuezeile-Zeichen (Carriage-Return) angegeben werden.