awk/Reguläre Ausdrücke

Aus Mikiwiki
< awk
Wechseln zu: Navigation, Suche

Ein regulärer Ausdruck (engl. regular expression; auch: regexp) ist eine Art, eine Menge von Zeichenketten zu beschreiben. Ein in Schrägstriche ("/") eingeschlossener regulärer Ausdruck ist ein awk-Muster, das auf jede Eingabezeile passt, deren Text zu dieser Menge gehört. Der einfachste reguläre Ausdruck ist eine Folge von Buchstaben, Nummern oder beiden. Der reguläre Ausdruck "foo" passt deshalb auf jede Zeichenfolge, die "foo" enthält. Das Muster "/foo/" passt also auf jede Eingabezeile, welche die drei Buchstaben "foo" irgendwo in der Zeile enthalten.

Verwendung regulärer Ausdrücke

Ein regulärer Ausdruck kann als Muster verwendet werden, indem er in Schrägstriche eingeschlossen wird. In diesem Fall wird der reguläre Ausdruck mit dem gesamten Text jeder Zeile verglichen.

Ausgabe des zweiten Felds jeder Zeile, welche irgendwo die Zeichenkette "foo" enthält.

$ awk '/foo/ { print $2 }' bbslist
555-1234
555-6699
555-6480
555-2127

Reguläre Ausdrücke können auch in übereinstimmenden Ausdrücken verwendet werden. Diese Ausdrücke erlauben es, die Zeichenkette zu bestimmen, gegen die verglichen werden soll; es braucht also nicht die vollständige aktuelle Zeile zu sein. Die beiden Operastoren "~" und "!~" Vergleiche auf reguläre Ausdrücke durch. Ausdrücke, welche diese Operatoren verwenden, können als Muster verwendet werden, ausserdem auch in "if"-, "while"-, "for"- und "do"-Anweisungen.

"exp ~ /regexp/" wahr, wenn der Ausdruck "exp" (als Zeichenkette) mit "regexp" übereinstimmt.

Ausgabe aller Zeilen der Datei "inventory", in denen irgendwo im ersten Feld der Grossbuchstabe "J" vorkommt.

$ awk '$1 ~ /J/' inventory
Jan  13  25  15 115
Jun  31  42  75 492
Jul  24  34  67 436
Jan  21  36  64 620

Dasselbe Ergebnis liefert das folgende Beispiel.

$ awk '{ if ($1 ~ /J/) print }' inventory

"exp !~ /regexp/" ist wahr, wenn der Ausdruck "exp" (als Zeichenkette) nicht mit "regexp" übereinstimmt.

Ausgabe aller Zeilen der Datei "inventory", in denen irgendwo im ersten Feld der Grossbuchstabe "J" nicht vorkommt.

$ awk '$1 !~ /J/' inventory
Feb  15  32  24 226
Mar  15  24  34 228
Apr  31  52  63 420
...

Escape-Sequenzen

Einige Zeichen können nicht ohne weiteres in Zeichenketten- ("foo") oder regexp-Konstanten ("/foo/") eingeschlossen werden. Stattdessen müssen sie durch Escape-Sequenzen dargestellt werden, also durch Zeichenfolgen, die mit einem umgekehrten Schrägstrich beginnen und die als erster Schritt bei der Verarbeitung von regulären Ausdrücken in die entsprechenden wirklichen Zeichen umgewandelt werden.

Eine Verwendungsmöglichkeit von Escape-Sequenzen ist der Einschluss eines doppelten Anführungszeichens in eine Zeichenkettenkonstante. Weil ein gewöhnliches doppeltes Anführungszeichen die Zeichenkette beendet, muss "\"" verwendet werden, um ein tatsächliches doppeltes Anführungszeichen darzzustellen.

$ awk 'BEGIN { print "Er sagte \"Hallo!\" zu ihr." }'
Er sagte "Hallo!" zu ihr.

Der umgekehrte Schrägstrich seinerseits kann ebenfalls nicht auf normale Weise dargestellt werden; stattdessen muss er als "\\" geschrieben werden. Eine darzustellende Folge von einem doppelten Anführungszeichen und einem umgekehrte Schrägstrich muss also als "\"\\" geschrieben werden.

Mit dem umgekehrten Schrägstrich können auch nichtdruckbare Zeichen wie den Tabulator oder den Zeilenumbruch angezeigt werden. "/\t/" trifft beispielsweise für alle Zeilen zu, in denen ein Tabulatorzeichen enthalten ist. Folgende Liste zeigt alle Escape-Sequenzen in awk:

Escape-
Sequenz
Oktal Hexa-
dezimal
Tastatur Bedeutung
\a 007 (BEL) 07 CTRL+G Das "Alarm"-Zeichen,
\b 010 (BS) 08 CTRL+H Backspace.
\e 033 (ESC) 1b CTRL+[ Escape.
\f 014 (FF) 0c Seitenvorschub (engl. formfeed).
\n 012 (LF) 0a CTRL+J Zeilenumbruch (engl. newline, linefeed).
\r 015 (CR) 0d CTRL+M Wagenrücklauf (engl. carriage return) bzw. "Return"-Taste.
\t 011 (HT/TAB) 09 CTRL+I Horizontales Tabulatorzeichen.
\v 013 (VT) 0b Vertikales Tabulatorzeichen.
\nnn Oktaler Wert "nnn".
\xnn Hexadezimaler ASCII-Wert "nn". Für Unicode-Zeichen kann "\x{nnnn}" verwendet werden.
\/ Schrägstrich in durch Schrägstriche begrenzte regexp-Konstanten.
\" Doppelte Anführungszeichen in durch doppelte Anführungszeichen begrenzten Zeichenkettenkonstanten.
\\ Umgekehrter Schrägstrich ("\").
\c ein beliebiges anderes Zeichen "c" als darstellbares Zeichen. Das Zeichen wird also als einfaches Zeichen dargestellt, auch wenn es eigentlich ein regexp-Operator wäre. Das Muster "/a\+b/" stimmt also mit den drei Zeichen "a+b" überein.

In gawk gibt es einige zusätzliche mit umgekehrtem Schrägstrich beginnende Zeichen.

Metazeichen

Reguläre Ausdrücke können mit Sonderzeichen, sogenannten Metazeichen (engl. metacharacter, regular expression operator), verknüpft werden. Diese Sonderzeichen haben dieselbe Sonderbedeutung wie die entsprechenden Shell-Sonderzeichen:

Operator Bedeutung
\ Unterdrücken der Sonderbedeutung eines Zeichens. Z. B. stimmt "\$" mit dem Zeichen "$" überein.
^ Zeilenanfang. Z. B. stimmt "^@kapitel" mit "@kapitel" am Zeilenanfang überein.
$ Zeilenende. Z. B. stimmt "z$" mit "z" am Zeilenende überein.
. ein beliebiges Zeichen, einschliesslich des Zeilenumbruchs. Z. B. stimmt ".P" mit jedem beliebigen Zeichen gefolgt von einem "P" überein.
[...] Zeichenliste, die mit jedem zwischen den Klammern enthaltenen Zeichen übereinstimmt. Z. B. stimmt "[MVX]" sowohl mit "M", "V" und "X" überein.
[^...] Ergänzende Zeichenliste, die mit jedem Zeichen übereinstimmt, das nicht innerhalb der Klammern enthalten ist. Z. B. stimmt "[^MVX]" mit allen Zeichen überein, ausser mit "M", "V" und "X".
| Der "alternation operator" wird verwendet, um Alternativen anzugeben, und hat den niedrigsten Rang aller Sonderzeichen. Z. B. stimmt "^P|[[:digit:]]" mit jeder Zeichenkette überein, bei der entweder ein "P" am Zeilenanfang steht, oder die ein beliebiges numerisches Zeichen ("[[:digit:]]") enthält.
(...) Klammern dienen innerhalb regulärer Ausdrücke zum Gruppieren. Sie können verwendet werden, um um den "alternation operator" ("|") enthaltende reguläre Ausdrücke miteinander zu verknüpfen. Z. B. stimmt "@(samp|code)\{[^}]+\}" sowohl mit "@code{foo}" wie auch mit "@samp{bar}" überein.
* Das vorangehende Zeichen muss in einer beliebigen Anzahl (also auch keinmal) vorkommen. Z. B. wird das Symbol "*" bei "ph*" nur auf das vorangehende "h" angewendet und sucht nach Übereinstimmungen von einem "p" gefolgt von einer beliebigen Anzahl von "h". Gefunden wird also auch jedes einzelne "p", auf das kein "h" folgt.

"*" wiederholt dabei den kleinstmöglichen vorangehenden Ausdruck; wenn längere Ausdrücke wiederholt werden sollen, müssen Klammern verwendet werden. Der folgende Befehl gibt jede Zeile der Datei "file" aus, die eine Zeichenfolge der Art "(car x)", "(cdr x)", "(cadr x)" usw. enthält. Zu beachten sind hier die gefluchteten Klammern.

 awk '/\(c[ad][ad]*r x\)/ { print }' file
+ Das vorangehende Zeichen muss mindestens einmal vorkommen. Z. B. stimmt "wa+s" mit "was" und "waas", aber nicht mit "ws" überein (während "wa*s" auf alle drei Zeichenketten passen würde). Die folgende Zeile ist eine einfachere Art, um das obige Beispiel ohne "*" zu schreiben.
 awk '/\(c[ad]+r x\)/ { print }' file
? Das vorangehende Zeichen muss genau einmal oder gar nicht vorkommen. Z. B. stimmt "fe?d" mit "fed" und "fd" überein, aber mit nichts sonst.
{n}, {n,}, {n,m} Eine Zahl "n" in geschweiften Klammern bedeutet, dass der vorausgehende reguläre Ausdruck "n" mal wiederholt wird. Steht ein Komma dabei, so wird der Ausdruck mindestens "n" mal wiederholt. Stehen zwei Zahlen "n" und "m" da, so wird der Ausdruck "n" bis "m" mal wiederholt.
  • "wh{3}y" passt auf "whhhy".
  • "wh{3,}y" passt auf "whhhy", "whhhhy", "whhhhhy" und so weiter.
  • "wh{3,5}y" passt auf "whhhy", "whhhhy" und "whhhhhy".

Achtung: Um dieses Verhalten zu bewirken, muss awk bzw. gawk entweder mit dem Schalter "--posix" oder mit "--re-interval" aufgerufen werden.

"/^.$/" passt somit auf alle Sätze, die nur ein einziges Zeichen enthalten.

$ awk '$1 ~ /^.$/ { print $1 }' file

Folgendes Beispiel verwendet die Funktion "sub", um den Eingabedatensatz zu verändern. Der reguläre Ausdruck "/a+/" bedeutet "ein oder mehrere Vrkommen des Buchstabens a" und der Ersetzungstext ist "<A>".

echo aaaabcd | awk '{ sub(/a+/, "<A>"); print }'

Die Eingabe enthält viermal das Zeichen "a". Reguläre Ausdrücke bei awk (und POSIX) stimmen immer mit der längstmöglichen Zeichenfolge auf der linken Seite überein. Deshalb werden im folgenden Beispiel alle vier "a"-Zeichen durch "<A>" ersetzt:

$ echo aaaabcd | awk '{ sub(/a+/, "<A>"); print }'
<A>bcd

Verwendung von Zeichenlisten

Innerhalb einer Zeichenliste besteht ein Bereichsausdruck aus zwei Zeichen, getrennt durch einen Bindestrich. Z. B. entspricht in der C-Umgebung "[a-dx-y]" also "[abcdxyz]". Viele Umgebungen sortieren allerdings nach Wörterbuchordnung, wo "[a-dx-y]" dann beispielsweise "aBbCcDdXxYyZz]" enstpricht. Um die herkömmliche Interpretation der Klammerausdrücke zu erhalten, kann durch Setzen der Variable "LC_ALL" die C-Umgebung erzwungen werden.

Um eines der Zeichen "\", "]", "-" oder "^" in einer Zeichenliste verwenden zu können, müssen diese mit einem umgekehrten Schrägstrich gefluchtet werden, z. B. stimmt "[d\]]" mit "d" oder "]" überein.

Eine Zeichenklasse ist eine besondere Schreibweise zur Beschreibung von Zeichenlisten, die ein besonderes Merkmal aufweisen, wobei sich die tatsächlichen Zeichen von Land zu Land und von Zeichensatz zu Zeichensatz unterscheiden können. Beispielsweise unterscheiden sich in verschiedenen Ländern die Auffassungen darüber, was ein alphabetisches Zeichen ist.

Eine Zeichenklasse ist in einem regulären Ausdruck nur innerhalb einer Zeichenliste gültig. Zeichenklassen bestehen aus "[:", einem Schlüsselwort zur Bezeichnung der Klasse, und ":]". Die vom POSIX-Standard bestimmten Klassen sind:

Klasse Beschreibung
[:alnum:] Alphabetische Zeichen und dezimale Zahlen. Dies entspricht "A-Za-z0-9".
[:alpha:] Alphabetische Zeichen. Dies entspricht "A-Za-z".
[:blank:] Leerschlag oder Tabulator.
[:cntrl:] Steuerzeichen.
[:digit:] Dezimale Zahlen. Dies entspricht "0-9".
[:graph:] Die ASCII-Zeichen 33 bis 126, die sowohl druckbar wie sichtbar sind. Entspricht der Zeichenkalsse "[:print:]", enthält aber nicht den Leerschlag.
[:lower:] Kleingeschriebene alphabetische Zeichen. Dies entspricht "a-z".
[:print:] Die ASCII-Zeichen 32 bis 126. Entspricht der Zeichenklasse "[:graph:]", wobei aber auch der Leerschlag enthalten ist.
[:punct:] Interpunktionszeichen, also Zeichen, die weder Buchstaben, Zahlen, Steuerzeichen oder Leerzeichen sind.
[:space:] Leerzeichen (Leerschlag, horizontaler Tabulator, Seitenvorschub, usw.).
[:upper:] Grossgeschriebene alphabetische Zeichen. Dies entspricht "A-Z".
[:xdigit:] Hexadezimale Zahlen. Dies entspricht "0-9A-Fa-f".

In Zeichenlisten können ausserdem zwei weitere Zeichenfolgen auftreten. Diese betreffen Nicht-ASCII-Zeichensätze, die einzelne Symbole enthalten, welche durch mehr als ein Zeichen dargestellt werden. Diese können auch verschiedene Zeichen enthalten, die einander zum Zusammenfügen oder für die Sortierung entsprechen. Beispielsweise entsprechen sich im Französischen das einfache "e" und das Gravis-akzentuierte "è". Diese Zeichenfolgen (die von gawk allerdings nicht unerstützt werden) sind:

  • "Collating symbols" bestehen aus mehreren Zeichen und werden zwischen "[." und ".]" eingeschlossen. Ween beispielsweise "ch" ein "collating symbol" ist, so ist "[[.ch.]]" ein regulärer Ausdruck, der mit diesem "collating element" übereinstimmt, während der reguläre Ausdruck "[ch]" auf "c" oder "h" passt.
  • "Equivalence classes" sind umgebungsspezisfische Namen für gleichwertige Zeichenlisten. Der Name wird zwischen "[=" und "=]" eingeschlossen. Beispielsweise könnte der Name "e" verwendet werden, um die Zeichen "e", "è" und "é" zu repräsentieren. In diesem Fall passt der reguläre Ausdruck "[[=e=]]" auf "e", "è" oder "é".

gawk-spezifische regexp-Operatoren

GNU-Software, die mit reguläre Ausdrücken arbeitet, stellt zusätzliche regexp-Operatoren zur Verfügung. Folgende regexp-Operatoren sind spezifisch für gawk und stehen in anderen awk-Implementationen nicht zur Verfügung. Die meisten dieser Operatoren behandeln die Wortübereinstimmung. Für unsere Zwecke wird als "Wort" eine Reheienfolge von Buchstaben, Zahlen oder Unterstrichen verstanden.

Operator Bedeutung
\w Stimmt mit jedem Buchstaben, jeder Zahl und jedem Unterstrich überein. Kann also als Abkürzung für "[[:alnum]_]" angesehen werden.
\W Stimmt mit jedem Zeichen überein, das kein Buchstabe, keine Zahl und kein Unterstrich ist. Kann also als Abkürzung für "^[[:alnum]_]" angesehen werden.
\< Stimmt mit der leeren Zeichenfolge am Anfang eines Wortes überein. Z. B. stimmt "/\<hans/" mit "hans" überein, aber nicht mit "hansdampf".
\> Stimmt mit der leeren Zeichenfolge am Ende eines Wortes überein. Z. B. stimmt "/dampf\>/" mit "dampf" überein, aber nicht mit "hansdampf".
\y Stimmt mit der leeren Zeichenfolge am Anfang und Ende eines Wortes überein (also mit den Wortgrenzen). "/\yballs?\y" stimmt also mit "ball" oder "balls" als einzelnen Wörtern überein.
Bemerkung: In anderer GNU-Software ist "\b" der Wortbegrenzungsoperator, was allerdings mit der Bedeutung von "\b" (Backspace) in awk in Konflikt steht, weswegen gawk einen anderen Buchstaben verwendet.
\B Stimmt mit der leeren Zeichenfolge zwischen zwei Buchstaben, Zahlen oder Unterstrichen überein. Z. B. stimmt "/\Brat\B/" mit "crate" überein, jedoch nicht mit "dirty rat". "\B" ist im Grunde das Gegenteil von "\y".

Die verschiedenen Befehlszeilenoptionen bestimmen, wie gawk Zeichen in regulären Ausdrücken interpretiert:

Option Beschreibung
Keine Optionen Im Standard bietet gawk alle Möglichkeiten von POSIX-regexps und den zuvor beschriebenen GNU-regexp-Operatoren. Intervall-Ausdrücke werden nicht unterstützt.
--posix Nur POSIX-regexps werden unterstützt. Die GNU-Operatoren werden nicht erkannt (z. B. stimmt "\w" mit einem gewöhnlichen "w" überein. Intervall-Ausdrücke sind erlaubt.
--traditional Unterstützung der herkömmlichen awk-regexps. Die GNU-Operatoren werden nicht erkannt (z. B. stimmt "\w" mit einem gewähnlichen "w" überein. Intervall-Ausdrücke stehen nicht zur Verfügung und ebensownig die PSIX-Zeichenklassen ("[:alnum:]" usw.). Zeichen die mit oktalen und hexadezimalen Escape-Sequenzen beschrieben werden, werden buchstäblich behandelt, sogar wenn sie regexp-Metazeichen (regexp-Operatoren) darstellen.
--re-interval Intervall-Ausdrücke in regulären Ausdrücken sind erlaubt, sogar wenn "--traditional" verwendet wird.

Gross- und Kleinschreibung bei Übereinstimmungen

Gross- und Kleinschreibung ist in regulären Ausdrücken normalerweise bedeutsam, sowohl wenn Übereinstimmungen mit gewöhnlichen Zeichen (also keine Metazeichen) wie auch innerlab von Zeichensätzen gesucht werden. Ein "w" in einem regulären Ausdruck stimmt also nur mit einem kleingeschriebenen "w" und nicht mit einem grossgeschriebenen "W" überein.

Der einfachste Weg, um die Suche unabhängig von Gross- und Kleinschreibung durchzuführen, ist die Verwendung einer Zeichenliste - z. B. "[Ww]". Das ist ein wenig lästig ist, jedoch gibt es dazu zwei Alternativen.

Eine Möglichkeit besteht darin, alle Zeichen in Klein- oder Grossschreibung umzuwandeln, wobei die Zeichenkettenfunktionen ""tolower" und "toupper" verwendet werden können. Folgende Zeile wandelt das erste Feld in Kleinschreibung um, before darin nach Übereinstimmungen gesucht wird. Das funktioniert mit allen POSIX-konformen awk's.

tolower($1) ~ /foo/ { ... }

Eine andere Möglichkeit besteht unter gawk darin, die Variable "IGNORECASE" (die standardmässig den Wert "0" besitzt) auf einen Wert ungleich Null zu setzen. In diesem Fall wird bei allen regulären Ausdrücken und Zeichenkettenoperationen die Gross- und Kleinschreibung ausser Acht gelassen.

x = "aB"
if (x ~ /ab/) ... # Dieser Test wird nicht erfolgreich sein

IGNORECASE = 1
if (x ~ /ab/) ... # Dieser Test wird erfolgreich sein

Dynamische reguläre Ausdrücke

Die rechte Seite der Operatoren "~" oder "!~" muss keine regexp-Konstante sein (also eine Zeichenfolge zwischen zwei Schrägstrichen), sondern kann auch irgendein Ausdruck sein. Der Ausdruck wird gegebenenfalls berechnet und zu einer Zeichenfolge umgewandelt; die Inhalte der Zeichenfolge werden dann als regulärer Ausdruck verwendet. Ein auf diese Weise erzeugter regulärer Ausdruck wird "dynamischer regulärer Ausdruck" genannt.

Das folgende Beispiel setzt den Wert von "digits_regexp" auf einen regulären Ausdruck, der eine oder mehrere Zahlen beschreibt, und testet, ob der Eingabe-Datensatz mit diesem regulären Ausdruck übereinstimmt.

$ echo 123456 | awk 'BEGIN { digits_regexp = "[[:digit:]]+" }
  $0 ~ digits_regexp { print }'
123456

Bei Verwendung der Operatoren "~" oder "!~" gibt es einen Unterschied zwischen einer regexp-Konstante in Schrägstrichen und einer in Anführungszeichen eingeschlossenene Zeichenkettenkonstante.

Bei Verwendung einer Zeichenkettenkonstante wird die Zeichenkette zweimal gelesen: das erste Mal, wenn awk das Programm liest, das zweite Mal, wenn es nach der Übereinstimmung mit der Zeichenkette auf der linken Seite des Operators mit dem Muster auf der rechten Seite sucht. Das trifft auf alle Ausdrücke zu, deren Wert aus Zeichenketten besteht (wie "digits_regexp" im Beispiel), nicht nur auf Zeichenkettenkonstanten. Weil die Zeichenkette zweimal gelesen wird, müssen umgekehrte Schrägstriche in regulären Ausdrücken gefluchtet werden, es müssen also zwei umgekehrte Schrägstriche geschrieben werden. "/\*" ist also eine regexp-Konstante für das Zeichen "*", hier wird nur ein umgekehrter Schrägstrich benötigt. Um dasselbe für eine Zeichenkette zu erreichen, werden zwei umgekehrte Schrägstriche benötigt: "\\*". Der erste umgekehrte Schrägstrich fluchtet dabei den zweiten, sodass die Zeichenkette die beiden Zeichen "\*" enthält.

Um reguläre Ausdrücke zu beschreiben können sowohl regexp- wie auch Zeichenketten-Konstanten verwendet werden. Aus folgenden Gründen sollten jedoch regexp-Konstanten zum Zug kommen:

  • Zeichenkettenkonstanten sind schwieriger zu schreiben und zu lesen. Die Verwendung von regexp-Konstanten macht Programme weniger fehleranfällig. Den Unterschied zwischen beiden Arten von Konstanten nicht zu begreifen ist eine häufige Fehlerquelle.
  • Die Verwendung von regexp-Konstanten ist effizienter. awk bemerkt die Verwendung eines regulären Ausdrucks und speichert ihn intern in einer Form, der die Mustererkennung effizienter macht. Wird dagegen eine Zeichenkettenkonstante verwendet, so muss awk die Zeichenkette erst in seine interne Form verwandeln und danach die Mustererkennung durchführen.
  • Die Verwendung von regexp-Konstanten ist die klarere Form. Sie zeigt klar, dass die Übereinstimmung mit einem regulären Ausdruck beabsichtigt ist.