Reguläre Ausdrücke sind speziell kodierte Strings, die als Muster zum Finden von Strings genutzt werden. Sie tauchten zuerst in den 1940er Jahren als Möglichkeit zum Beschreiben regulärer Sprachen auf, ließen sich in der Programmier-Welt aber erst in den 1970er Jahren richtig blicken. Ich fand sie das erste Mal im QED-Texteditor, der von Ken Thompson geschrieben worden war.
»Ein regulärer Ausdruck ist ein Muster, das eine Reihe von Zeichenketten festlegt; es ist zum Finden von bestimmten Strings gedacht.« – Ken Thompson
Reguläre Ausdrücke wurden später ein wichtiger Bestandteil der aus dem Unix-Betriebssystem erwachsenden Toolsuite – die Editoren ed, sed und vi (vim), grep, AWK und andere. Aber die Art und Weise, wie reguläre Ausdrücke implementiert wurden, ist leider nicht so regulär.
Reguläre Ausdrücke haben den Ruf, sehr unleserlich zu sein, aber das hängt nur davon ab, wie Sie sie angehen. Es gibt eine natürliche Entwicklung von so etwas Einfachem wie
d
als Zeichenkürzel, das zu jeder Ziffer zwischen 0 und 9 passt, bis hin zu etwas komplizierteren Dingen wie
^((d{3})|^d{3}[.-]?)?d{3}[.-]?d{4}$
wo wir am Ende des Kapitels landen werden: einem ziemlich robusten regulären Ausdruck, der zur einer zehnstelligen nordamerikanischen Telefonnummer passt – mit oder ohne Klammern um den Area Code, mit oder ohne Bindestriche und Punkte, um die Nummer aufzuteilen. (Die Klammern müssen zudem immer »ausbalanciert« sein, zu jeder öffnenden Klammer muss es auch eine schließende Klammer geben.)
Wenn Ihnen nicht klar ist, wie das alles funktioniert, machen Sie sich keine Sorgen: Ich werde den gesamten Ausdruck in diesem Kapitel nach und nach erläutern. Wenn Sie nur den Beispielen (möglichst im gesamten Buch) folgen, wird Ihnen das Schreiben regulärer Ausdrücke bald ganz locker von der Hand gehen. Sind Sie bereit, in diese neue Welt aufzubrechen?
Gelegentlich stelle ich Unicode-Zeichen in diesem Buch über ihren Codepoint dar – eine vierstellige, hexadezimale (zur Basis 16) Zahl. Diese Codepoints haben die Form U+0000. U+002E ist zum Beispiel der Codepoint für einen Punkt (.).
Sie erkennen Sie vermutlich nicht, aber es ist die Telefonnummer von O'Reilly Media in Sebastopol in Kalifornien.
Lassen Sie uns diese Nummer mit einem regulären Ausdruck finden. Es gibt viele Möglichkeiten, das zu erreichen, aber als Einstieg geben Sie einfach die gleiche Nummer im oberen Bereich ein – so, wie sie im unteren Feld steht:
707-827-7019
Nun sollte die eingegebene Nummer im unteren Feld komplett in Gelb hervorgehoben werden (siehe Abbildung 1.2). Wenn das bei Ihnen auch der Fall ist, sind Sie im Geschäft!
Sie haben in diesem regulären Ausdruck ein Stringliteral verwendet, um einen String im Zieltext zu finden. Ein Stringliteral ist eine buchstabengetreue Darstellung eines Strings.
Jetzt löschen Sie die Nummer im oberen Feld und ersetzen sie durch die einzelne Ziffer 7. Haben Sie gesehen, was gerade passiert ist? Nur die Siebenen sind jetzt hervorgehoben. Der Buchstabe (beziehungsweise die Ziffer) 7 als regulärer Ausdruck findet die vier Vorkommen der Ziffer 7 im Zieltext.
Abbildung 1.2 Zehnstellige Telefonnummer, in Regexpal hervorgehoben
Alle Nummern (genauer gesagt, alle Ziffern) im unteren Feld werden hervorgehoben, abwechselnd in Gelb und Blau. Der reguläre Ausdruck [0-9]
weist nämlich den Regex-Prozessor an: »Finde jede Ziffer im Bereich von 0 bis 9.«
Die eckigen Klammern werden nicht als solche gefunden, weil sie als Metazeichen genutzt werden. Ein Metazeichen hat in regulären Ausdrücken eine besondere Bedeutung und ist reserviert. Ein regulärer Ausdruck der Form [0-9]
wird als Zeichenklasse (Character Class) oder manchmal auch als Zeichenmenge (Character Set) bezeichnet.
Sie können die zu findenden Ziffern weiter einschränken, indem Sie sie einzeln in der Zeichenklasse angeben, zum Beispiel:
[012789]
Damit werden nur die angegebenen Ziffern gefunden, hier also 0, 1, 2, 7, 8 und 9. Geben Sie diesen Ausdruck einmal im oberen Feld ein. Erneut wird jede Ziffer im unteren Feld abwechselnd in Gelb und Blau hervorgehoben.
Um beliebige zehnstellige nordamerikanische Telefonnummern zu finden, deren Teile durch Bindestriche voneinander getrennt sind, könnten Sie folgenden Ausdruck eingeben:
[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]
Das funktioniert, ist aber ganz schön lang. Es gibt eine bessere Möglichkeit, nämlich den Einsatz eines Zeichenkürzels.
Durch ein drei- oder vierfaches Wiederholen von d
werden genau drei beziehungsweise vier aufeinanderfolgende Ziffern gefunden. Der Bindestrich im obigen regulären Ausdruck wird als Literal eingegeben und auch genauso gefunden.
Wie finden wir diesen Bindestrich am besten? Sie können einen literalen Bindestrich (-) wie eben genutzt einsetzen, oder Sie verwenden ein maskiertes großes D (D
), womit jedes Zeichen gefunden wird, das keine Ziffer ist.
Dieses Beispiel nutzt D
statt des literalen Bindestrichs:
dddDdddDdddd
Erneut sollte die gesamte Telefonnummer, einschließlich der Bindestriche, hervorgehoben werden.
Der Punkt dient als Wildcard und findet jedes Zeichen (außer in manchen Situationen ein Zeilenende). Im obigen Beispiel passt der reguläre Ausdruck auf die Bindestriche, aber es könnte hier auch ein Prozentzeichen (%) stehen:
707%827%7019
Oder ein vertikaler Balken (|):
707|827|7019
Oder irgendein anderes Zeichen.
Die 1
bezieht sich auf das, was in der durch Klammern umschlossenen Gruppe gefunden wurde . Als Ergebnis wird dieser Ausdruck die Vorwahl 707
finden. Schlüsseln wir einmal die einzelnen Elemente auf:
(d)
passt auf die erste Ziffer, die damit auch eingefangen wird (die Ziffer 7)
d
passt auf die nächste Ziffer (die 0), fängt sie aber nicht ein, weil sie nicht in Klammern steht
1
verweist auf die eingefangene Ziffer (die 7)
Damit wird nur die Vorwahl gefunden. Machen Sie sich keine Sorgen, wenn Sie das Ganze jetzt noch nicht vollständig verstehen. Sie werde im Buch noch viele Beispiele mit Gruppen finden.
Sie könnten nun die gesamte Telefonnummer mit einer Gruppe und einer Reihe von Rückwärtsreferenzen finden:
(d)01Ddd1D1ddd
Aber das ist nicht so elegant, wie es sein könnte. Lassen Sie uns etwas ausprobieren, was noch besser funktioniert.
Die Zahlen in geschweiften Klammern weisen den Regex-Prozessor an, genau so viele Vorkommen dieser Ziffern zu finden, nach denen Sie suchen. Die geschweiften Klammern mit einer Zahl sind eine Form von Quantoren. Die geschweiften Klammern selbst zählen zu den Metazeichen.
Das Fragezeichen (?
) ist eine andere Form eines Quantors. Es folgt auf den Bindestrich im regulären Ausdruck und bedeutet, dass der Bindestrich optional ist – er kann also null- oder einmal vorkommen (»One or None«). Es gibt noch andere Quantoren wie zum Beispiel das Pluszeichen (+
), das für »einmal oder häufiger« steht, oder der Asterisk (*
) für »nullmal oder häufiger«.
Mit Quantoren können Sie einen regulären Ausdruck viel knapper halten:
(d{3,4}[.-]?)+
Das Pluszeichen bedeutet hier, dass das vorige Element einmal oder häufiger vorkommen kann. Dieser reguläre Ausdruck findet entweder drei oder vier Ziffern, gefolgt von einem optionalen Bindestrich oder Punkt. Das Ganze ist durch Klammern zu einer Gruppe zusammengefasst, die durch das Pluszeichen (+
) einmal oder häufiger gefunden werden kann.
Raucht Ihnen der Kopf? Ich hoffe nicht. Hier eine Zeichen-für-Zeichen-Analyse dieses Ausdrucks: