Gruppierende und einfangende Klammern, logische und gierige Konstrukte
(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)
Gruppierende und einfangende Klammern: (...), \1, \2, ...
Die ganz normalen runden Klammern ohne Firlefanz haben im Allgemeinen zwei Funktionen: Sie gruppieren und speichern den gefundenen Text. In den meisten Dialekten wird die Notation ˹(...)˼ verwendet, in seltenen Fällen ˹\(...\)˼ (bei GNU Emacs, sed, vi und grep).
Die Klammerpaare werden nach den öffnenden Klammern von links nach rechts nummeriert, wie in den Abbildungen Mit Klammern Textteile auffangen, Verschachtelte Klammern und Verschachtelte Klammern, $1 und $2 zu sehen ist. Wenn Rückwärtsreferenzen unterstützt sind, kann man weiter hinten in der Regex mit ˹\1˼, ˹\2˼ nach dem Text suchen, der vom entsprechenden Klammerpaar gefunden wurde.
Häufig werden Klammern dazu verwendet, um Substrings und andere Daten aus einem String herauszupflücken. Der Text, auf den der geklammerte Unterausdruck gepasst hat (auch »der von den Klammern erkannte Text« genannt), steht nach der Mustersuche für die weitere Verwendung zur Verfügung. Dies geschieht je nach Sprache auf unterschiedliche Weise, in Perl werden dafür die Variablen $1, $2 usw. benutzt. (Ein häufiger Fehler ist, die ˹\1˼-Syntax außerhalb der Regex zu benutzen; das ist die Syntax von sed und vi.) In der folgenden Tabelle wird gezeigt, wie verschiedene Programme die von den Klammern eingefangenen Textstücke zur Weiterverwendung zur Verfügung stellen.
Tabelle: Einige Programme und wie man darin eingefangenen Text weiterverarbeitet.
Programm | Vollständiger Treffer | Erstes Klammerpaar |
---|---|---|
GNU egrep | - | - |
GNU Emacs | (match-string 0) (\& im Ersatztext) | (match-string 1) (\1 im Ersatztext) |
GNU awk | substr($text, RSTART, RLENGTH) (\& im Ersatztext) | - |
MySQL | - | - |
Perl (siehe Nebeneffekte bei erfolgreicher Mustererkennung) | $& | $1 |
PHP (siehe Trefferdaten auswerten) | $treffer[0] | $treffer[1] |
Python (siehe Reguläre Ausdrücke in Python) | MatchObj.group(0) | MatchObj.group(1) |
Ruby | $& | $1 |
GNU sed | \& (nur im Ersatztext von s///) | \1 (nur in Regex und Ersatztext) |
Java (siehe Gebrauch von regulären Ausdrücken in Java) | MatcherObj.group() | MatcherObj.group(1) |
Tcl | In Variablen, die der Benutzer beim Aufruf von regexp bzw. regsub angibt | |
VB.NET (siehe Reguläre Ausdrücke in VB und anderen .NET-Sprachen) | MatchObj.Groups(0) | MatchObj.Groups(1) |
C# | MatchObj.Groups(0) | MatchObj.Groups(1) |
vi | & | \1 |
Programmversionen siehe: Programmversionen in diesem Buch. |
Nur gruppierende (nicht-einfangende) Klammern: (?:...)
Bei den nur gruppierenden oder nicht-einfangenden Klammern, ˹(?:...)˼, wird der Text, der vom darin enthaltenen Unterausdruck erkannt wurde, nicht abgespeichert. Sie werden nur für die Gruppenbildung für die Alternation oder für die Quantoren benutzt. Diese Klammern werden beim Nummerieren für $1, $2 usw. nicht mitgezählt. Nach einer erfolgreichen Mustersuche mit ˹(1|ein)(?:und|oder)(2|zwei)˼ beispielsweise enthält die Variable $1 den Wert ›1‹ oder ›ein‹, $2 enthält ›2‹ oder ›zwei‹.
Nicht-einfangende Klammern sind aus verschiedenen Gründen nützlich. Man kann mit ihnen eine komplizierte Regex klarer darstellen, weil sich der Leser nicht zu fragen braucht, ob der von einem Klammerpaar eingefangene Text irgendwo anders mit $1 verwendet wird. Die Regex wird effizienter – wenn nicht abgespeichert werden muss, werden Rechenzeit und Speicher eingespart. (Fragen der Effizienz werden unter Die Kunst, reguläre Ausdrücke zu schreiben erörtert.)
Die nicht-einfangenden Klammern sind beim Aufbau eines großen regulären Ausdrucks aus einzelnen Bauteilen sehr praktisch. Betrachten wir das Beispiel von Eine Regex-Sammlung, bei dem die Variable $HostnameRegex einen regulären Ausdruck enthält. Nehmen wir an, wir wären am Whitespace vor und nach einem Hostnamen interessiert und suchen nach solchem mit m/(\s*)$HostnameRegex(\s*)/. Nach der Mustersuche enthält $1 den Whitespace vor und $2 den Whitespace nach dem Hostnamen – könnte man meinen! Falsch, denn der Whitespace nach dem Hostnamen landet in $4, weil unsere Variable $HostnameRegex schon zwei einfangende Klammerpaare enthält:
$HostnameRegex = qr/[-a-z0-9]+(\.[-a-z0-9]+)*\.(com|edu|info)/i;
Hätten wir stattdessen in $HostnameRegex nicht-einfangende Klammern benutzt, wäre die unliebsame Überraschung ausgeblieben:
$HostnameRegex = qr/[-a-z0-9]+(?:\.[-a-z0-9]+)*\.(?:com|edu|info)/i;
Man kann dieses Problem auch mit einer anderen, in Perl nicht verfügbaren Methode lösen. Dabei bekommen Klammern Namen statt Nummern.
Benannte Unterausdrücke: (?<Name>...)
In Python, bei den preg-Funktionen von PHP und in den .NET-Sprachen kann man dem Unterausdruck in einem einfangenden Klammerpaar einen Namen geben. In Python und in PHP wird die Syntax ˹(?P<Name>...)˼ verwendet, bei .NET ˹(?<Name>...)˼, was mir besser gefällt. Hier ein Beispiel in .NET:
˹\b(?<Land>\d+)-(?<Vorwahl>\d+)-(?<Num>\d+)\b˼
Und das Äquivalent in Python/PHP:
˹\b(?P<Land>\d+)-(?P<Vorwahl>\d+)-(?P<Num>\d+)\b˼
Dabei werden die »Namen« mit den entsprechenden Ziffern aus der Telefonnummer ausgefüllt. Im Programm kann man auf die gefundenen Substrings mit den entsprechenden Namen zugreifen, z.B. mit RegexObj.Groups("Land") in VB.NET und den meisten .NET-Sprachen, mit RegexObj.Groups["Land"] in C# und mit RegexObj.group("Land") in Python und $treffer["Land"] in PHP.
Auch innerhalb der Regex kann man in der Art einer Rückwärtsreferenz auf den Text zugreifen, der von den benannten Klammern eingefangen wurde, in .NET mit ˹\k<Land>˼, in Python und PHP mit ˹(?P=Land)˼.
In Python und in .NET (aber nicht in PHP) kann der gleiche Name in ein und demselben regulären Ausdruck mehrfach auftauchen. Nicht selten wird beispielsweise die Vorwahl der Telefonnummer wie in (nnn) in Klammern geschrieben, man könnte dafür (in der .NET-Syntax) ˹...(?:\((?<Vorwahl>\d+)\)|(?<Vorwahl>\d+))...˼ schreiben. In beiden Fällen würde die Vorwahl im benannten Ausdruck Vorwahl aufgefangen.
Atomare Klammern: (?>...)
Atomare Klammern, ˹(?>...)˼, werden Sie ganz leicht verstehen, sobald Sie im Detail wissen, wie die Regex-Maschine vorgeht (siehe ...)">Atomare Gruppen mit (?>...)). Hier muss eine kurze Erklärung anhand eines Beispiels genügen. Sobald der geklammerte Unterausdruck einmal gepasst hat, lässt er den gefundenen Text bis zum Ende der ganzen Mustersuche nicht mehr los (er wird zu einem unteilbaren »Atom«), es sei denn, der gesamte atomare Klammerausdruck müsste aufgegeben oder neu angewendet werden.
Die Regex ˹¡.*!˼ passt normalerweise auf den String ›¡Hola!‹, aber diese Regex passt nicht mehr, wenn der Teil ˹.*˼ in eine atomare Klammer eingepackt wird, ˹¡(?>.*)!˼. In beiden Fällen passt das ˹.*˼ zunächst auf so viele Zeichen wie möglich (›¡Hola!‹), aber im ersten Fall wird das ˹.*˼ vom ˹!˼ am Ende dazu gezwungen, ein Zeichen (das ›!‹) zurückzugeben, damit die Regex als Ganzes passen kann. Im zweiten Fall befindet sich das ˹.*˼ innerhalb der atomaren Klammern (die nie ein Zeichen »zurückgeben«), deshalb bleibt für das ˹!˼ am Ende der Regex nichts übrig, und es wird kein Treffer gefunden.
Das Beispiel zeigt zwar, wie atomare Gruppen funktionieren, gibt aber keinen Hinweis darauf, warum das von Nutzen sein könnte. Man kann damit die Mustersuche wesentlich beschleunigen (siehe Schnellere Fehlschläge mit atomaren Gruppen), und man kann außerdem genauer steuern, welche Teile einer Regex passen sollen und welche nicht (siehe Ewiges Matching mit atomaren Klammern vermeiden).
Alternation: ... |... | ...
Die Alternation erlaubt es, an einer bestimmten Stelle einen von mehreren Unterausdrücken zuzulassen. Die Unterausdrücke heißen Alternativen. Das Symbol ˹|˼ (vertikaler Strich) hat verschiedene Namen, es heißt manchmal »oder« oder Bar. Manche Dialekte verwenden dafür ˹\|˼.
Die Alternation hat einen sehr niedrigen Vorrang, das bedeutet, dass ˹dies und|oder das˼ dasselbe bewirkt wie ˹(dies und)|(oder das)˼ und nicht etwa ˹dies (und|oder) das˼, obwohl vermutlich Letzteres gemeint war.
Bei den meisten Dialekten ist die leere Alternative ˹(klipp|klar|)˼ zulässig. Die leere Alternative passt immer. Dieses Beispiel ist also logisch mit ˹(klipp|klar)?˼ vergleichbar. (Anmerkung: Um ganz genau zu sein: Der Ausdruck ˹(klipp|klar|)˼ ist mit ˹((?:klipp|klar)?)˼ äquivalent. Der Unterschied zu ˹(klipp|klar)?˼ ist geringfügig, aber in manchen Programmiersprachen lässt sich herausfinden, ob ein »Nichts« innerhalb eines Klammerpaars gepasst hat oder ob die Klammer nicht Teil des Gesamttreffers war.) Der POSIX-Standard hingegen verbietet die leere Alternative, auch in lex und den meisten awk-Versionen ist sie nicht zugelassen. Für mich ist sie ganz nützlich und hilft oft, eine Regex klar zu formulieren. Larry Wall hat mir das einmal so erklärt: »Das ist dasselbe wie die Null im Zahlensystem.«
Bedingte reguläre Ausdrücke: (? if then | else)
Mit diesem Konstrukt kann man innerhalb einer Regex eine »if/then/else«-Bedingung formulieren. Im if-Teil steht eine besondere Art von logischer Bedingung, die wir gleich besprechen. Die Teile nach then bzw. else sind normale Unterausdrücke. Wenn die Bedingung im if -Teil wahr ist, wird der then-Unterausdruck angewandt, wenn nicht, wird gesucht, ob der else-Unterausdruck passt (der else-Teil kann fehlen; wenn er fehlt, kann auch das ›|‹ davor weggelassen werden).
Worauf im if-Teil getestet werden kann, hängt vom Regex-Dialekt ab. In den meisten Fällen kann man auf weiter vorn in der Regex eingefangenen Text prüfen oder aber auf ein Lookaround-Konstrukt.
Auf einfangende Klammer-Konstrukte prüfen
Wenn im if-Teil eine Zahl in runden Klammern steht, wird geprüft, ob das zugehörige einfangende Klammerpaar bis zum aktuellen Zeitpunkt der Mustersuche Teil des Gesamttreffers ist. Hier ein HTML-Beispiel mit einem <IMG>-Tag, das entweder allein oder innerhalb von <A>...</A>-Tags steht. Wir verwenden den Modus »Freie Form«, der Bedingungsteil (hier ohne else-Teil) ist (?(1)...):
( <A\s+[^>]+> \s* )? # <A>-Tag, falls vorhanden.
<IMG\s+[^>]+> # <IMG>-Tag.
(?(1)\s*</A>) # </A>-Tag, falls wir vorhin ein <A> gefunden hatten.
Mit dem (1) in ˹(?(1)...)˼ wird geprüft, ob der erste einfangende Klammerausdruck Teil des Treffers ist. »... Teil des Treffers ist« bedeutet etwas anderes als »... auf ein oder mehrere Zeichen aus dem Suchtext gepasst hat«.
Wir betrachten dazu zwei Ansätze, die ein Wort erkennen sollen, das durch spitze Klammern eingefasst ist oder auch nicht: ˹(<)?\w+(?(1)>)˼ funktioniert, aber mit ˹(<?)\w+(?(1)>)˼ geht es nicht. Die zwei unterscheiden sich nur in der Position des ersten Fragezeichens. Im ersten (richtigen) Ansatz bezieht sich das Fragezeichen auf den gesamten Klammerausdruck, also ist der Klammerausdruck mit allem, was darin steht, fakultativ. Im zweiten Fall ist der Klammerausdruck an sich nicht optional, sondern nur das ˹<˼, das er enthält. Der Klammerausdruck ist also immer Teil eines Gesamttreffers, gleichgültig, ob ein ›<‹ gefunden wurde oder nicht. Deshalb wird in diesem fehlerhaften zweiten Fall die Bedingung im if-Teil immer ›wahr‹ sein.
Wenn benannte Klammerausdrücke (siehe oben Benannte Unterausdrücke) unterstützt werden, kann man statt der Nummer des Klammerpaares auch den entsprechenden Namen angeben.
Auf Lookaround-Konstrukte prüfen
Man kann im if-Teil auch ein komplettes Lookaround-Konstrukt wie ˹(?=...)˼ oder ˹(?<=...)˼ angeben. Wenn der Lookaround passt, ist die Bedingung »wahr« und der Unterausdruck im then-Teil wird angewandt; wenn nicht, kommt der else-Teil zum Einsatz. Ein etwas konstruiertes Beispiel: ˹(?(?<=NUM:)\d+|\w+)˼ sucht nach einem ˹NUM:˼ nach Ziffern (˹\d+˼), sonst nach einem Wort (˹\w+˼). Das Lookbehind-Konstrukt ist unterstrichen.
Andere Tests für die Bedingung
In Perl gibt es eine interessante Erweiterung zu diesem Konstrukt: Hier kann im if-Teil ein allgemeines Stück Perl-Code stehen. Der Code wird ausgeführt, sein Rückgabewert wird in der Bedingung geprüft, und je nachdem wird der then- oder der else-Unterausdruck angewendet. Bei Verrückte Dinge mit den Regex-Erweiterungen in Perl unter Perl wird das genauer behandelt.
Gierige Quantoren: *, +, ?, {min, max}
Die Quantoren (Stern, Plus, Fragezeichen und die Intervalle – Metazeichen, die sich auf die Quantität der vorhergehenden Elemente beziehen) sind bereits ausführlich beschrieben worden. Zu bemerken ist noch, dass in manchen Programmen ˹\+˼ und ˹\?˼ statt ˹+˼ und ˹?˼ verwendet werden. Bei manchen können sich die Quantoren nicht auf Rückwärtsreferenzen beziehen, bei wieder anderen nicht auf geklammerte Ausdrücke.
Intervalle – {min, max} oder \{min, max\}
Intervalle sind so etwas wie »zählende Quantoren«, weil sie genau angeben, wie oft das vorhergehende Element mindestens vorkommen muss und wie oft es maximal vorkommen darf. Wenn nur eine einzige Zahl angegeben ist wie in ˹[a-z]{3}˼ (oder ˹[a-z]\{3\}˼, vom Regex-Dialekt abhängig), dann müssen genauso viele Elemente vorkommen. Dieses Beispiel ist identisch mit ˹[a-z][a-z][a-z]˼, das Letztere kann bei bestimmten Typen von Regex-Maschinen effizienter sein (siehe Kleine Quantoren).
Eine Warnung: Benutzen Sie nicht so etwas wie ˹X{0,0}˼, um auszudrücken: »Hier darf kein X vorkommen.« ˹X{0,0}˼ ist tatsächlich ein sinnloser Ausdruck, er bedeutet etwa: »Keine Forderung für ein X hier, und überhaupt, hier muss nach gar nichts gesucht werden.« Das hat die gleiche Wirkung, als ob gar kein ˹X{0,0}˼ angegeben wäre – wenn im String ein X vorkommt, kann es immer noch von weiteren Elementen des regulären Ausdrucks gefunden werden; und damit wird die Absicht vereitelt. (Anmerkung: Was ich hier über {0,0} sage, stimmt zwar, ist aber graue Theorie. In der Praxis verhalten sich die verschiedenen Programme schlimmer – beinahe zufällig! In vielen Programmen (mindestens in GNU awk, GNU grep und älteren Perl-Versionen) wird {0,0} offenbar so behandelt, als ob es ein * wäre. In anderen (den meisten Versionen von sed und in manchen grep-Versionen) bedeutet es dasselbe wie ?. Verrückt!) Für die Bedingung »Hier darf kein X vorkommen« kann man dagegen negatives Lookahead gut verwenden.
Nicht-gierige, »genügsame« Quantoren: *?, +?, ??, {min, max}?
In etlichen Werkzeugen gibt es die unschön aussehenden Quantoren *?, +?, ?? sowie {min, max}?. Dies sind die nicht-gierigen (non-greedy oder lazy) Versionen der bekannten Quantoren. Quantoren sind normalerweise »gierig«, d.h., sie versuchen, so viel Text wie möglich zu erkennen. Im Gegensatz dazu versuchen die nicht-gierigen Quantoren, so wenige Zeichen wie möglich zu »verbrauchen« und doch sicherzustellen, dass die Regex als Ganzes passt. Dieser Unterschied hat weitreichende Auswirkungen, die wir unter Wie Regex-Maschinen arbeiten im Detail besprechen (siehe Zwei wichtige Regeln beim Backtracking).
Possessive Quantoren: *+, ++, ?+, {min, max}+
Im Moment werden die possessiven Quantoren nur von java.util.regex und von PCRE (also auch von PHP) unterstützt, aber es ist leicht möglich, dass sie auch woanders auftauchen. Die possessiven Quantoren versuchen wie die normalen, gierigen Quantoren so viele Zeichen wie möglich zu erkennen, aber wenn sie sich einmal ein Zeichen geschnappt haben, »geben sie es nicht wieder her«. Wie die atomaren Klammern – mit denen sie verwandt sind – sind die possessiven Quantoren viel einfacher zu verstehen, wenn Sie mehr über die internen Vorgänge bei der Mustersuche wissen (das ist das Thema von Wie Regex-Maschinen arbeiten).
In einem gewissen Sinn sind possessive Quantoren nur eine syntaktische Abkürzung, weil man sie mit atomaren Gruppen ersetzen kann. Das Resultat von ˹.++˼ ist genau dasselbe wie ˹(?>.+)˼, eventuell kann eine gute Implementation den possessiven Quantor besser optimieren als die atomare Klammer (siehe Löschen von unnötigen abgespeicherten Zuständen mit possessiven Quantoren).
<< zurück | vor >> |
Tipp der data2type-Redaktion: Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an: |
Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.
O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln