Anker und andere »Zusicherungen der Länge null«
(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)
Die Zeilenanker und einige andere Metazeichen sind »Zusicherungen der Länge null«, sie erkennen keine Zeichen, sondern sie passen auf eine bestimmte Position im Text.
Zeilenanfang, Stringanfang: ^, \A
Der Zirkumflex ˹^˼ passt auf den Anfang des zu durchsuchenden Texts und, im Mehrzeilenmodus (siehe Der Modus »Verbesserte Zeilenanker« oder Mehrzeilenmodus), auf die Positionen nach jedem Newline im Text. Bei manchen Systemen passt ˹^˼ im Mehrzeilenmodus auch auf die Position nach einem Unicode-Zeilenendezeichen (siehe Das Zeilenende in Unicode).
Der Anker ˹\A˼ passt, sofern er unterstützt wird, immer nur auf den Anfang des Suchtexts, unabhängig vom Modus.
Zeilenende, Stringende: $, \Z, \z
Wie die nächste Tabelle nahelegt, ist der Begriff »Zeilenende« etwas komplizierter als sein Gegenstück. Je nach verwendetem Werkzeug hat ˹$˼ eine leicht unterschiedliche Wirkung. Meist passt es auf das Ende des Strings, aber auch auf die Position vor dem Newline am Ende des Suchtexts. So bedeutet ˹s$˼ salopp gesagt »ein s am Zeilenende«, unabhängig davon, ob die Zeile noch ein Zeilenendezeichen hat (›...sNL‹) oder nicht.
Es gibt noch zwei mögliche Bedeutungen: Bei manchen Systemen passt ˹$˼ nur genau auf das Ende des Suchtexts, bei anderen auf die Position vor einem Newline. Bei manchen Unicodef ähigen Systemen wird die Position nach jedem Unicode-Zeilenendezeichen erkannt (siehe Das Zeilenende in Unicode; in Java ist die Bedeutung von ˹$˼ in Bezug auf Unicode-Zeilenendezeichen sehr unübersichtlich, siehe Zeilenendezeichen in Unicode).
Je nach Regex-Modus (siehe Der Modus »Verbesserte Zeilenanker« oder Mehrzeilenmodus) verändert sich die Wirkung von ˹$˼ derart, dass auch die Position vor jedem Newline (bzw. Unicode-Zeilenendezeichen)mitten im String erkannt wird.
Wenn das Metazeichen ˹\Z˼ unterstützt wird, erkennt es das Gleiche wie ˹$˼ im »normalen« Modus (also nicht im Mehrzeilenmodus). In den meisten Implementationen ist das die Position am Ende des Suchtexts und die Position vor einem Newline am Ende des Suchtexts. Der Anker ˹\z˼ passt nur genau am Ende des Suchtexts, ungeachtet jeglicher Modi und Newlines.
Tabelle: String- und Zeilenanker in einigen Skriptsprachen.
Eigenschaft | Java | Perl | PHP | Python | Ruby | Tcl | .Net | ||
---|---|---|---|---|---|---|---|---|---|
Normalerweise... | |||||||||
passt ^ am Anfang des ganzen Strings | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
passt ^ nach jedem Newline | ✓2 | ||||||||
passt $ am Ende des ganzen Strings | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
passt $ vor einem Newline am Stringende | ✓1 | ✓ | ✓ | ✓ | ✓ | ✓ | |||
passt $ vor jedem Newline | ✓2 | ||||||||
Verbesserter Zeilenanker-Modus ist unterstützt (siehe Der Modus »Verbesserte Zeilenanker« oder Mehrzeilenmodus) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
Im verbesserten Zeilenanker-Modus (Mehrzeilenmodus) ... | |||||||||
passt ^ am Anfang des ganzen Strings | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | ||
passt ^ nach jedem Newline | ✓1 | ✓ | ✓ | ✓ | - | ✓ | ✓ | ||
passt $ am Ende des ganzen Strings | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | ||
passt $ vor jedem Newline | ✓1 | ✓ | ✓ | ✓ | - | ✓ | ✓ | ||
\A passt immer so wie normalerweise ^ | ✓ | ✓ | ✓ | ✓ | ●4 | ✓ | ✓ | ||
\Z passt immer so wie normalerweise $ | ✓ | ✓ | ✓ | ●3 | ●5 | ✓ | ✓ | ||
\z passt immer nur auf das Ende des ganzen Strings | ✓ | ✓ | ✓ | - | - | ✓ | ✓ | ||
✓1 | Das Java-Regex-Package von Sun unterstützt in diesen Fällen die Unicode-Zeilenendezeichen. | ||||||||
✓2 | In Ruby passen ^ und $ auf Newlines mitten im String, nicht aber \A und \Z. | ||||||||
●3 | In Python erkennt \Z nur das Stringende. | ||||||||
●4 | In Ruby passt \A nur auf den Stringanfang, im Gegensatz zu ^. | ||||||||
●5 | In Ruby passt \Z auf das Stringende oder vor einem Newline direkt davor, im Gegensatz zu $. | ||||||||
Programmversionen siehe: Programmversionen in diesem Buch. |
Beginn der neuen (oder Ende der letzten) Mustersuche: \G
Das Metazeichen ˹\G˼ wurde bei Perl zuerst im Zusammenhang mit der globalen Mustersuche mit /g eingeführt (siehe unter Programmbeispiel: Serienbrief). Es passt an der Position, bei der die letzte Mustersuche aufgehört hat. Bei der ersten Iteration ist das der Anfang des Strings, wie bei ˹\A˼.
Wenn kein Treffer gefunden wird, wird diese Position, auf die ˹\G˼ passt, wieder auf den Anfang des Suchstrings zurückgesetzt. Wenn ein regulärer Ausdruck wiederholt angewendet wird, zum Beispiel mit ˹s/.../.../g˼ in Perl oder mit einer »Match all«-Funktion in einer anderen Sprache, dann wird bei der letzten Iteration kein Treffer mehr gefunden und die Position für das ˹\G˼ auf den Anfang des Suchstrings zurückgesetzt.
Das ˹\G˼ hat drei besondere Aspekte, die ich für nützlich und interessant halte:
- Die mit dem ˹\G˼ verbundene Position ist ein Merkmal des Suchstrings, nicht eines der Regex, in der ˹\G˼ auftritt. Das bedeutet, dass man mit mehreren verschiedenen regulären Ausdrücken im gleichen String suchen kann und mit ˹\G˼ dort weitersucht, wo die letzte Suche aufgehört hat.
- In Perl gibt es außerdem den Modifikator /c (siehe unter Jedes Text-Element einzeln erkennen mit /gc), der bewirkt, dass die ˹\G˼-Position nach einer erfolglosen Suche nicht auf den Anfang des Suchstrings zurückgesetzt wird. Damit kann man einen String mit verschiedenen regulären Ausdrücken testen und fährt nur dann im Suchstring fort, wenn eine Regex gepasst hat.
- Die mit ˹\G˼ verknüpfte Position im Suchstring kann mit Regex-fremden Mitteln (mit der pos-Funktion von Perl) abgefragt und verändert werden. Man könnte so die Position initialisieren, bevor überhaupt mit einer Mustersuche begonnen wurde. Wenn die Sprache diese Funktionalität unterstützt, kann man damit auch das Verhalten aus dem vorherigen Punkt (mit /c) simulieren, wenn dieses nicht implementiert ist.
Unter Ein Beispiel für den Gebrauch von \G in Perl sehen Sie ein ausgeführtes Beispiel einer sinnvollen Anwendung, die ohne ˹\G˼ sehr viel komplizierter programmiert werden müsste. Leider funktioniert das ˹\G˼ in Perl nur dann zuverlässig, wenn es an erster Stelle in der Regex auftritt; immerhin ist das auch der Ort, wo es wohl in der großen Mehrheit der Fälle ohnehin hingehört.
Ende der letzten Mustersuche oder Anfang der aktuellen?
Je nach Implementation bezieht sich ˹\G˼ auf den »Anfang der aktuellen Mustersuche« oder auf das »Ende des letzten Treffers«. In den meisten Fällen fallen diese zwei zusammen, und man braucht sich nicht um diese feine Unterscheidung zu kümmern. Es gibt aber einen Unterschied, und unter CSV-Dateien verarbeiten finden Sie ein praktisches Beispiel für einen Fall, in dem das eine Rolle spielt. Der Unterschied ist mit einem konstruierten Beispiel aber einfacher zu verstehen. Nehmen wir an, ˹x?˼ werde auf den String ›abcde‹ angewandt. Die Regex passt bei ›▵abcde‹, und es wird kein Text verbraucht. Bei einer globalen Suche wird die Regex immer wieder angewendet, bis kein Treffer mehr gefunden wird, und zwar jedes Mal von der Position aus, bei der der letzte Treffer geendet hat – außer das »Getriebe« der Regex-Maschine greift ein. In diesem Fall gäbe es eine unendliche Schleife, wenn bei jeder Iteration an der gleichen Stelle gesucht würde, deshalb schaltet das Getriebe ein Zeichen weiter, wenn diese Situation erkannt wird (siehe Das »Getriebe« schaltet zum nächsten Zeichen). Sie können dieses Verhalten bei s/x?/!/g gut erkennen: Sie bekommen ›!a!b!c!d!e!‹.
Dadurch ergibt sich als Nebeneffekt, dass in diesem Fall das »Ende des letzten Treffers« nicht mit dem »Anfang der aktuellen Mustersuche« übereinstimmt. Daraus ergibt sich sofort die Frage: Auf welche der zwei Positionen passt denn nun ˹\G˼? In Perl ergibt s/\Gx?/!/g das Resultat ›!abcde‹, also bezieht sich ˹\G˼ in Perl auf das Ende des letzten Treffers. Wenn das Getriebe nach der ersten Runde ein Zeichen weiterschaltet, passt ˹\G˼ nicht mehr, und die Mustersuche ist beendet.
Bei anderen Programmiersprachen ist es umgekehrt: Da erhält man ohne das ˹\G˼ das Resultat ›!a!b!c!d!e!‹; also bezieht sich ˹\G˼ dort auf den Anfang der neuen Mustersuche, nachdem das Regex-Getriebe ein Zeichen weitergeschaltet hat.
Man kann sich hier nicht einmal auf die Dokumentation verlassen; sowohl die von Microsofts .NET als auch die von Suns Java-Package waren in diesem Punkt unzuverlässig, bis ich auf die Fehler hingewiesen hatte. Jetzt ist es also so, dass sich das ˹\G˼ in PHP und in Ruby auf den Anfang der aktuellen Suche bezieht, in Perl, java.util.regex und in den .NET-Sprachen auf das Ende des letzten Treffers.
Ein Beispiel für den Gebrauch von \G in Perl |
Dies ist ein Entwurf für ein kleines Programm, das HTML-Code in der Variablen $html prüft. Es darf darin nur eine kleine Untermenge von HTML verwendet werden (einfache <IMG>- und <A>-Tags und Entities wie >). Ich habe diese Methode bei einem Programm für Yahoo! verwendet und damit HTML-Code geprüft, den die Benutzer zum Server schicken. Der Code nutzt das Verhalten des Match-Operators m/.../gc. Dieser wendet den regulären Ausdruck auf den Text ab der Stelle an, an der der letzte erfolgreiche Treffer geendet hat. Wenn nichts gefunden wird, bleibt die Position im String erhalten (siehe Jedes Text-Element einzeln erkennen mit /gc) Mit diesem Verhalten arbeiten die verschiedenen, regulären Ausdrücke (\G(\w+), \G[^<>&\w]+, \G<img\s+([^>]+)>, \G<A\s+([^>]+)>, \G</A>, \G&(#\d+|\w+); und \G(.{1,12})) aus dem folgenden Code-Beispiel als »HTML-Tag-Team« zusammen und arbeiten sich durch den Text durch. Das ähnelt in gewisser Weise einer großen Alternation, aber auf diese Art können wir nach jeder Regex ein maßgeschneidertes Stück Code einfügen. |
my $need_close_anchor = 0; # ›Wahr‹, wenn wir ein <A>, aber noch kein schließendes
# </A> angetroffen haben.
while (not $html =~ m/\G\z/gc) { # Solange wir noch nicht alles durchgearbeitet haben ...
if ($html =~ m/\G(\w+)/gc) {
# Wir haben ein Wort oder eine Zahl in $1, die wir jetzt prüfen könnten ...
} elsif ($html =~ m/\G[^<>&\w]+/gc) {
# Weder Wort noch Zahl noch HTML -- erlaubt.
} elsif ($html =~ m/\G<img\s+([^>]+)>/gci) {
# Image-Tag, Inhalt auf Zulässigkeit prüfen ...
.
.
.
} elsif (not $need_close_anchor and $html =~ m/\G<A\s+([^>]+)>/gci) {
# Ein Link -- den wir hier überprüfen können ...
.
.
.
$need_close_anchor = 1; # Wir brauchen jetzt ein </A>.
} elsif ($need_close_anchor and $html =~ m{\G</A>}gci){
$need_close_anchor = 0; # </A> gefunden, kein weiteres erlaubt.
} elsif ($html =~ m/\G&(#\d+|\w+);/gc) {
# Entities wie > oder { zulassen.
} else {
# Bis hier nichts erkannt, muss ein Fehler sein. Wir holen ein Dutzend Zeichen aus dem
# HTML-Text ab der aktuellen Position und geben damit eine Fehlermeldung aus.
my $location = pos($html); # \G-Position ist der Offset des Fehlers.
my ($badstuff) = $html =~ m/\G(.{1,12})/s;
die "Unerwartetes HTML bei $location: $badstuff\n";
}
}
if ($need_close_anchor) { # Sicherstellen, dass kein offenes <A> zurückbleibt.
die "Kein </A> am Ende."
}
Wortgrenzen: \b, \B, \<, \>, ...
Wie die Zeilenanker passen auch die Metazeichen für Wortgrenzen auf eine bestimmte Position im Suchstring. Es gibt dabei zwei Ansätze: Bei dem einen gibt es zwei verschiedene Metasequenzen für Wortanfang und -ende (meist \< und \>), beim anderen eine einzige Metasequenz für die Wortgrenze (meist \b). Bei beiden Varianten gibt es meist eine zusätzliche Metasequenz für die Nicht-Wortgrenze (meist \B). In der nächsten Tabelle sind diese Metasequenzen für einige Werkzeuge und Programmiersprachen aufgeführt. Wenn ein Werkzeug kein explizites Metazeichen für den Wortanfang und das Wortende kennt, dafür aber Lookaround, kann man die Wortgrenzen-Bedingung mit Lookaround-Konstrukten emulieren. In der Tabelle habe ich, wo möglich, diese Ersatzdarstellungen eingetragen.
»Wortgrenze« wird für gewöhnlich als eine Stelle definiert, bei der auf der einen Seite ein Wortzeichen und auf der anderen ein Nicht-Wortzeichen auftritt. Welche Zeichen genau als Wortzeichen gelten, hängt vom benutzten Werkzeug ab. Es wäre sicher sinnvoll, wenn die hier benutzte Definition mit der für \w zusammenfiele, aber das ist nicht überall der Fall.
Tabelle: Einige Werkzeuge und ihre Metasequenzen für Wortgrenzen.
Programm | Wortanfang ... Wortende | Wortgrenze | Nicht-Wortgrenze |
---|---|---|---|
GNU awk | \< ... \> | \y | \B |
GNU egrep | \< ... \> | \b | \B |
GNU Emacs | \< ... \> | \b | \B |
Java | (?<!\pL)(?=\pL) ... (?<=\pL)(?!\pL) | \b a | \B a |
MySQL | [[:<:]] ... [[:>:]] | [[:<:]]|[[:>:]] | |
.NET | (?<!\w)(?=\w) ... (?<=\w)(?!\w) | \b | \B |
Perl | (?<!\w)(?=\w) ... (?<=\w)(?!\w) | \b | \B |
PHP | (?<!\w)(?=\w) ... (?<=\w)(?!\w) | \b | \B a |
Python | (?<!\w)(?=\w) ... (?<=\w)(?!\w) | \b | \B |
Ruby | \b a | \B a | |
GNU sed | \< ... \> | \b | \B |
Tcl | \m ... \M | \y | \Y |
Programmversionen siehe: Programmversionen in diesem Buch. a Funktioniert nur für ASCII-Zeichen (oder für solche aus dem gewählten 8-Bit-Locale), obwohl die Implementation an sich Unicode-fähig ist. |
Bei PHP und java.util.regex beispielsweise bezieht sich \w nur auf ASCII und nicht auf alle Unicode-Zeichen, deshalb habe ich in der Tabelle in den Lookaround-Ersatzdarstellungen statt \w die Unicode-»Letter«-Eigenschaft \pL verwendet (die wiederum eine Abkürzung für ˹\p{L}˼ ist, siehe unter Unicode-Eigenschaften, Schriftsysteme und Blockbereiche).
Welche Zeichen auch immer zu den »Wortzeichen« gerechnet werden, bei diesen Wort-Ankern werden einfach zwei nebeneinanderliegende Zeichen miteinander verglichen. Keine Regex-Implementation sucht in einem Wörterbuch oder führt linguistische Untersuchungen durch, um zu entscheiden, was ein Wort ist und was nicht: Bei allen Systemen ist »NE14AD8« ein Wort, nicht aber »M.I.T.«.
Lookahead: (?=...), (?!...); Lookbehind: (?<=...), (?<!...)
Die Lookahead- und Lookbehind-Konstrukte (zusammen Lookaround genannt) wurden im Abschnitt Große Zahlen in Dreiergruppen aufteilen: Lookaround anhand eines längeren Beispiels schon eingehend behandelt. Dabei haben wir einen wichtigen Punkt außer Acht gelassen: welche Arten von Ausdrücken in den jeweiligen Lookbehind-Konstrukten auftreten dürfen. Bei den meisten Implementationen gibt es nämlich Einschränkungen, wie lang der von einem Lookbehind-Konstrukt erkannte Text sein darf (bei Lookahead gibt es keine solchen Restriktionen).
Die einschneidendste Beschränkung gibt es in Perl und in Python, hier darf in der Lookbehind-Klammer nur nach einem String fester Länge gesucht werden. Zum Beispiel sind (?<!\w) und (?<!klipp|klar) erlaubt, nicht aber (?<!Flops?) und (?<!^\w+:), denn diese können auf Texte unterschiedlicher Länge passen. In manchen Fällen kann man den Ausdruck umformen, beispielsweise (?<!Flops?) durch ˹(?<!Flop)(?<!Flop|Flops)˼ ersetzen, aber besonders übersichtlich ist das nicht.
Bei den nächstbesseren Implementationen darf das Lookbehind-Konstrukt Alternativen verschiedener Länge aufweisen, man kann (?<!Flops?) dann durch (?<Flop|Flops) ersetzen. Dies ist bei PCRE (und damit auch bei den preg-Funktionen von PHP) der Fall.
Noch mal einfacher wird es, wenn der Lookbehind-Ausdruck auf Text unterschiedlicher Länge passen kann, aber nur bis zu einer bestimmten Maximallänge. Dann wird (?<!Flops?) direkt möglich, nicht aber (?<!^\w+:), weil der damit erkannte String potenziell unendlich lang ist. So verhält sich die Regex-Implementation im Java-Package von Sun.
Prinzipiell sind diese drei beschränkten Arten der Lookbehind-Unterstützung äquivalent, weil man alle drei mit der einfachsten Art simulieren kann (auch wenn das sehr schwerfällig wird). Die vierte und am weitesten gehende Implementation erlaubt Lookbehind-Ausdrücke, die auf beliebig lange Textstrings passen können, auch das Beispiel (?<!^\w+) ist hier erlaubt. Die .NET-Sprachen von Microsoft unterstützen diese Art von Lookbehind. Damit geht allerdings die Gefahr von sehr ineffizientem Code einher, wenn dieses Feature unbedacht verwendet wird. (Bei Lookbehind-Konstrukten, die auf Strings jeder Länge passen können, muss die Regex-Maschine bis an den Anfang des Strings zurückgehen und jede Treffermöglichkeit testen; wenn sich die aktuelle Position am Ende eines langen Strings befindet, wird das enorm aufwendig.)
<< 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