Sie sind auf Seite 1von 532

Ei

Detaillierte Lösungen

ns
t
für acht Programmiersprachen

M s-Tu
ie
it to
g

ria
l
Reguläre
Ausdrücke
Kochbuch
Jan Goyvaerts & Steven Levithan
O’Reilly Deutsche Übersetzung von Thomas Demmig
Reguläre Ausdrücke Kochbuch

Jan Goyvaerts & Steven Levithan

Deutsche Übersetzung von Thomas Demmig

Beijing · Cambridge · Farnham · Köln · Sebastopol · Taipei · Tokyo


Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können
Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen
keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und
deren Folgen.
Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind
möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den
Schreibweisen der Hersteller. 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.

Kommentare und Fragen können Sie gerne an uns richten:


O’Reilly Verlag
Balthasarstr. 81
50670 Köln
Tel.: 0221/9731600
Fax: 0221/9731608
E-Mail: kommentar@oreilly.de

Copyright der deutschen Ausgabe:


© 2010 by O’Reilly Verlag GmbH & Co. KG

Die Originalausgabe erschien 2009 unter dem Titel


Regular Expressions Cookbook im Verlag O’Reilly Media, Inc.

Die Darstellung einer Spitzmaus im Zusammenhang


mit dem Thema Reguläre Ausdrücke ist ein Warenzeichen von O’Reilly Media, Inc.

Bibliografische Information Der Deutschen Bibliothek


Die Deutsche Bibliothek verzeichnet diese Publikation in der
Deutschen Nationalbibliografie; detaillierte bibliografische Daten
sind im Internet über http://dnb.ddb.de abrufbar.

Lektorat: Alexandra Follenius & Susanne Gerbert, Köln


Korrektorat: Sibylle Feldmann, Düsseldorf
Satz: Tim Mergemeier, Reemers Publishing Services GmbH, Krefeld, www.reemers.de
Umschlaggestaltung: Michael Oreal, Köln
Produktion: Karin Driesen & Andrea Miß, Köln
Belichtung, Druck und buchbinderische Verarbeitung:
Druckerei Kösel, Krugzell; www.koeselbuch.de

ISBN 978-3-89721-957-1

Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.


Inhalt

Vorwort ......................................................... XI

1 Einführung in reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Definition regulärer Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Suchen und Ersetzen mit regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Tools für das Arbeiten mit regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2 Grundlagen regulärer Ausdrücke ..................................... 27


2.1 Literalen Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2 Nicht druckbare Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3 Ein oder mehrere Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.4 Ein beliebiges Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.5 Etwas am Anfang und/oder Ende einer Zeile finden. . . . . . . . . . . . . . . . . . 39
2.6 Ganze Wörter finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode . . . . . 47
2.8 Eine von mehreren Alternativen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.9 Gruppieren und Einfangen von Teilen des gefundenen Texts . . . . . . . . . . 61
2.10 Vorher gefundenen Text erneut finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
2.11 Teile des gefundenen Texts einfangen und benennen . . . . . . . . . . . . . . . . 66
2.12 Teile der Regex mehrfach wiederholen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
2.13 Minimale oder maximale Wiederholung auswählen . . . . . . . . . . . . . . . . . 72
2.14 Unnötiges Backtracking vermeiden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.15 Aus dem Ruder laufende Wiederholungen verhindern. . . . . . . . . . . . . . . . 78
2.16 Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis
hinzuzufügen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden. . . . . . . 87
2.18 Kommentare für einen regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . . . . 90

| V
2.19 Literalen Text im Ersetzungstext nutzen. . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2.20 Einfügen des Suchergebnisses in den Ersetzungstext . . . . . . . . . . . . . . . . . 95
2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen . . . . . . . . . . . . 96
2.22 Suchergebniskontext in den Ersetzungstext einfügen . . . . . . . . . . . . . . . . 100

3 Mit regulären Ausdrücken programmieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103


3.1 Literale reguläre Ausdrücke im Quellcode . . . . . . . . . . . . . . . . . . . . . . . . 109
3.2 Importieren der Regex-Bibliothek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
3.3 Erstellen eines Regex-Objekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
3.4 Optionen für reguläre Ausdrücke setzen. . . . . . . . . . . . . . . . . . . . . . . . . . 123
3.5 Auf eine Übereinstimmung in einem Text prüfen. . . . . . . . . . . . . . . . . . . 131
3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem
Text prüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
3.7 Auslesen des übereinstimmenden Texts . . . . . . . . . . . . . . . . . . . . . . . . . . 142
3.8 Position und Länge der Übereinstimmung ermitteln . . . . . . . . . . . . . . . . 149
3.9 Teile des übereinstimmenden Texts auslesen . . . . . . . . . . . . . . . . . . . . . . 154
3.10 Eine Liste aller Übereinstimmungen erhalten . . . . . . . . . . . . . . . . . . . . . . 162
3.11 Durch alle Übereinstimmungen iterieren . . . . . . . . . . . . . . . . . . . . . . . . . 167
3.12 Übereinstimmungen in prozeduralem Code überprüfen . . . . . . . . . . . . . 173
3.13 Eine Übereinstimmung in einer anderen Übereinstimmung finden . . . . . 177
3.14 Alle Übereinstimmungen ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
3.15 Übereinstimmungen durch Teile des gefundenen Texts ersetzen . . . . . . . 189
3.16 Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde . . 194
3.17 Alle Übereinstimmungen innerhalb der Übereinstimmungen einer
anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
3.18 Alle Übereinstimmungen zwischen den Übereinstimmungen einer
anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
3.19 Einen String aufteilen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
3.20 Einen String aufteilen und die Regex-Übereinstimmungen behalten . . . . 217
3.21 Zeile für Zeile suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222

4 Validierung und Formatierung ...................................... 227


4.1 E-Mail-Adressen überprüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
4.2 Nordamerikanische Telefonnummern validieren . . . . . . . . . . . . . . . . . . . 233
4.3 Internationale Telefonnummern überprüfen . . . . . . . . . . . . . . . . . . . . . . 239
4.4 Klassische Datumsformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
4.5 Klassische Datumsformate exakt validieren . . . . . . . . . . . . . . . . . . . . . . . 245
4.6 Klassische Zeitformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren . . . . . . . . . . . 252

VI | Inhalt
4.8 Eingabe auf alphanumerische Zeichen beschränken . . . . . . . . . . . . . . . . 257
4.9 Die Länge des Texts begrenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
4.10 Die Zeilenanzahl eines Texts beschränken . . . . . . . . . . . . . . . . . . . . . . . . 265
4.11 Antworten auswerten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
4.12 US-Sozialversicherungsnummern validieren. . . . . . . . . . . . . . . . . . . . . . . 271
4.13 ISBN validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
4.14 ZIP-Codes validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
4.15 Kanadische Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
4.16 Britische Postleitzahlen validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
4.17 Deutsche Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“
umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
4.19 Kreditkartennummern validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
4.20 Europäische Umsatzsteuer-Identifikationsnummern . . . . . . . . . . . . . . . . 294

5 Wörter, Zeilen und Sonderzeichen ................................... 301


5.1 Ein bestimmtes Wort finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
5.2 Eines von mehreren Wörtern finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
5.3 Ähnliche Wörter finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
5.4 Alle Wörter außer einem bestimmten finden . . . . . . . . . . . . . . . . . . . . . . 310
5.5 Ein beliebiges Wort finden, auf das ein bestimmtes Wort nicht folgt . . . 312
5.6 Ein beliebiges Wort finden, das nicht hinter einem bestimmten
Wort steht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
5.7 Wörter finden, die nahe beieinanderstehen . . . . . . . . . . . . . . . . . . . . . . . 317
5.8 Wortwiederholungen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
5.9 Doppelte Zeilen entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
5.10 Vollständige Zeilen finden, die ein bestimmtes Wort enthalten . . . . . . . . 330
5.11 Vollständige Zeilen finden, die ein bestimmtes Wort nicht enthalten . . . 332
5.12 Führenden und abschließenden Whitespace entfernen . . . . . . . . . . . . . . 333
5.13 Wiederholten Whitespace durch ein einzelnes Leerzeichen ersetzen . . . . 336
5.14 Regex-Metazeichen maskieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337

6 Zahlen ......................................................... 341


6.1 Integer-Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
6.2 Hexadezimale Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
6.3 Binärzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
6.4 Führende Nullen entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
6.5 Zahlen innerhalb eines bestimmten Bereichs . . . . . . . . . . . . . . . . . . . . . . 350
6.6 Hexadezimale Zahlen in einem bestimmten Bereich finden . . . . . . . . . . . 357

Inhalt | VII
6.7 Gleitkommazahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
6.8 Zahlen mit Tausendertrennzeichen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
6.9 Römische Zahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364

7 URLs, Pfade und Internetadressen ................................... 367


7.1 URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
7.2 URLs in einem längeren Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
7.3 URLs in Anführungszeichen in längerem Text finden . . . . . . . . . . . . . . . 373
7.4 URLs mit Klammern in längerem Text finden . . . . . . . . . . . . . . . . . . . . . 374
7.5 URLs in Links umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
7.6 URNs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
7.7 Generische URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
7.8 Das Schema aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . 385
7.9 Den Benutzer aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . 386
7.10 Den Host aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
7.11 Den Port aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
7.12 Den Pfad aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
7.13 Die Query aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
7.14 Das Fragment aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . 397
7.15 Domainnamen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
7.16 IPv4-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
7.17 IPv6-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
7.18 Einen Pfad unter Windows validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
7.19 Pfade unter Windows in ihre Bestandteile aufteilen . . . . . . . . . . . . . . . . . 420
7.20 Den Laufwerkbuchstaben aus einem Pfad unter Windows extrahieren . . 425
7.21 Den Server und die Freigabe aus einem UNC-Pfad extrahieren . . . . . . . . 426
7.22 Die Ordnernamen aus einem Pfad unter Windows extrahieren . . . . . . . . 427
7.23 Den Dateinamen aus einem Pfad unter Windows extrahieren . . . . . . . . . 430
7.24 Die Dateierweiterung aus einem Pfad unter Windows extrahieren . . . . . 431
7.25 Ungültige Zeichen aus Dateinamen entfernen . . . . . . . . . . . . . . . . . . . . . 432

8 Markup und Datenaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435


8.1 Tags im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442
8.2 <b>-Tags durch <strong> ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
8.3 Alle Tags im XML-Stil außer <em> und <strong> entfernen . . . . . . . . . . 463
8.4 XML-Namen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
8.5 Einfachen Text durch Ergänzen von <p>- und <br>- Tags nach
HTML konvertieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
8.6 Ein bestimmtes Attribut in Tags im XML-Stil finden . . . . . . . . . . . . . . . . 476

VIII | Inhalt
8.7 Tags vom Typ <table> ein Attribut „cellspacing“ hinzufügen, die es
noch nicht haben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
8.8 Kommentare im XML-Stil entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484
8.9 Wörter in Kommentaren im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . 488
8.10 Ändern der Feldbegrenzer in CSV-Dateien . . . . . . . . . . . . . . . . . . . . . . . . 493
8.11 CSV-Felder aus einer bestimmten Spalte extrahieren . . . . . . . . . . . . . . . . 496
8.12 Sektionsüberschriften in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . 500
8.13 Sektionsblöcke in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
8.14 Name/Wert-Paare in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . . 503

Index .......................................................... 505

Inhalt | IX
Vorwort

Im letzten Jahrzehnt ist die Beliebtheit regulärer Ausdrücke deutlich angestiegen. Heut-
zutage gibt es in allen verbreiteten Programmiersprachen mächtige Bibliotheken zur
Verarbeitung regulärer Ausdrücke. Zum Teil bietet die Sprache sogar selbst die entspre-
chenden Möglichkeiten. Viele Entwickler nutzen diese Features, um den Anwendern
ihrer Applikationen das Suchen und Filtern der Daten mithilfe regulärer Ausdrücke zu
ermöglichen. Reguläre Ausdrücke sind überall.
Es gibt viele Bücher, die sich mit regulären Ausdrücken befassen. Die meisten machen
ihren Job ganz gut – sie erklären die Syntax und enthalten ein paar Beispiele sowie eine
Referenz. Aber es gibt keine Bücher, die Lösungen vorstellen. Lösungen, die auf regulä-
ren Ausdrücken basieren und für eine ganze Reihe von praktischen Problemen aus der
realen Welt genutzt werden können. Bei solchen Problemen geht es vor allem um Fragen
zu Texten auf einem Computer und um Internetanwendungen. Wir, Steve und Jan,
haben uns dazu entschieden, diese Lücke mit diesem Buch zu füllen.
Wir wollten vor allem zeigen, wie Sie reguläre Ausdrücke in Situationen verwenden kön-
nen, in denen weniger Erfahrene im Umgang mit regulären Ausdrücken sagen würden,
das sei nicht möglich, oder in denen Softwarepuristen der Meinung sind, ein regulärer
Ausdruck sei nicht das richtige Tool für diese Aufgabe. Da reguläre Ausdrücke heute
überall zu finden sind, sind sie oft auch als Tool verfügbar, das von Endanwendern
genutzt werden kann. Auch Programmierer können durch die Verwendung einiger weni-
ger regulärer Ausdrücke viel Zeit sparen, etwa wenn sie Informationen suchen und ver-
ändern müssen. Die Alternative ist oft, Stunden oder Tage mit dem Umsetzen in
prozeduralen Code zu verbringen oder eine Bibliothek von dritter Seite zu nutzen.

Gefangen im Gewirr der verschiedenen Versionen


Vergleichbar mit anderen beliebten Dingen der IT-Branche, gibt es auch reguläre Ausdrü-
cke in vielen unterschiedlichen Ausprägungen mit unterschiedlicher Kompatibilität. Das
hat dazu geführt, dass es diverse Varianten eines regulären Ausdrucks gibt, die sich nicht
immer gleich verhalten oder die teilweise gar nicht funktionieren.

| XI
Viele Bücher erwähnen, dass es unterschiedliche Varianten gibt, und führen auch einige
der Unterschiede auf. Aber immer wieder lassen sie bestimmte Varianten unerwähnt –
vor allem wenn in diesen Varianten Features fehlen –, statt auf alternative Lösungen und
Workarounds hinzuweisen. Das ist frustrierend, wenn Sie mit unterschiedlichen Varian-
ten regulärer Ausdrücke in den verschiedenen Anwendungen oder Programmiersprachen
arbeiten müssen.
Saloppe Bemerkungen in der Literatur wie „jeder nutzt mittlerweile reguläre Ausdrücke
im Perl-Stil“ bagatellisieren leider eine ganze Reihe von Inkompatibilitäten. Selbst Pakete
im „Perl-Stil“ besitzen entscheidende Unterschiede, und Perl entwickelt sich ja auch
noch weiter. Solche oberflächlichen Äußerungen können für Programmierer trostlose
Folgen haben und zum Beispiel dazu führen, dass sie eine halbe Stunde oder mehr damit
verbringen, nutzlos im Debugger herumzustochern, statt die Details ihrer Implementie-
rung für reguläre Ausdrücke zu kontrollieren. Selbst wenn sie herausfinden, dass ein Fea-
ture, auf dem sie aufbauen, nicht vorhanden ist, wissen sie nicht immer, wie sie
stattdessen vorgehen können.
Dieses Buch ist das erste, das die am meisten verbreiteten und umfangreichsten Varian-
ten regulärer Ausdrücke nebeneinander aufführt – und zwar durchgängig im ganzen
Buch.

Für wen dieses Buch gedacht ist


Sie sollten dieses Buch lesen, wenn Sie regelmäßig am Computer mit Text zu tun haben –
egal ob Sie einen Stapel Dokumente durchsuchen, Text in einem Texteditor bearbeiten
oder Software entwickeln, die Text durchsuchen oder verändern soll. Reguläre Ausdrü-
cke sind für diese Aufgaben exzellente Hilfsmittel. Das Reguläre Ausdrücke Kochbuch
erklärt Ihnen alles, was Sie über reguläre Ausdrücke wissen müssen. Sie brauchen kein
Vorwissen, da wir selbst einfachste Aspekte regulärer Ausdrücke erklären werden.
Wenn Sie schon Erfahrung mit regulären Ausdrücken haben, werden Sie eine Menge
Details kennenlernen, die in anderen Büchern oder Onlineartikeln häufig einfach über-
gangen werden. Sind Sie jemals über eine Regex gestolpert, die in einer Anwendung
funktioniert hat, in einer anderen aber nicht, werden Sie die in diesem Buch detailliert
und gleichwertig behandelten Beschreibungen zu sieben der verbreitetsten Varianten
regulärer Ausdrücke sehr hilfreich finden. Wir haben das ganze Buch als Kochbuch auf-
gebaut, sodass Sie direkt zu den Themen springen können, die Sie interessieren. Wenn
Sie dieses Buch von vorne bis hinten durchlesen, werden Sie am Ende Meister regulärer
Ausdrücke sein.
Mit diesem Buch erfahren Sie alles, was Sie über reguläre Ausdrücke wissen müssen, und
noch ein bisschen mehr – unabhängig davon, ob Sie Programmierer sind oder nicht.
Wenn Sie reguläre Ausdrücke in einem Texteditor nutzen wollen, in einem Suchtool oder
in irgendeiner Anwendung, die ein Eingabefeld „Regex“ enthält, können Sie dieses Buch
auch ganz ohne Programmiererfahrung lesen. Die meisten Rezepte bieten Lösungen an,
die allein auf einem oder mehreren regulären Ausdrücken basieren.

XII | Vorwort
Die Programmierer unter Ihnen erhalten in Kapitel 3 alle notwendigen Informationen
zum Implementieren regulärer Ausdrücke in ihrem Quellcode. Dieses Kapitel geht davon
aus, dass Sie mit den grundlegenden Features der Programmiersprache Ihrer Wahl ver-
traut sind, aber Sie müssen keine Erfahrung mit dem Einsatz regulärer Ausdrücke in
Ihrem Quellcode mitbringen.

Behandelte Technologien
.NET, Java, JavaScript, PCRE, Perl, Python und Ruby kommen nicht ohne Grund auf
dem Rückseitentext vor. Vielmehr stehen diese Begriffe für die sieben Varianten regulärer
Ausdrücke, die in diesem Buch behandelt werden, wobei alle sieben gleichermaßen
umfassend beschrieben werden. Insbesondere haben wir versucht, alle Uneinheitlichkei-
ten zu beschreiben, die wir in diesen verschiedenen Varianten finden konnten.
Das Kapitel zur Programmierung (Kapitel 3) enthält Code-Listings in C#, Java, Java-
Script, PHP, Perl, Python, Ruby und VB.NET. Auch hier gibt es zu jedem Rezept Lösun-
gen und Erläuterungen für alle acht Sprachen. Damit gibt es in diesem Kapitel zwar
einige Wiederholungen, aber Sie können die Abhandlungen über Sprachen, an denen Sie
nicht interessiert sind, gern überspringen, ohne etwas in der Sprache zu verpassen, die Sie
selbst anwenden.

Aufbau des Buchs


In den ersten drei Kapiteln dieses Buchs geht es um nützliche Tools und grundlegende
Informationen, die eine Basis für die Verwendung regulärer Ausdrücke bilden. Jedes der
folgenden Kapitel stellt dann eine Reihe von regulären Ausdrücken vor, die bestimmte
Bereiche der Textbearbeitung behandeln.
Kapitel 1, Einführung in reguläre Ausdrücke, erläutert die Rolle regulärer Ausdrücke und
präsentiert eine Reihe von Tools, die das Erlernen, Aufbauen und Debuggen erleichtern.
Kapitel 2, Grundlagen regulärer Ausdrücke, beschreibt alle Elemente und Features regulä-
rer Ausdrücke zusammen mit wichtigen Hinweisen zu einer effektiven Nutzung.
Kapitel 3, Mit regulären Ausdrücken programmieren, stellt Coding-Techniken vor und
enthält Codebeispiele für die Verwendung regulärer Ausdrücke in jeder der in diesem
Buch behandelten Programmiersprachen.
Kapitel 4, Validierung und Formatierung, enthält Rezepte für den Umgang mit typischen
Benutzereingaben, wie zum Beispiel Datumswerten, Telefonnummern und Postleitzah-
len in den verschiedenen Staaten.
Kapitel 5, Wörter, Zeilen und Sonderzeichen, behandelt häufig auftretende Textbearbei-
tungsaufgaben, wie zum Beispiel das Testen von Zeilen auf die An- oder Abwesenheit
bestimmter Wörter.

Vorwort | XIII
Kapitel 6, Zahlen, zeigt, wie man Integer-Werte, Gleitkommazahlen und viele andere
Formate in diesem Bereich aufspürt.
Kapitel 7, URLs, Pfade und Internetadressen, zeigt Ihnen, wie Sie mit den Strings umgehen,
die im Internet und in Windows-Systemen für das Auffinden von Inhalten genutzt werden.
Kapitel 8, Markup und Datenaustausch, dreht sich um das Bearbeiten von HTML, XML,
Comma-Separated Values (CSV) und Konfigurationsdateien im INI-Stil.

Konventionen in diesem Buch


Die folgenden typografischen Konventionen werden in diesem Buch genutzt:
Kursiv
Steht für neue Begriffe, URLs, E-Mail-Adressen, Dateinamen und Dateierweiterungen.
Feste Breite
Wird genutzt für Programme, Programmelemente wie Variablen oder Funktionsna-
men, Werte, die das Ergebnis einer Ersetzung mithilfe eines regulären Ausdrucks
sind, und für Elemente oder Eingabetexte, die einem regulären Ausdruck überge-
ben werden. Dabei kann es sich um den Inhalt eines Textfelds in einer Anwendung
handeln, um eine Datei auf der Festplatte oder um den Inhalt einer String-Variablen.
Feste Breite, kursiv
Zeigt Text, der vom Anwender oder durch den Kontext bestimmt werden sollte.
‹RegulärerzAusdruck›
Steht für einen regulären Ausdruck, entweder allein oder so, wie Sie ihn in das Such-
feld einer Anwendung eingeben würden. Leerzeichen in regulären Ausdrücken wer-
den durch graue Kreise wiedergegeben, außer im Free-Spacing-Modus.
«Textzzuzersetzen»
Steht für den Text, der bei einer Suchen-und-Ersetzen-Operation durch den regulä-
ren Ausdruck gefunden wird und dann ersetzt werden soll. Leerzeichen im zu erset-
zenden Text werden mithilfe grauer Kreise dargestellt.
Gefundener Text
Steht für den Teil des Texts, der zu einem regulären Ausdruck passt.
...
Graue Punkte in einem regulären Ausdruck weisen darauf hin, dass Sie diesen
Bereich erst mit Leben füllen müssen, bevor Sie den regulären Ausdruck nutzen
können. Der Begleittext erklärt, was Sie dort eintragen können.
(CR), (LF) und (CRLF)
CR, LF und CRLF in Rahmen stehen für die echten Zeichen zum Zeilenumbruch in
Strings und nicht für die Escape-Zeichen \r, \n und \r\n. Solche Strings können ent-
stehen, wenn man in einem mehrzeiligen Eingabefeld die Eingabetaste drückt oder
im Quellcode mehrzeilige String-Konstanten genutzt werden, wie zum Beispiel die
Verbatim-Strings in C# oder Strings mit dreifachen Anführungszeichen in Python.

XIV | Vorwort
Der „Wagenrücklauf“-Pfeil, den Sie vielleicht auf Ihrer Tastatur auf der Eingabe-
taste sehen, wird genutzt, wenn wir eine Zeile auftrennen müssen, damit sie auf die
Druckseite passt. Geben Sie den Text in Ihrem Quellcode ein, sollten Sie hier nicht
die Eingabetaste drücken, sondern alles auf einer Zeile belassen.

Dieses Icon steht für einen Tipp, einen Vorschlag oder eine allgemeine
Anmerkung.

Dieses Icon steht für einen Warnhinweis.

Die Codebeispiele verwenden


Dieses Buch ist dazu da, Ihnen bei Ihrer Arbeit zu helfen. Sie können den Code dieses
Buchs in Ihren Programmen und Dokumentationen verwenden. Sie brauchen uns nicht um
Erlaubnis zu fragen, solange Sie nicht einen beachtlichen Teil des Codes wiedergeben. Bei-
spielsweise benötigen Sie keine Erlaubnis, um ein Programm zu schreiben, das einige
Codeteile aus diesem Buch verwendet. Für den Verkauf oder die Verbreitung einer CD-
ROM mit Beispielen aus O’Reilly-Büchern brauchen Sie auf jeden Fall unsere Erlaubnis.
Die Beantwortung einer Frage durch das Zitieren dieses Buchs und seiner Codebeispiele
benötigt wiederum keine Erlaubnis. Wenn Sie einen erheblichen Teil der Codebeispiele
dieses Buchs in die Dokumentation Ihres Produkts einfügen, brauchen Sie eine Erlaubnis.
Wir freuen uns über einen Herkunftsnachweis, bestehen aber nicht darauf. Eine Referenz
enthält i.d.R. Titel, Autor, Verlag und ISBN, zum Beispiel: „Reguläre Ausdrücke Koch-
buch von Jan Goyvaerts & Steven Levithan, Copyright 2010, O’Reilly Verlag, ISBN 978-
3-89721-957-1.“
Wenn Sie denken, Ihre Verwendung unserer Codebeispiele könnte den angemessenen
Gebrauch oder die hier erteilte Erlaubnis überschreiten, nehmen Sie einfach mit uns über
kommentar@oreilly.de Kontakt auf.

Danksagung
Wir danken Andy Oram, unserem Lektor bei O’Reilly Media, Inc., für seine Begleitung
bei diesem Projekt vom Anfang bis zum Ende. Ebenso danken wir Jeffrey Friedl, Zak
Greant, Nikolaj Lindberg und Ian Morse für ihre sorgfältigen fachlichen Korrekturen,
durch die dieses Buch umfassender und genauer wurde.

Vorwort | XV
KAPITEL 1
Einführung in reguläre Ausdrücke

Wenn Sie dieses Kochbuch aufgeschlagen haben, sind Sie wahrscheinlich schon ganz
erpicht darauf, ein paar der hier beschriebenen seltsamen Strings mit Klammern und Fra-
gezeichen in Ihren Code einzubauen. Falls Sie schon so weit sind: nur zu! Die verwendba-
ren regulären Ausdrücke sind in den Kapiteln 4 bis 8 aufgeführt und beschrieben.
Aber wenn Sie zunächst die ersten Kapitel dieses Buchs lesen, spart Ihnen das langfristig
möglicherweise eine Menge Zeit. So finden Sie in diesem Kapitel zum Beispiel eine Reihe
von Hilfsmitteln – einige wurden von Jan, einem der beiden Autoren, erstellt –, mit
denen Sie einen regulären Ausdruck testen und debuggen können, bevor Sie ihn in Ihrem
Code vergraben, wo Fehler viel schwieriger zu finden sind. Außerdem zeigen Ihnen diese
ersten Kapitel, wie Sie die verschiedenen Features und Optionen regulärer Ausdrücke
nutzen können, um Ihr Leben leichter zu machen. Sie werden verstehen, wie reguläre
Ausdrücke funktionieren, um deren Performance zu verbessern, und Sie lernen die subti-
len Unterschiede kennen, die in den verschiedenen Programmiersprachen existieren –
selbst in unterschiedlichen Versionen Ihrer bevorzugten Programmiersprache.
Wir haben uns also mit diesen Grundlageninformationen viel Mühe gegeben und sind
recht zuversichtlich, dass Sie sie lesen werden, bevor Sie mit der Anwendung regulärer
Ausdrücke beginnen – oder spätestens dann, wenn Sie nicht mehr weiterkommen und
Ihr Wissen aufstocken wollen.

Definition regulärer Ausdrücke


Im Rahmen dieses Buchs ist ein regulärer Ausdruck eine bestimmte Art von Textmuster,
das Sie in vielen modernen Anwendungen und Programmiersprachen nutzen können.
Mit ihm können Sie prüfen, ob eine Eingabe zu einem Textmuster passt, Sie können
Texte eines bestimmten Musters in einer größeren Datei finden, durch anderen Text oder
durch eine veränderte Version des bisherigen Texts ersetzen, einen Textabschnitt in eine
Reihe von Unterabschnitten aufteilen – oder auch sich selbst ins Knie schießen. Dieses
Buch hilft Ihnen dabei, genau zu verstehen, was Sie tun, um Katastrophen zu vermeiden.

| 1
Geschichte des Begriffs „regulärer Ausdruck“
Der Begriff regulärer Ausdruck kommt aus der Mathematik und der theoretischen Infor-
matik. Dort steht er für eine Eigenschaft mathematischer Ausdrücke namens Regularität.
Solch ein Ausdruck kann als Software mithilfe eines deterministischen endlichen Automa-
ten (DEA) implementiert werden. Ein DEA ist ein endlicher Automat, der kein Back-
tracking nutzt.
Die Textmuster, die von den ersten grep-Tools genutzt wurden, waren reguläre Ausdrücke
im mathematischen Sinn. Auch wenn der Name geblieben ist, sind aktuelle reguläre Aus-
drücke im Perl-Stil keine regulären Ausdrücke im mathematischen Sinn. Sie sind mit einem
nicht deterministischen endlichen Automaten (NEA) implementiert. Später werden Sie
noch mehr über Backtracking erfahren. Alles, was ein normaler Entwickler aus diesem
Textkasten mitnehmen muss, ist, dass ein paar Informatiker in ihren Elfenbeintürmen sehr
verärgert darüber sind, dass ihr wohldefinierter Begriff durch eine Technologie überlagert
wurde, die in der realen Welt viel nützlicher ist.

Wenn Sie reguläre Ausdrücke sinnvoll einsetzen, vereinfachen sie viele Programmier-
und Textbearbeitungsaufgaben oder ermöglichen gar erst deren Umsetzung. Ohne sie
bräuchten Sie Dutzende, wenn nicht Hunderte von Zeilen prozeduralen Codes, um zum
Beispiel alle E-Mail-Adressen aus einem Dokument zu ziehen – Code, der nicht beson-
ders spannend zu schreiben ist und der sich auch nur schwer warten lässt. Mit dem pas-
senden regulären Ausdruck, wie er in Rezept 4.1, zu finden ist, braucht man nur ein paar
Zeilen Code, wenn nicht sogar nur eine einzige.
Aber wenn Sie versuchen, mit einem einzelnen regulären Ausdruck zu viel auf einmal zu
machen, oder wenn Sie Regexes auch dort nutzen, wo sie eigentlich nicht sinnvoll sind,
werden Sie folgendes Statement nachvollziehen können:1
Wenn sich manche Menschen einem Problem gegenübersehen, denken sie: „Ah, ich werde
reguläre Ausdrücke nutzen.“ Jetzt haben sie zwei Probleme.
Dieses zweite Problem taucht jedoch nur auf, wenn diese Menschen die Anleitung nicht
gelesen haben, die Sie gerade in den Händen halten. Lesen Sie weiter. Reguläre Ausdrü-
cke sind ein mächtiges Tool. Wenn es bei Ihrer Arbeit darum geht, Text auf einem Com-
puter zu bearbeiten oder zu extrahieren, wird Ihnen ein solides Grundwissen über
reguläre Ausdrücke viele Überstunden ersparen.

Viele Varianten regulärer Ausdrücke


Okay, der Titel des vorigen Abschnitts war gelogen. Wir haben gar nicht definiert, was
reguläre Ausdrücke sind. Das können wir auch nicht. Es gibt keinen offiziellen Standard,
der genau definiert, welche Textmuster reguläre Ausdrücke sind und welche nicht. Wie

1 Jeffrey Friedl hat die Geschichte dieses Zitats in seinem Blog verfolgt: http://regex.info/blog/2006-09-15/247.

2 | Kapitel 1: Einführung in reguläre Ausdrücke


Sie sich vorstellen können, hat jeder Designer einer Programmiersprache und jeder Ent-
wickler einer textverarbeitenden Anwendung eine andere Vorstellung davon, was genau
ein regulärer Ausdruck tun sollte. Daher sehen wir uns einem ganzen Reigen von Varian-
ten regulärer Ausdrücke gegenüber.
Glücklicherweise sind die meisten Designer und Entwickler faul. Warum sollte man
etwas total Neues aufbauen, wenn man kopieren kann, was schon jemand anderer
gemacht hat? Im Ergebnis lassen sich alle modernen Varianten regulärer Ausdrücke, ein-
schließlich derer, die in diesem Buch behandelt werden, auf die Programmiersprache Perl
zurückverfolgen. Wir nennen diese Varianten reguläre Ausdrücke im Perl-Stil. Ihre Syntax
ist sehr ähnlich und meist auch kompatibel, aber eben nicht immer.
Autoren sind ebenfalls faul. Meistens verwenden wir den Ausdruck Regex oder Regexp,
um einen einzelnen regulären Ausdruck zu bezeichnen, und Regexes für den Plural.
Regex-Varianten entsprechen nicht eins zu eins den Programmiersprachen. Skriptspra-
chen haben meist ihre eigene, eingebaute Regex-Variante. Andere Programmiersprachen
nutzen Bibliotheken, um eine Unterstützung regulärer Ausdrücke anzubieten. Manche
Bibliotheken gibt es für mehrere Sprachen, während man bei anderen Sprachen auch aus
unterschiedlichen Bibliotheken wählen kann.
Dieses einführende Kapitel kümmert sich nur um die Varianten regulärer Ausdrücke und
ignoriert vollständig irgendwelche Programmierüberlegungen. Kapitel 3 beginnt dann
mit den Codebeispielen, sodass Sie schon mal in Kapitel 3, Mit regulären Ausdrücken pro-
grammieren, spicken könnten, um herauszufinden, mit welchen Varianten Sie arbeiten
werden. Aber ignorieren Sie erst einmal den ganzen Programmierkram. Die im nächsten
Abschnitt vorgestellten Tools ermöglichen einen viel einfacheren Weg, die Regex-Syntax
durch „Learning by Doing“ kennenzulernen.

Regex-Varianten in diesem Buch


In diesem Buch haben wir die Regex-Varianten ausgewählt, die heutzutage am verbrei-
tetsten sind. Es handelt sich bei allen um Regex-Varianten im Perl-Stil. Manche der Vari-
anten besitzen mehr Features als andere. Aber wenn zwei Varianten das gleiche Feature
besitzen, haben sie meist auch die gleiche Syntax dafür. Wir werden auf die wenigen,
aber nervigen Unregelmäßigkeiten hinweisen, wenn wir ihnen begegnen.
All diese Regex-Varianten sind Teile von Programmiersprachen und Bibliotheken, die
aktiv entwickelt werden. Aus der Liste der Varianten können Sie ersehen, welche Versio-
nen in diesem Buch behandelt werden. Im weiteren Verlauf des Buchs führen wir die
Varianten ohne Versionen auf, wenn die vorgestellte Regex überall gleich funktioniert.
Das ist nahezu immer der Fall. Abgesehen von Fehlerkorrekturen, die Grenzfälle betref-
fen, ändern sich Regex-Varianten im Allgemeinen nicht, es sei denn, es werden neue
Features ergänzt, die einer Syntax Bedeutung verleihen, die vorher als Fehler angesehen
wurde:

Definition regulärer Ausdrücke | 3


Perl
Perls eingebaute Unterstützung für reguläre Ausdrücke ist der Hauptgrund dafür,
dass Regexes heute so beliebt sind. Dieses Buch behandelt Perl 5.6, 5.8 und 5.10.
Viele Anwendungen und Regex-Bibliotheken, die behaupten, reguläre Ausdrücke im
Perl- oder einem zu Perl kompatiblen Stil zu nutzen, tun das meist gar nicht. In
Wirklichkeit nutzen sie eine Regex-Syntax, die der von Perl ähnlich ist, aber nicht
die gleichen Features unterstützt. Sehr wahrscheinlich verwenden sie eine der
Regex-Varianten aus dem Rest der Liste. Diese Varianten nutzen alle den Perl-Stil.
PCRE
PCRE ist die C-Bibliothek „Perl-Compatible Regular Expressions”, die von Philip
Hazel entwickelt wurde. Sie können diese Open Source-Bibliothek unter
http://www.pcre.org herunterladen. Dieses Buch behandelt die Versionen 4 bis 7.
Obwohl die PCRE behauptet, zu Perl kompatibel zu sein, und dies vermutlich mehr
als alle anderen Varianten in diesem Buch auch ist, setzt sie eigentlich nur den Perl-
Stil um. Manche Features, wie zum Beispiel die Unicode-Unterstützung, sind etwas
anders, und Sie können keinen Perl-Code mit Ihrer Regex mischen, wie es bei Perl
selbst möglich ist.
Aufgrund der Open Source-Lizenz und der ordentlichen Programmierung hat die
PCRE Eingang in viele Programmiersprachen und Anwendungen gefunden. Sie ist in
PHP eingebaut und in vielen Delphi-Komponenten verpackt. Wenn eine Anwendung
behauptet, reguläre Ausdrücke zu nutzen, die „zu Perl kompatibel“ sind, ohne aufzu-
führen, welche Regex-Variante genau genutzt wird, ist es sehr wahrscheinlich PCRE.
.NET
Das .NET Framework von Microsoft stellt über das Paket System.Text.Regular-
Expressions eine vollständige Regex-Variante im Perl-Stil bereit. Dieses Buch behan-
delt die .NET-Versionen 1.0 bis 3.5. Im Prinzip gibt es aber nur zwei Versionen von
System.Text.RegularExpressions: 1.0 und 2.0. In .NET 1.1, 3.0 und 3.5 gab es keine
Änderungen an den Regex-Klassen.
Jede .NET-Programmiersprache, unter anderem C#, VB.NET, Delphi for .NET und
sogar COBOL.NET, hat einen vollständigen Zugriff auf die .NET-Variante der
Regexes. Wenn eine Anwendung, die mit .NET entwickelt wurde, eine Regex-
Unterstützung anbietet, können Sie ziemlich sicher sein, dass sie die .NET-Variante
nutzt, selbst wenn sie behauptet, „reguläre Ausdrücke von Perl“ zu nutzen. Eine
wichtige Ausnahme bildet das Visual Studio (VS) selbst. Die in VS integrierte Ent-
wicklungsumgebung (IDE) nutzt immer noch die gleiche alte Regex-Variante, die sie
von Anfang an unterstützt hat. Und die ist überhaupt nicht im Perl-Stil nutzbar.
Java
Java 4 ist das erste Java-Release, das durch das Paket java.util.regex eine einge-
baute Unterstützung regulärer Ausdrücke anbietet. Schnell wurden dadurch die ver-
schiedenen Regex-Bibliotheken von dritter Seite verdrängt. Abgesehen davon, dass
es sich um ein Standardpaket handelt, bietet es auch einen vollständige Regex-Vari-
ante im Perl-Stil an und hat eine ausgezeichnete Performance, selbst im Vergleich zu

4 | Kapitel 1: Einführung in reguläre Ausdrücke


Anwendungen, die in C geschrieben wurden. Dieses Buch behandelt das Paket
java.util.regex in Java 4, 5 und 6.
Wenn Sie Software verwenden, die in den letzten paar Jahren mit Java entwickelt
wurden, wird jede Unterstützung regulärer Ausdrücke vermutlich auf die Java-Vari-
ante zurückgreifen.
JavaScript
In diesem Buch nutzen wir den Begriff JavaScript, um damit die Variante regulärer
Ausdrücke zu beschreiben, die in Version 3 des ECMA-262-Standards definiert ist.
Dieser Standard ist die Basis der Programmiersprache ECMAScript. Diese ist besser
bekannt durch ihre Implementierungen JavaScript und JScript in den verschiedenen
Webbrowsern. Internet Explorer 5.5 bis 8.0, Firefox, Opera und Safari implementie-
ren alle Version 3 von ECMA-262. Trotzdem gibt es in allen Browsern unterschiedli-
che Grenzfälle, in denen sie vom Standard abweichen. Wir weisen auf solche
Probleme hin, wenn sie eine Rolle spielen.
Ist es möglich, auf einer Website mit einem regulären Ausdruck suchen oder filtern
zu können, ohne auf eine Antwort vom Webserver warten zu müssen, wird die
Regex-Variante von JavaScript genutzt, die die einzige browserübergreifende Regex-
Variante auf Clientseite ist. Selbst VBScript von Microsoft und ActionScript 3 von
Adobe nutzen sie.
Python
Python unterstützt reguläre Ausdrücke durch sein Modul re. Dieses Buch behan-
delt Python 2.4 und 2.5. Die Unterstützung regulärer Ausdrücke in Python hat sich
seit vielen Jahren nicht geändert.
Ruby
Die Unterstützung regulärer Ausdrücke in Ruby ist wie bei Perl Teil der Sprache
selbst. Dieses Buch behandelt Ruby 1.8 und 1.9. Eine Standardkompilierung von
Ruby 1.8 verwendet die Variante regulärer Ausdrücke, die direkt im Quellcode von
Ruby implementiert ist. Eine Standardkompilierung von Ruby 1.9 nutzt die Onigu-
ruma-Bibliothek für reguläre Ausdrücke. Ruby 1.8 kann so kompiliert werden, dass
es Oniguruma verwendet, und Ruby 1.9 so, dass es die ältere Ruby-Regex-Variante
nutzt. In diesem Buch bezeichnen wir die native Ruby-Variante als Ruby 1.8 und die
Oniguruma-Variante als Ruby 1.9.
Um herauszufinden, welche Ruby-Regex-Variante Ihre Site nutzt, versuchen Sie,
den regulären Ausdruck ‹a++› zu verwenden. Ruby 1.8 wird Ihnen mitteilen, dass
der reguläre Ausdruck ungültig ist, da es keine possessiven Quantoren unterstützt,
während Ruby 1.9 damit einen String finden wird, der aus einem oder mehreren
Zeichen besteht.
Die Oniguruma-Bibliothek ist so entworfen, dass sie abwärtskompatibel zu Ruby
1.8 ist und neue Features so ergänzt, dass keine bestehenden Regexes ungültig wer-
den. Die Implementatoren haben sogar Features darin gelassen, die man wohl bes-
ser entfernt hätte, wie zum Beispiel die Verwendung von (?m), mit der der Punkt
auch Zeilenumbrüche findet, wofür andere Varianten (?s) nutzen.

Definition regulärer Ausdrücke | 5


Suchen und Ersetzen mit regulären Ausdrücken
Suchen und Ersetzen ist eine Aufgabe, für die reguläre Ausdrücke häufig eingesetzt wer-
den. Solch eine Funktion übernimmt einen Ausgangstext, einen regulären Ausdruck und
einen Ersetzungsstring als Eingabe. Die Ausgabe ist der Ausgangstext, in dem alle Über-
einstimmungen mit dem regulären Ausdruck durch den Ersetzungstext ausgetauscht
wurden.
Obwohl der Ersetzungstext kein regulärer Ausdruck ist, können Sie bestimmte Syntax-
elemente nutzen, um einen dynamischen Ersetzungstext aufzubauen. Bei allen Varianten
können Sie den Text einfügen, der durch den regulären Ausdruck gefunden wurde, oder
einen Teil davon. In den Rezepten 2.20 und 2.21 wird das beschrieben. Manche Varian-
ten unterstützen auch noch das Einfügen von passendem Kontext im Ersetzungstext, wie
in Rezept 2.22 zu lesen ist. In Kapitel 3, Rezept 3.16 erfahren Sie, wie man für jede Über-
einstimmung einen anderen Ersetzungstext erzeugen kann.

Viele Varianten des Ersetzungstexts


Unterschiedliche Ideen von unterschiedlichen Softwareentwicklern haben dazu geführt,
dass es viele verschiedene Varianten regulärer Ausdrücke gibt. Jede davon hat eine
andere Syntax und unterstützt andere Features. Bei den Ersetzungstexten ist das nicht
anders. Tatsächlich gibt es sogar noch mehr Varianten als bei den regulären Ausdrücken
selbst. Es ist schwer, eine Engine für reguläre Ausdrücke aufzubauen. Die meisten Pro-
grammierer bevorzugen es, eine bestehende zu nutzen, aber es ist recht einfach, eine
Funktion zum Suchen und Ersetzen auf einer bestehenden Regex-Engine aufzubauen. So
gibt es viele Varianten für Ersetzungstexte in Regex-Bibliotheken, die nicht von sich aus
bereits Funktionen zum Ersetzen anbieten.
Glücklicherweise haben alle Varianten regulärer Ausdrücke in diesem Buch entspre-
chende Varianten für den Ersetzungstext, mit Ausnahme der PCRE. Diese Lücke in
PCRE macht den Programmierern, die damit arbeiten, das Leben schwer. Die Open
Source-PCRE-Bibliothek enthält keinerlei Funktionen für Ersetzungen. Das heißt, alle
Anwendungen und Programmiersprachen, die auf PCRE aufbauen, müssen ihre eigenen
Funktionen bereitstellen. Die meisten Programmierer versuchen, eine bestehende Syntax
zu kopieren, aber sie machen es nie exakt gleich.
Dieses Buch behandelt die folgenden Ersetzungstextvarianten. In „Viele Varianten regu-
lärer Ausdrücke“ auf Seite 2, finden Sie weitere Informationen über die Regex-Varianten,
die zu den entsprechenden Ersetzungstextvarianten gehören:
Perl
Perl besitzt eine eingebaute Unterstützung für Ersetzungen mit regulären Ausdrü-
cken. Dazu nutzt es den Operator s/regex/replace/. Die Perl-Variante für Ersetzun-
gen gehört zu der Perl-Variante für reguläre Ausdrücke. Dieses Buch behandelt Perl
5.6 bis Perl 5.10. In der letzten Version ist eine Unterstützung für benannte Rück-
wärtsreferenzen im Ersetzungstext hinzugekommen, so wie auch bei den regulären
Ausdrücken nun benannte einfangende Gruppen enthalten sind.

6 | Kapitel 1: Einführung in reguläre Ausdrücke


PHP
In diesem Buch bezieht sich die PHP-Variante für den Ersetzungstext auf die Funk-
tion preg_replace. Diese Funktion nutzt die PCRE-Variante für die regulären Aus-
drücke und die PHP-Variante für den Ersetzungstext.
Andere Programmiersprachen, die PCRE nutzen, verwenden nicht die gleiche Erset-
zungstextvariante wie PHP. Abhängig davon, woher die Designer Ihrer Program-
miersprache ihre Inspiration beziehen, kann die Ersetzungstextsyntax der von PHP
gleichen oder eine beliebige andere Variante aus diesem Buch nutzen.
PHP bietet zudem noch die Funktion ereg_replace. Diese Funktion nutzt eine
andere Variante für reguläre Ausdrücke (POSIX ERE) und auch eine andere Vari-
ante für die Ersetzungstexte. PHPs ereg-Funktionen werden in diesem Buch nicht
behandelt.
.NET
Das Paket System.Text.RegularExpressions stellt eine ganze Reihe von Funktionen
zum Suchen und Ersetzen bereit. Die .NET-Variante für den Ersetzungstext gehört
zur .NET-Variante für die regulären Ausdrücke. Alle Versionen von .NET verwen-
den die gleiche Ersetzungstextvariante. Die neuen Features in .NET 2.0 für die regu-
lären Ausdrücke haben keinen Einfluss auf die Syntax für den Ersetzungstext.
Java
Das Paket java.util.regex enthält Funktionen zum Suchen und Ersetzen. Dieses
Buch behandelt Java 4, 5 und 6. Alle Versionen greifen auf die gleiche Ersetzungs-
textsyntax zurück.
JavaScript
In diesem Buch nutzen wir den Begriff JavaScript, um sowohl auf die Variante für
den Ersetzungstext zu verweisen als auch auf die für die regulären Ausdrücke. Bei-
des ist in Edition 3 des ECMA-262-Standards definiert.
Python
Pythons Modul re stellt eine Funktion sub bereit, mit der gesucht und ersetzt wer-
den kann. Die Python-Variante für den Ersetzungstext gehört zur Python-Variante
für reguläre Ausdrücke. Dieses Buch behandelt Python 2.4 und 2.5. Die Regex-
Unterstützung ist bei Python in den letzten Jahren sehr stabil gewesen.
Ruby
Die Unterstützung regulärer Ausdrücke in Ruby ist Bestandteil der Sprache selbst
und auch die Funktion zum Suchen und Ersetzen. Dieses Buch behandelt Ruby 1.8
und 1.9. Eine Standardkompilierung von Ruby 1.8 nutzt die Variante für reguläre
Ausdrücke, die direkt im Quellcode von Ruby definiert ist, während eine Standard-
kompilierung von Ruby 1.9 die Oniguruma-Bibliothek für reguläre Ausdrücke
nutzt. Ruby 1.8 kann so kompiliert werden, dass Oniguruma verwendet wird, wäh-
rend Ruby 1.9 so kompiliert werden kann, dass die alte Regex-Variante von Ruby
genutzt wird. In diesem Buch bezeichnen wir die native Ruby-Variante als Ruby 1.8
und die Oniguruma-Variante als Ruby 1.9.

Suchen und Ersetzen mit regulären Ausdrücken | 7


Die Syntax für Ersetzungstexte ist in Ruby 1.8 und 1.9 die gleiche, nur dass Ruby
1.9 auch noch benannte Rückwärtsreferenzen im Ersetzungstext unterstützt.
Benannte einfangende Gruppen sind ein neues Feature bei den regulären Ausdrü-
cken von Ruby 1.9.

Tools für das Arbeiten mit regulären Ausdrücken


Sofern Sie nicht schon längere Zeit mit regulären Ausdrücken gearbeitet haben, empfeh-
len wir Ihnen, Ihre ersten Experimente in einem Tool durchzuführen und nicht in Quell-
code. Die Beispiel-Regexes in diesem Kapitel und in Kapitel 2, sind einfache reguläre
Ausdrücke, die keine zusätzliche Maskierung in einer Programmiersprache (oder sogar in
einer Unix-Shell) brauchen. Sie können diese regulären Ausdrücke direkt in das Suchfeld
einer Anwendung eingeben.
Kapitel 3 erklärt, wie Sie reguläre Ausdrücke in Ihren Quellcode einbauen können. Will
man einen regulären Ausdruck als String mit Anführungszeichen versehen, wird er noch
schwerer lesbar, weil sich dann die Maskierungsregeln für Strings mit denen für reguläre
Ausdrücke vermischen. Wir heben uns das für Rezept 3.1, auf. Haben Sie einmal die
Grundlagen regulärer Ausdrücke verstanden, werden Sie auch durch den Backslash-
Dschungel finden.
Die in diesem Abschnitt beschriebenen Tools ermöglichen es Ihnen auch, reguläre Aus-
drücke zu debuggen, die Syntax zu prüfen und andere Informationen zu bekommen, die
in den meisten Programmierumgebungen nicht erhältlich sind. Wenn Sie also reguläre
Ausdrücke in Ihren Anwendungen entwickeln, ist es für Sie vielleicht sinnvoll, einen
komplizierten regulären Ausdruck in einem dieser Tools aufzubauen, bevor Sie ihn in
Ihrem Programm einsetzen.

RegexBuddy
RegexBuddy (Abbildung 1-1) ist das Tool mit den (zum Zeitpunkt der Entstehung dieses
Buchs) meisten verfügbaren Features zum Erstellen, Testen und Implementieren regulä-
rer Ausdrücke. Es bietet die einzigartige Möglichkeit, alle Varianten regulärer Ausdrücke
zu emulieren, die in diesem Buch behandelt werden, und sogar zwischen den verschiede-
nen Varianten zu konvertieren.
RegexBuddy wurde von Jan Goyvaerts entworfen und entwickelt, einem der Autoren die-
ses Buchs. Dadurch wurde Jan zu einem Experten für reguläre Ausdrücke und mithilfe
von RegexBuddy war Koautor Steven in der Lage, sich so gut mit regulären Ausdrücken
vertraut zu machen, dass er sich an diesem Buch beteiligen konnte.
Wenn der Screenshot (Abbildung 1-1) ein bisschen unruhig aussieht, liegt das daran, dass
wir einen Großteil der Panels auf den Bildschirm gebracht haben, um zu zeigen, was Regex-
Buddy alles drauf hat. Die Standardansicht ordnet alle Panels hübsch in Registerkarten an.
Sie können sie aber auch abreißen, um sie auf einen zweiten Monitor zu ziehen.

8 | Kapitel 1: Einführung in reguläre Ausdrücke


Abbildung 1-1: RegexBuddy

Um einen der regulären Ausdrücke aus diesem Buch auszuprobieren, tippen Sie ihn ein-
fach in das Eingabefeld im oberen Bereich des RegexBuddy-Fensters ein. RegexBuddy
stellt den Ausdruck dann automatisch mit Syntax-Highlighting dar und macht deutlich
auf Fehler und fehlende Klammern aufmerksam.
Das Create-Panel baut automatisch eine detaillierte Analyse in englischer Sprache auf,
während Sie die Regex eintippen. Durch einen Doppelklick auf eine Beschreibung im
Baum mit der Struktur des regulären Ausdrucks können Sie diesen Teil des regulären
Ausdrucks bearbeiten. Neue Teile lassen sich in Ihren regulären Ausdruck per Hand oder
über den Button Insert Token einfügen. Bei Letzterem können Sie dann aus einem Menü
auswählen, was Sie haben wollen. Wenn Sie sich zum Beispiel nicht an die komplizierte
Syntax für einen positiven Lookahead erinnern, können Sie RegexBuddy bitten, die pas-
senden Zeichen für Sie einzufügen.
Geben Sie einen Beispieltext im Test-Panel ein – indem Sie ihn entweder eintippen oder
hineinkopieren. Wenn der Highlight-Button aktiv ist, markiert RegexBuddy automatisch
den Text, der zur Regex passt.
Einige dieser Buttons werden Sie wahrscheinlich am häufigsten nutzen:
List All
Gibt eine Liste mit allen Übereinstimmungen aus.

Tools für das Arbeiten mit regulären Ausdrücken | 9


Replace
Der Replace-Button am oberen Fensterrand zeigt ein neues Fenster an, in dem Sie
den Ersetzungstext eingeben können. Der Replace-Button im Test-Panel zeigt Ihnen
dann den Ausgangstext an, nachdem die Ersetzungen vorgenommen wurden.
Split (der Button im Test-Panel, nicht der am oberen Fensterrand)
Behandelt den regulären Ausdruck als Separator und teilt den Ausgangstext in
Tokens auf, die zeigen, wo in Ihrem Text Übereinstimmungen gefunden wurden.
Klicken Sie auf einen dieser Buttons und wählen Sie Update Automatically, damit Regex-
Buddy die Ergebnisse dynamisch aktualisiert, wenn Sie Ihre Regex oder den Ausgangs-
text anpassen.
Um genau zu sehen, wie Ihre Regex funktioniert (oder warum nicht), klicken Sie im Test-
Panel auf eine hervorgehobene Übereinstimmung oder auf eine Stelle, an der die Regex
nicht funktioniert hat, und danach auf den Button Debug. RegexBuddy wechselt dadurch
zum Debug-Panel und zeigt den gesamten Prozess zum Finden von Übereinstimmungen
Schritt für Schritt. Klicken Sie in die Ausgabe des Debuggers, um herauszufinden, wel-
ches Regex-Token zu dem Text passte, den Sie angeklickt haben.
Im Use-Panel wählen Sie Ihre bevorzugte Programmiersprache aus. Dann wählen Sie eine
Funktion, um den Quellcode für das Implementieren Ihrer Regex zu erzeugen. Die Quell-
code-Templates von Regex-Buddy lassen sich mit dem eingebauten Template-Editor
problemlos bearbeiten. Sie können neue Funktionen und sogar neue Sprachen ergänzen
oder bestehende anpassen.
Möchten Sie Ihre Regex mit einer größeren Datenmenge testen, wechseln Sie zum GREP-
Panel, um eine beliebige Anzahl von Dateien und Ordnern zu durchsuchen (und eventu-
ell Ersetzungen vorzunehmen).
Wenn Sie in Code, den Sie warten, eine Regex finden, kopieren Sie sie in die Zwischenab-
lage – einschließlich der umschließenden Anführungszeichen oder Schrägstriche. In
RegexBuddy klicken Sie auf den Paste-Button und wählen den String-Stil Ihrer Program-
miersprache aus. Ihre Regex wird dann in RegexBuddy als pure Regex erscheinen – ohne
die zusätzlichen Anführungszeichen oder Maskierungen, die für String-Literale notwen-
dig sind. Mit dem Copy-Button erzeugen Sie einen String in der gewünschten Syntax,
sodass Sie ihn in Ihren Quellcode einfügen können.
Sobald Sie mehr Erfahrung haben, können Sie im Library-Panel eine praktische Biblio-
thek mit regulären Ausdrücke aufbauen. Ergänzen Sie die dort abgelegten Regexes auf
jeden Fall um eine detaillierte Beschreibung und einen Test-String. Reguläre Ausdrücke
können sehr kryptisch sein, selbst für Experten.
Haben Sie dennoch ein Problem damit, eine passende Regex aufzubauen, klicken Sie auf
das Forum-Panel und dann auf den Login-Button. Falls Sie RegexBuddy gekauft haben,
erscheint das Anmeldefenster. Klicken Sie auf OK, sind Sie direkt mit dem Benutzer-
forum von RegexBuddy verbunden. Steven und Jan sind dort häufig zu finden.

10 | Kapitel 1: Einführung in reguläre Ausdrücke


RegexBuddy läuft unter Windows 98, ME, 2000, XP und Vista. Falls Sie eher Linux oder
Apple bevorzugen, lässt sich RegexBuddy auch gut mit VMware, Parallels, CrossOver
Office und (mit ein paar Einschränkungen) auch mit WINE betreiben. Sie können eine
kostenlose Testversion von RegexBuddy unter http://www.regexbuddy.com/RegexBuddy-
Cookbook.exe herunterladen. Abgesehen vom Benutzerforum ist die Testversion sieben
Tage lang uneingeschränkt nutzbar.

RegexPal
RegexPal (Abbildung 1-2) ist ein Online-Testtool für reguläre Ausdrücke. Es wurde von
Steven Levithan erstellt, einem der Autoren dieses Buchs. Sie brauchen dazu lediglich
einen modernen Webbrowser. RegexPal ist vollständig in JavaScript geschrieben. Daher
unterstützt es nur die JavaScript-Variante für reguläre Ausdrücke, so wie sie in dem von
Ihnen verwendeten Webbrowser implementiert ist.

Abbildung 1-2: RegexPal

Um einen der regulären Ausdrücke auszuprobieren, die in diesem Buch vorgestellt wer-
den, rufen Sie http://www.regexpal.com auf. Geben Sie die Regex in das Feld ein, in dem
Enter regex here. steht. RegexPal zeigt Ihren regulären Ausdruck automatisch mit Syntax-
Highlighting an, wodurch Sie auch Syntaxfehler in der Regex erkennen können. Regex-
Pal ist sich der Probleme unterschiedlicher Implementierungen in den verschiedenen

Tools für das Arbeiten mit regulären Ausdrücken | 11


Browsern bewusst, die Ihnen das Leben ganz schön schwer machen können. Wenn daher
eine bestimmte Syntax in manchen Browsern nicht korrekt arbeitet, wird RegexPal diese
als Fehler hervorheben.
Jetzt geben Sie einen Beispieltext in das Feld mit dem Inhalt Enter test data here. ein.
RegexPal hebt automatisch die Übereinstimmungen zu Ihrer Regex hervor.
Es gibt keine Buttons, die man anklicken muss – das macht RegexPal zu einem der ange-
nehmsten Onlinetools für das Testen regulärer Ausdrücke.

Weitere Onlinetools für Regexes


Es ist einfach, ein simples Onlinetool für das Testen regulärer Ausdrücke aufzubauen.
Wenn Sie ein paar grundlegende Web-Entwicklungskenntnisse besitzen, reichen die
Informationen aus Kapitel 3 aus, um selbst etwas zu bauen. Hunderte von Entwicklern
haben das schon getan, ein paar haben Features ergänzt, die erwähnenswert sind.

regex.larsolavtorvik.com
Lars Olav Torvik hat ein tolles kleines Testtool für reguläre Ausdrücke unter
http://regex.larsolavtorvik.com bereitgestellt (siehe Abbildung 1-3).
Zunächst wählen Sie die Variante aus, mit der Sie arbeiten wollen, indem Sie im oberen
Bereich der Seite auf deren Namen klicken. Lars bietet PHP PCRE, PHP POSIX und Java-
Script an. PHP PCRE, die PCRE-Variante, die wir in diesem Buch behandeln, wird von
der PHP-Funktion preg genutzt. POSIX ist eine alte und recht eingeschränkte Regex-
Variante, die von der PHP-Funktion ereg verwendet wird, aber in diesem Buch keine wei-
tere Erwähnung findet. Wenn Sie JavaScript auswählen, arbeiten Sie mit der JavaScript-
Implementierung Ihres Browsers.
Geben Sie Ihren regulären Ausdruck in das Pattern-Feld und Ihren Ausgangstext in das
Subject-Feld ein. Einen Moment später wird im Matches-Feld Ihr Ausgangstext mit den
hervorgehobenen Übereinstimmungen angezeigt. Das Code-Feld enthält eine einzelne
Codezeile, die Ihre Regex auf Ihren Ausgangstext anwendet. Kopieren Sie diese Zeile in
Ihren Codeeditor, brauchen Sie die Regexp nicht selbst in ein String-Literal umzuwan-
deln. Strings oder Arrays, die vom Code zurückgegeben werden, finden Sie im Result-
Feld. Da Lars Ajax-Technologie verwendet hat, um seine Site aufzubauen, sind die
Ergebnisse für alle Varianten immer nach kurzer Zeit verfügbar. Um dieses Tool nutzen
zu können, müssen Sie online sein, da auf dem Server PHP verarbeitet wird.
Die zweite Spalte zeigt eine Liste mit Regex-Befehlen und -Optionen an. Diese hängen
davon ab, welche Variante Sie gewählt haben. Zu den Befehlen gehören üblicherweise
solche zum Finden von Übereinstimmungen (match), zum Ersetzen von Text (replace)
und zum Aufteilen von Texten (split). Die Optionen enthalten die üblichen Verdächti-
gen, wie zum Beispiel das Ignorieren von Groß- und Kleinschreibung, aber auch imple-
mentierungsspezifische Optionen. Diese Befehle und Optionen werden in Kapitel 3,
beschrieben.

12 | Kapitel 1: Einführung in reguläre Ausdrücke


Abbildung 1-3: regex.larsolavtorvik.com

Nregex
http://www.nregex.com (Abbildung 1-4) ist ein unkompliziertes Online-Testtool für
Regexes, das von David Seruyange mit .NET-Technologie gebaut wurde. Die Site
erwähnt zwar nicht, welche Variante sie nutzt, aber als dieses Buch hier geschrieben
wurde, war es .NET 1.x.
Das Layout der Seite ist ein bisschen verwirrend. Sie geben Ihren regulären Ausdruck in
das Feld unter dem Text Regular Expression ein und setzen die Regex-Optionen mithilfe
der Kontrollkästchen darunter. Geben Sie Ihren Ausgangstext in das große Feld am unte-
ren Ende der Seite ein, wobei Sie den Standardtext If I just had $5.00 then "she"
wouldn't be so @#$! mad. ersetzen. Wenn Ihr Text von einer Webseite kommt, geben Sie
die URL in das Feld Load Target From URL ein und klicken auf den Button Load, der
sich darunter befindet. Möchten Sie eine Datei auf Ihrer Festplatte als Ausgangsbasis
nehmen, klicken Sie auf den Button Browse, wählen die gewünschte Datei aus und kli-
cken dann auf den Button Load unter dem entsprechenden Eingabefeld.

Tools für das Arbeiten mit regulären Ausdrücken | 13


Abbildung 1-4: Nregex

Ihr Ausgangstext wird im Feld Matches & Replacements in der Mitte der Webseite noch-
mals erscheinen, wobei die Übereinstimmungen zur Regex hervorgehoben sind. Wenn
Sie etwas in das Feld Replacement String eingeben, wird stattdessen das Ergebnis des
Ersetzungsvorgangs angezeigt. Wenn Ihr regulärer Ausdruck ungültig ist, erscheint: ...
Das Auswerten der Regex wird mit .NET-Code auf dem Server durchgeführt, daher müs-
sen Sie für diese Site online sein. Arbeiten die automatischen Aktualisierungen langsam –
vielleicht weil Ihr Ausgangstext sehr groß ist –, markieren Sie das Kontrollkästchen
Manually Evaluate Regex über dem Eingabefeld für Ihren regulären Ausdruck, um einen
Evaluate-Button zu erhalten. Diesen können Sie dann anklicken, um das Feld Matches &
Replacements zu aktualisieren.

14 | Kapitel 1: Einführung in reguläre Ausdrücke


Rubular
Michael Lovitt hat ein minimalistisches Regex-Testtool unter http://www.rubular.com
(Abbildung 1-5) bereitgestellt, wobei die Regex-Variante von Ruby 1.8 genutzt wird.

Abbildung 1-5: Rubular

Geben Sie Ihren regulären Ausdruck im Feld Your regular expression zwischen den bei-
den Schrägstrichen ein. Sie können Groß- und Kleinschreibung ignorieren, wenn Sie in
das kleine Feld nach dem zweiten Schrägstrich ein i tippen. Genauso können Sie die
Option Punkt findet auch Zeilenumbruch durch die Eingabe eines m im gleichen Feld akti-
vieren. im schaltet beide Optionen ein. Auch wenn diese Konventionen ein wenig
unpraktisch zu sein scheinen, wenn Sie mit Ruby noch nicht so vertraut sind, entspre-
chen Sie dennoch der Syntax /regex/im, die in Ruby genutzt wird, um einen regulären
Ausdruck anzugeben.
Tragen Sie Ihren Ausgangstext in das Feld Your test string ein und warten Sie einen
Moment. Es wird ein neues Feld Match result auf der rechten Seite erscheinen, in dem Ihr
Ausgangstext mit allen Übereinstimmungen hervorgehoben zu finden ist.

myregexp.com
Sergey Evdokimov hat eine ganze Reihe von Regex-Testtools für Java-Entwickler
geschrieben. Die Homepage unter http://www.myregexp.com (Abbildung 1-6) bietet auch
eine Onlineversion an. Dabei handelt es sich um ein Java-Applet, das in Ihrem Browser

Tools für das Arbeiten mit regulären Ausdrücken | 15


läuft. Dafür muss die Java 4-Runtime (oder eine neuere) auf Ihrem Computer installiert
sein. Das Applet nutzt das Paket java.util.regex (das seit Java 4 existiert), um Ihren
regulären Ausdruck auszuwerten. In diesem Buch bezieht sich die Java-Variante auf die-
ses Paket.

Abbildung 1-6: myregexp.com

Geben Sie Ihren regulären Ausdruck in das Feld Regular Expression ein. Mit dem Menü
Flags können Sie die gewünschten Regex-Optionen setzen. Drei der Optionen sind auch
als Kontrollkästchen direkt auf der Oberfläche zu finden.
Wenn Sie eine Regex testen wollen, die schon als String in Java-Code vorhanden ist,
kopieren Sie den gesamten String in die Zwischenablage. Im myregexp.com-Tester wäh-
len Sie im Edit-Menü den Punkt Paste Regex from Java String aus. Im gleichen Menü kön-
nen Sie auch Copy Regex for Java Source aufrufen, wenn Sie mit der Bearbeitung des
regulären Ausdrucks fertig sind. In diesem Menü gibt es außerdem ähnliche Einträge für
JavaScript und XML.

16 | Kapitel 1: Einführung in reguläre Ausdrücke


Unterhalb des regulären Ausdrucks gibt es vier Registerkarten, auf denen vier verschie-
dene Tests ausgeführt werden können:
Find
Alle Übereinstimmungen zum regulären Ausdruck werden im Ausgangstext hervor-
gehoben. Dies sind die von der Java-Methode Matcher.find() gefundenen Elemente.
Match
Prüft, ob der reguläre Ausdruck mit dem Ausgangstext vollständig übereinstimmt.
Wenn das der Fall ist, wird der gesamte Text hervorgehoben. Die Methoden
String.matches() und Matcher.matches() gehen so vor.
Split
Das zweite Feld auf der rechten Seite zeigt das Array mit Strings an, das von
String.split() oder Pattern.split() zurückgegeben wird.
Replace
Geben Sie einen Ersetzungstext ein, zeigt das Feld auf der rechten Seite den von
String.replaceAll() oder Matcher.replaceAll() zurückgegebenen Text an.
Sie finden die anderen Regex-Tools von Sergey über die Links oben auf der Seite
http://www.myregexp.com. Eines ist ein Plug-in für Eclipse, das andere eines für IntelliJ
IDEA.

reAnimator
Oliver Steeles reAnimator unter http://osteele.com/tools/reanimator (Abbildung 1-7)
bringt keine tote Regex ins Leben zurück. Aber es ist ein nettes kleines Tool, das eine gra-
fische Darstellung des endlichen Automaten ausgibt, den eine Regex-Engine nutzt, um
einen regulären Ausdruck umzusetzen.
Die Syntax des reAnimator ist sehr eingeschränkt. Es lassen sich alle in diesem Buch
behandelten Varianten nutzen. Jede Regex, die Sie im reAnimator darstellen können,
wird in allen Varianten funktionieren, die in diesem Buch beschrieben sind, aber das
Gegenteil ist definitiv nicht der Fall. Das liegt daran, dass die regulären Ausdrücke von
reAnimator im mathematischen Sinn regulär sind. Der Kasten „Geschichte des Begriffs
,regulärer Ausdruck’“ auf Seite 2 erklärt das kurz.
Gehen Sie zum Pattern-Feld im oberen Bereich der Seite und klicken Sie auf den Edit-But-
ton. Geben Sie nun Ihren regulären Ausdruck ein und klicken Sie auf Set. Tippen Sie
langsam den Ausgangstext in das Input-Feld ein.
Beim Eintippen jedes einzelnen Zeichens bewegen sich bunte Bälle durch den Automa-
ten, um zu zeigen, wo sich der Endpunkt aufgrund Ihrer bisherigen Eingabe befindet.
Blaue Bälle stehen für eine vom Automaten akzeptierte Eingabe, die aber noch mehr
benötigt, um eine vollständige Übereinstimmung zu erreichen. Grüne Bälle zeigen, dass
der endliche Automat das ganze Muster abbilden kann. Wenn keine Bälle zu sehen sind,
kann der Automat mit der Eingabe nichts anfangen.

Tools für das Arbeiten mit regulären Ausdrücken | 17


Abbildung 1-7: reAnimator

reAnimator zeigt Ihnen nur dann eine Übereinstimmung, wenn der reguläre Ausdruck
zum ganzen Eingabetext passt – so also ob Sie ihn zwischen die Anker ‹^› und ‹$› gesetzt
hätten. Das ist eine weitere Eigenschaft von Ausdrücken, die im mathematischen Sinn
regulär sind.

Weitere Desktop-Tools für das Arbeiten mit regulären Ausdrücken


Expresso
Expresso (nicht zu verwechseln mit dem koffeinhaltigen Espresso) ist eine .NET-Anwen-
dung, mit der reguläre Ausdrücke erstellt und getestet werden können. Das Programm
lässt sich unter http://www.ultrapico.com/Expresso.htm herunterladen. Um es zu nutzen,
muss das .NET Framework 2.0 auf Ihrem Computer installiert sein.
Beim Download handelt es sich um eine Testversion, die 60 Tage kostenlos nutzbar ist.
Danach müssen Sie das Programm registrieren, da ansonsten ein Großteil der Funktiona-
lität nicht mehr nutzbar ist. Das Registrieren ist kostenfrei, aber Sie müssen den Jungs
von Ultrapico Ihre E-Mail-Adresse mitteilen. Der Registrierungsschlüssel wird per E-Mail
verschickt.

18 | Kapitel 1: Einführung in reguläre Ausdrücke


Expresso präsentiert sich, wie in Abbildung 1-8 gezeigt. Der Bereich Regular Expression,
in den Sie Ihren regulären Ausdruck eingeben, ist immer sichtbar. Es gibt kein Syntax-
Highlighting. Im Bereich Regex Analyzer wird automatisch eine kurze Analyse Ihres regu-
lären Ausdrucks in englischer Sprache aufgebaut. Auch er ist stets sichtbar.

Abbildung 1-8: Expresso

Im Designmodus können Sie am unteren Ende des Fensters die Regex-Optionen setzen,
zum Beispiel Ignore Case. Den meisten Platz auf dem Bildschirm nimmt eine Reihe von
Registerkarten ein, auf denen Sie das Regex-Token auswählen können, das Sie einfügen
wollen. Wenn Sie zwei Monitore nutzen oder auch einen großen, klicken Sie auf den
Undock-Button, um die Registerkarten zu „lösen“. Dann können Sie Ihren regulären Aus-
druck auch im anderen Modus (Testmodus) erstellen.
Im Testmodus geben Sie Ihren Ausgangstext in der linken unteren Ecke ein. Dann kli-
cken Sie auf den Button Run Match, um eine Liste aller Übereinstimmungen im Bereich
Search Results zu erhalten. Es gibt keine Hervorhebungen des Ausgangstexts. Klicken Sie
auf eine Übereinstimmung in den Ergebnissen, um die entsprechende Stelle im Aus-
gangstext anzuwählen.

Tools für das Arbeiten mit regulären Ausdrücken | 19


Die Expression Library enthält eine Liste mit Beispiel-Regexes und eine mit den zuletzt
verwendeten. Ihre Regex wird dieser Liste immer dann hinzugefügt, wenn Sie Run Match
anklicken. Sie können die Bibliothek über das Menü Library im Hauptmenü anpassen.

The Regulator
The Regulator kann von http://sourceforge.net/projects/regulator heruntergeladen wer-
den. Dabei handelt es sich um eine weitere .NET-Anwendung für das Erstellen und Tes-
ten regulärer Ausdrücke. Die neueste Version erfordert .NET 2.0 oder neuer. Ältere
Versionen für .NET 1.x können immer noch heruntergeladen werden. The Regulator ist
Open Source, und man muss weder etwas bezahlen, noch muss man sich registrieren.
Im Regulator passiert alles in einem Fenster (Abbildung 1-9). Auf der Registerkarte New
Document geben Sie Ihren regulären Ausdruck ein. Es gibt ein automatisches Syntax-
Highlighting, aber Syntaxfehler in Ihrer Regex werden nicht hervorgehoben. Per
Rechtsklick wählen Sie das Regex-Token aus, das Sie aus einem Menü einfügen wollen.
Sie können die Optionen für reguläre Ausdrücke über die Buttons in der Toolbar setzen.
Die Icons sind ein wenig kryptisch. Warten Sie auf den Tooltipp, um zu verstehen, wel-
che Einstellung Sie mit welchem Button vornehmen.

Abbildung 1-9: The Regulator

Rechts unterhalb des Bereichs für Ihre Regex klicken Sie auf den Input-Button, um den
Bereich anzuzeigen, in dem Ihr Ausgangstext eingegeben werden kann. Klicken Sie auf
den Button Replace with, um den Ersetzungstext einzugeben, wenn Sie suchen und erset-
zen wollen. Links unterhalb der Regex können Sie die Ergebnisse Ihrer Regex-Operation
sehen. Diese werden nicht automatisch aktualisiert, Sie müssen stattdessen auf einen der
Buttons Match, Replace oder Split in der Toolbar klicken. Es gibt für die Eingabe kein
Highlighting. Klicken Sie auf eine Übereinstimmung in den Ergebnissen, um sich den
entsprechenden Teil im Ausgangstext anzeigen zu lassen.

20 | Kapitel 1: Einführung in reguläre Ausdrücke


Das Panel Regex Analyzer zeigt eine einfache Analyse Ihres regulären Ausdrucks in engli-
scher Sprache. Diese wird allerdings nicht automatisch erstellt und ist auch nicht interak-
tiv. Um die Analyse zu aktualisieren, wählen Sie im Menü View den Punkt Regex
Analyzer, auch wenn sie schon sichtbar ist. Klicken Sie dagegen auf die Analyse, wird nur
der Textcursor bewegt.

grep
Der Name grep leitet sich vom Befehl g/re/p ab, der im Unix-Texteditor ed eine Suche
mithilfe eines regulären Ausdrucks durchführt. Dieser Befehl wurde so häufig genutzt,
dass mittlerweile alle Unix-Systeme ein eigenes grep-Tool haben, um Dateien mit regulä-
ren Ausdrücken zu durchsuchen. Wenn Sie Unix, Linux oder OS X nutzen, geben Sie in
einem Terminal-Fenster man grep ein, um alles darüber zu lernen.
Die folgenden drei Tools sind Windows-Anwendungen, die das tun, was auch grep tut,
und sogar mehr.

PowerGREP
PowerGREP wurde von Jan Goyvaerts entwickelt, einem der Autoren dieses Buchs, und
ist vermutlich das umfassendste grep-Tool, das für Microsoft Windows verfügbar ist
(Abbildung 1-10). PowerGREP nutzt eine eigene Regex-Variante, die das Beste aller in
diesem Buch besprochenen Varianten kombiniert. Diese Variante wird in RegexBuddy
als „JGsoft“ bezeichnet.
Um schnell eine Suche mit regulären Ausdrücken durchzuführen, klicken Sie im Action-
Menü einfach auf Clear und geben Ihren regulären Ausdruck in das Suchfeld des Action-
Panels ein. Klicken Sie dann auf einen Ordner im Panel File Selector und wählen Sie im
Menü File Selector entweder Include File or Folder oder Include Folder and Subfolders.
Dann wählen Sie im Action-Menü Execute aus, um Ihre Suche zu starten.
Um Suchergebnisse auch zu ersetzen, wählen Sie in der linken oberen Ecke des Action-
Panels in der Auswahlliste Action Type den Eintrag search-and-replace aus. Es erscheint
dann unterhalb des Suchfelds ein Replace-Feld. Geben Sie hier Ihren Ersetzungstext ein.
Alle anderen Schritte laufen genau so ab wie beim reinen Suchen.
PowerGREP besitzt die einmalige Fähigkeit, bis zu drei Listen mit regulären Ausdrücken
gleichzeitig nutzen zu können, wobei jede Liste eine beliebige Anzahl von Regexes ent-
halten kann. Während die beiden vorigen Absätze beschrieben haben, wie Sie einfache
Suchen durchführen können, die auch jedes andere grep-Tool ermöglicht, muss man
schon ein bisschen in der umfangreichen Dokumentation zu PowerGREP lesen, um alle
Möglichkeiten ausschöpfen zu können.

Tools für das Arbeiten mit regulären Ausdrücken | 21


Abbildung 1-10: PowerGREP

PowerGREP läuft unter Windows 98, ME, 2000, XP und Vista. Sie können eine kosten-
lose Testversion über http://www.powergrep.com/PowerGREPCookbook.exe herunterla-
den. Abgesehen von der Möglichkeit, Ergebnisse und Bibliotheken speichern zu können,
ist die Testversion für 15 Tage vollständig nutzbar. In dieser Zeit lassen sich zwar keine
Ergebnisse aus dem Results-Panel abspeichern, aber Suchen-und-Ersetzen-Vorgänge
ändern trotzdem Ihre Dateien, so wie es die vollständige Version auch tut.

Windows Grep
Windows Grep (http://www.wingrep.com) ist eines der ältesten grep-Tools für Windows.
Seine Oberfläche ist zwar schon etwas angestaubt (Abbildung 1-11), aber es erledigt das,
was es soll, ohne Probleme. Dabei wird eine eingeschränkte Regex-Variante namens
POSIX ERE unterstützt. Bei den unterstützten Features wird die gleiche Syntax genutzt
wie bei den Varianten in diesem Buch. Windows Grep ist Shareware, Sie können es also
kostenlos herunterladen, aber es wird erwartet, dass Sie es bezahlen, wenn Sie längerfris-
tig damit arbeiten wollen.
Um mit einer Suche zu beginnen, wählen Sie im Search-Menü den Eintrag Search. Das
sich daraufhin öffnende Fenster sieht je nach gewähltem Modus unterschiedlich aus – es
gibt im Options-Menü einen Beginner Mode und einen Expert Mode. Anfänger erhalten

22 | Kapitel 1: Einführung in reguläre Ausdrücke


Abbildung 1-11: Windows Grep

einen Wizard, der sie durch die einzelnen Schritte führt, während Experten einen Dialog
mit Registerkarten vorfinden.
Wenn Sie eine Suche eingerichtet haben, führt Windows Grep sie sofort aus und zeigt
Ihnen eine Liste von Dateien an, in denen Treffer gefunden wurden. Klicken Sie auf eine
der Dateien, um die Übereinstimmungen zu sehen. Per Doppelklick öffnen Sie die Datei.
Mit All Matches im View-Menü zeigt das untere Panel alles an.
Um ein Suchen und Ersetzen durchzuführen, wählen Sie im Search-Menü den Eintrag
Replace.

RegexRenamer
RegexRenamer (Abbildung 1-12) ist eigentlich kein grep-Tool. Statt den Inhalt von
Dateien zu durchsuchen, sucht und ersetzt es in Dateinamen. Sie können das Programm
auf http://regexrenamer.sourceforge.net herunterladen. RegexRenamer benötigt das .NET
Framework von Microsoft in der Version 2.0.

Tools für das Arbeiten mit regulären Ausdrücken | 23


Abbildung 1-12: RegexRenamer

Geben Sie Ihren regulären Ausdruck in das Feld Match ein, den Ersetzungstext ins Feld
Replace. Klicken Sie auf /i, um Groß- und Kleinschreibung zu ignorieren, oder auf /g,
um alle Übereinstimmungen in einem Dateinamen zu ersetzen statt nur die erste. Mit /x
wird zur Freiformsyntax gewechselt, was hier nicht sehr sinnvoll ist, da Sie nur eine Zeile
haben, um Ihren regulären Ausdruck einzugeben.
Nutzen Sie den Baum auf der linken Seite, um den Ordner auszuwählen, der die umzube-
nennenden Dateien enthält. Sie können in der rechten oberen Ecke eine Dateimaske oder
einen Regex-Filter definieren. Damit wird die Liste der Dateien, auf die Ihre Regex zum
Suchen und Ersetzen angewandt werden soll, eingeschränkt. Es ist viel praktischer, eine
Regex zum Filtern und eine zum Ersetzen zu nutzen, statt beides mit einer einzigen Regex
abhandeln zu wollen.

Beliebte Texteditoren
Die meisten modernen Texteditoren bieten zumindest eine grundlegende Unterstützung
für reguläre Ausdrücke an. Beim Suchen oder beim Ersetzen finden Sie im Allgemeinen
eine Checkbox, mit der reguläre Ausdrücke genutzt werden können. Manche Editoren,
wie zum Beispiel EditPad Pro, nutzen zudem reguläre Ausdrücke für verschiedenste
Features bei der Textbearbeitung, wie zum Beispiel beim Syntax-Highlighting oder zum
Erstellen von Klassen- und Funktionslisten. Die Dokumentation jedes Editors beschreibt
alle diese Features. Einige der beliebtesten Texteditoren mit einer Unterstützung regulä-
rer Ausdrücke sind:

24 | Kapitel 1: Einführung in reguläre Ausdrücke


• Boxer Text Editor (PCRE)
• Dreamweaver (JavaScript)
• EditPad Pro (eigene Variante, die das Beste der Varianten aus diesem Buch kombi-
niert, in RegexBuddy als „JGsoft“ bezeichnet)
• Multi-Edit (PCRE, wenn Sie die Option Perl auswählen)
• NoteTab (PCRE)
• UltraEdit (PCRE)
• TextMate (Ruby 1.9 [Oniguruma])

Tools für das Arbeiten mit regulären Ausdrücken | 25


KAPITEL 2
Grundlagen regulärer Ausdrücke

Die in diesem Kapitel vorgestellten Probleme sind keine echten Probleme, die Ihr Chef
oder Ihr Kunde von Ihnen gelöst haben will. Stattdessen sind es technische Probleme,
denen Sie sich vielleicht gegenübersehen, wenn Sie reguläre Ausdrücke erstellen oder
bearbeiten, mit denen die eigentlichen Probleme gelöst werden sollen. Das erste Rezept
erklärt zum Beispiel, wie man literalen Text mit einem regulären Ausdruck finden kann.
Das ist normalerweise nicht das eigentliche Ziel, da Sie keinen regulären Ausdruck brau-
chen, wenn Sie nur nach literalem Text suchen wollen. Aber wenn Sie eine Regex erstel-
len, werden Sie auch literalen Text finden wollen, daher müssen Sie wissen, welche
Zeichen zu maskieren sind. Rezept 2.1 beschreibt Ihnen, wie das geht.
Die Rezepte beginnen mit sehr einfachen Techniken für reguläre Ausdrücke. Wenn
bereits Sie Regexes verwendet haben, reicht es wahrscheinlich, sie nur zu überfliegen,
oder Sie überspringen sie sogar. Die Rezepte weiter unten in diesem Kapitel werden
Ihnen dann aber sicherlich doch etwas Neues vermitteln, sofern Sie nicht schon Reguläre
Ausdrücke von Jeffrey E. F. Friedl (O’Reilly) von vorne bis hinten durchgelesen haben.
Wir haben die Rezepte in diesem Kapitel so zusammengestellt, dass jedes einen bestimm-
ten Aspekt der Syntax regulärer Ausdrücke erklärt. Lesen Sie sie von Anfang bis Ende,
um sich ein solides Wissen über regulären Ausdrücke anzueignen. Oder Sie schauen sich
direkt die reguläre Ausdrücke aus der realen Welt an, die Sie in den Kapiteln 4 bis 8 fin-
den, um dann dort den Verweisen auf dieses Kapitel zu folgen, wenn Sie sich einer Ihnen
unbekannten Syntax gegenübersehen.
Dieses Tutorium-Kapitel kümmert sich nur um reguläre Ausdrücke und ignoriert voll-
ständig sämtliche Überlegungen zur Programmierung. Das nächste Kapitel ist das mit
den ganzen Codebeispielen. Sie können schon mal einen Blick auf „Programmierspra-
chen und Regex-Varianten“ in Kapitel 3 werfen, um herauszufinden, welche Variante
regulärer Ausdrücke Ihre Programmiersprache nutzt. Die Varianten, die in diesem Kapi-
tel behandelt werden, wurden in „Regex-Varianten in diesem Buch“ auf Seite 3, vorge-
stellt.

| 27
2.1 Literalen Text finden
Problem
Erstellen eines regulären Ausdrucks, der genau auf den so hübsch ausgedachten folgen-
den Satz passt: Die Sonderzeichen in der ASCII-Tabelle sind: !"#$%&'()*+,-./:;<=>?@
[\]^_`{|}~.

Lösung
DiezSonderzeichenzinzderzASCII-Tabellezsind:z
!"#\$%&'\(\)\*\+,-\./:;‹=>\?@\[\\]\^_`\{\|}~
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Jeder reguläre Ausdruck, der keines der zwölf Zeichen $()*+.?[\^{| enthält, findet ein-
fach sich selbst. Um herauszufinden, ob sich in dem Text, den Sie gerade bearbeiten, Ein
Loch ist im Eimer findet, suchen Sie einfach nach ‹EinzLochzistzimzEimer›. Es ist dabei
egal, ob das Kontrollkästchen Regulärer Ausdruck in Ihrem Texteditor markiert ist oder
nicht.
Die zwölf Sonderzeichen, durch die die regulären Ausdrücke so spannend werden, hei-
ßen Metazeichen. Wenn Sie mit Ihrer Regex literal nach ihnen suchen wollen, müssen Sie
sie maskieren, indem Sie einen Backslash vor sie setzen. Somit passt die Regex:
\$\(\)\*\+\.\?\[\\\^\{\|

zum Text:
$()*+.?[\^{|
Bemerkenswerterweise fehlen in dieser Liste die schließende eckige Klammer ], der Bin-
destrich - und die schließende geschweifte Klammer }. Die Klammer ]wird nur dann zu
einem Metazeichen, wenn es vorher ein nicht maskiertes [ gab, und } nur nach einem
nicht maskierten {. Es gibt keinen Grund, } jemals zu maskieren. Regeln für Metazeichen
für die Blöcke zwischen [ und ] werden in Rezept 2.3 erläutert.
Das Maskieren eines beliebigen anderen nicht alphanumerischen Zeichens ändert nichts
daran, wie Ihr regulärer Ausdruck arbeitet – zumindest nicht, solange Sie mit einer der in
diesem Buch behandelten Varianten arbeiten. Das Maskieren eines alphanumerischen
Zeichens verpasst ihm entweder eine spezielle Bedeutung, oder es führt zu einem Syntax-
fehler.
User, die mit regulären Ausdrücken noch nicht so vertraut sind, maskieren häufig alle
Sonderzeichen, die ihnen über den Weg laufen. Lassen Sie nicht jeden wissen, dass Sie
ein Anfänger sind. Maskieren Sie weise. Ein Dschungel unnötiger Backslashs sorgt dafür,

28 | Kapitel 2: Grundlagen regulärer Ausdrücke


dass reguläre Ausdrücke schwerer zu lesen sind, insbesondere wenn alle diese Backslashs
auch noch verdoppelt werden müssen, um die Regex als literalen String im Quellcode
unterbringen zu können.

Variationen
Blockmaskierung
DiezSonderzeichenzinzderzASCII-Tabellezsind:z
\Q!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\E
Regex-Optionen: Keine
Regex-Varianten: Java 6, PCRE, Perl
Perl, PCRE und Java unterstützen die Regex-Tokens ‹\Q› und ‹\E›. ‹\Q› unterdrückt die
Bedeutung aller Metazeichen, einschließlich des Backslashs, bis ein ‹\E› kommt. Wenn
Sie ‹\E› weglassen, werden alle Zeichen nach dem ‹\Q› bis zum Ende des Regex als Lite-
rale behandelt.
Einziger Vorteil von ‹\Q...\E› ist, dass es leichter lesbar ist als ‹\.\.\.›.

Obwohl Java 4 und 5 dieses Feature unterstützen, sollten Sie es nicht ver-
wenden. Fehler in der Implementierung führen dazu, dass reguläre Aus-
drücke mit ‹\Q...\E› zu etwas anderem passen, als Sie erwarten und als
PCRE, Perl oder Java 6 finden. Diese Fehler wurden in Java 6 behoben,
womit es sich genau so verhält wie PCRE und Perl.

Übereinstimmungen unabhängig von Groß- und Kleinschreibung


ascii
Regex-Optionen: Ignorieren von Groß- und Kleinschreibung
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
(?i)ascii
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Standardmäßig reagieren reguläre Ausdrücke auf Groß- und Kleinschreibung. ‹regex›
passt zu regex, aber nicht zu Regex, REGEX oder ReGeX. Um ‹regex› zu all diesen Texten
passen zu lassen, muss die Regex Groß- und Kleinschreibung ignorieren.
Bei den meisten Anwendungen lässt sich das über das Markieren eines Kontrollkästchens
erledigen. Alle Programmiersprachen, die im nächsten Kapitel behandelt werden, haben
eine Option oder Eigenschaft, die Sie setzen können, damit Ihre Regex nicht auf Groß-
und Kleinschreibung reagiert. Rezept 3.4 im nächsten Kapitel erläutert, wie Sie die zu
jeder Lösung aufgeführten Regex-Optionen in Ihrem Quellcode umsetzen können.

2.1 Literalen Text finden | 29


Wenn Sie das Ignorieren von Groß- und Kleinschreibung nicht außerhalb der Regex ein-
schalten können, lässt sich das auch über den Modus-Modifikator ‹(?i)› erreichen, wie bei
‹(?i)regex›. Das funktioniert mit den Varianten .NET, Java, PCRE, Perl, Python und Ruby.
.NET, Java, PCRE, Perl und Ruby unterstützen lokale Modus-Modifikatoren, die nur
einen Teil des regulären Ausdrucks beeinflussen. ‹empfindlich(?i)unempfindlich(?-i)
empfindlich› passt zu empfindlichUNEMPFINDLICHempfindlich, aber nicht zu EMPFINDLICH-
unempfindlichEMPFINDLICH. ‹(?i)› schaltet das Ignorieren von Groß- und Kleinschreibung
für den Rest der Regex ein, während ‹(?-i)› dies wieder deaktiviert. Beide lokalen Modi-
fikatoren funktionieren als Umschalter.
Rezept 2.10 zeigt, wie man lokale Modus-Modifikatoren mit Gruppen statt mit Umschal-
tern nutzt.

Siehe auch
Rezepte 2.3 und 5.14.

2.2 Nicht druckbare Zeichen finden


Problem
Finden eines Strings mit den folgenden ASCII-Steuerzeichen: Bell, Escape, Form Feed,
Line Feed, Carriage Return, horizontaler Tab, vertikaler Tab. Diese Zeichen haben die
hexadezimalen ASCII-Codes 07, 1B, 0C, 0A, 0D, 09, 0B.

Lösung
\a\e\f\n\r\t\v
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Python, Ruby
\x07\x1B\f\n\r\t\v
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Python, Ruby
\a\e\f\n\r\t\0x0B
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion
Sieben der meistgenutzten ASCII-Steuerzeichen haben eigene Maskierungssequenzen. Sie
bestehen alle aus einem Backslash, gefolgt von einem Buchstaben. Das ist die gleiche Syn-
tax, die auch in vielen Programmiersprachen für String-Literale genutzt wird. Tabelle 2-1
zeigt die gebräuchlichsten nicht druckbaren Zeichen und ihre Repräsentation.

30 | Kapitel 2: Grundlagen regulärer Ausdrücke


Tabelle 2-1: Nichtdruckbare Zeichen
Repräsentation Bedeutung Hexadezimale Repräsentation
‹\a› Bell 0x07
‹\e› Escape 0x1B
‹\f› Form Feed/Seitenvorschub 0x0C
‹\n› Line Feed (Newline)/Zeilenvorschub 0x0A
‹\r› Carriage Return/Wagenrücklauf 0x0D
‹\t› horizontaler Tab 0x09
‹\v› vertikaler Tab 0x0B

Der Standard ECMA-262 unterstützt ‹\a› und ‹\e› nicht. Daher nutzen wir für die Java-
Script-Beispiele im Buch eine andere Syntax, auch wenn viele Browser ‹\a› und ‹\e›
trotzdem unterstützen. Perl unterstützt ‹\v› nicht, daher müssen wir hier eine andere
Syntax für den vertikalen Tab verwenden.
Diese Steuerzeichen, wie auch ihre alternative Syntax, die im folgenden Abschnitt gezeigt
wird, können in Ihrem regulären Ausdruck innerhalb und außerhalb von Zeichenklassen
gleichermaßen genutzt werden.

Variationen zur Repräsentation nicht druckbarer Zeichen


Die 26 Steuerzeichen
\cG\x1B\cL\cJ\cM\cI\cK
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Ruby 1.9
Mit ‹\cA› bis ‹\cZ› können Sie eines der 26 Steuerzeichen finden, die in der ASCII-
Tabelle die Positionen 1 bis 26 einnehmen. Das c muss dabei kleingeschrieben sein. In
den meisten Varianten ist es egal, ob der dem c folgende Buchstabe klein- oder großge-
schrieben ist. Wir empfehlen aber, immer einen Großbuchstaben zu nutzen, da Java dies
benötigt.
Die Syntax kann praktisch sein, wenn Sie es gewohnt sind, Steuerzeichen auf Konsolen-
systemen einzugeben, indem Sie die Strg-Taste zusammen mit einem Buchstaben drü-
cken. Auf einem Terminal schickt Strg-H einen Rückschritt. In einer Regex findet ‹\cH›
dementsprechend einen Rückschritt.
Python und die klassische Ruby-Engine in Ruby 1.8 unterstützen diese Syntax nicht, die
Oniguruma-Engine in Ruby 1.9 dagegen schon.
Das Escape-Steuerzeichen an Position 27 in der ASCII-Tabelle lässt sich so mit dem eng-
lischen Alphabet nicht mehr abbilden, daher müssen wir in diesem Fall in unserem regu-
lären Ausdruck ‹\x1B› nutzen.

2.2 Nicht druckbare Zeichen finden | 31


Der 7-Bit-Zeichensatz
\x07\x1B\x0C\x0A\x0D\x09\x0B
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Ein kleines \x gefolgt von zwei hexadezimalen Ziffern (als Großbuchstaben) findet ein
einzelnes Zeichen im ASCII-Set. Abbildung 2-1, zeigt, welche hexadezimalen Kombina-
tionen von ‹\x00› bis ‹\x7F› zu welchem Zeichen im ganzen ASCII-Zeichensatz passen.
Die Tabelle ist so aufgebaut, dass die erste hexadezimale Ziffer links von oben nach
unten läuft, während die zweite Ziffer oben von links nach rechts läuft.

0 1 2 3 4 5 6 7 8 9 A B C D E F
0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
2 SP ! " # $ % & ' ( ) * + , - . /
3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
4 @ A B C D E F G H I J K L M N O
5 P Q R S T U V W X Y Z [ \ ] ^ _
6 ` a b c d e f g h i j k l m n o
7 p q r s t u v w x y z { | } ~ DEL

Abbildung 2-1ASCII-Tabelle

Es hängt von Ihrer Regex-Engine und der Codepage, in der Ihr Ausgangstext kodiert ist,
ab, welche Zeichen von ‹\x80› bis ‹\xFF› passen. Wir empfehlen, ‹\x80› bis ‹\xFF› nicht
zu verwenden. Stattdessen greifen Sie besser auf die Unicode-Codepoint-Token zurück,
die in Rezept 2.7, beschrieben werden.
Wenn Sie Ruby 1.8 nutzen oder PCRE ohne UTF-8-Unterstützung kompiliert haben,
können Sie die Unicode-Codepoints nicht nutzen. Ruby 1.8 und PCRE ohne UTF-8 sind
Regex-Engines für 8-Bit-Zeichen. Sie kennen keine Textkodierungen oder Zeichen aus
mehr als einem Byte. ‹\xAA› findet in diesen Engines einfach das Byte 0xAA, egal welches
Zeichen 0xAA darstellt oder ob 0xAA sogar Teil eines Multibyte-Zeichens ist.

Siehe auch
Rezept 2.7.

32 | Kapitel 2: Grundlagen regulärer Ausdrücke


2.3 Ein oder mehrere Zeichen finden
Problem
Erstellen eines regulären Ausdrucks, der alle üblichen Schreibfehler von Kalender findet,
sodass Sie dieses Wort in einem Dokument finden können, ohne den Rechtschreibküns-
ten des Autors vertrauen zu müssen. Es soll für jeden Vokal ein a oder e möglich sein.
Zudem soll ein anderer regulärer Ausdruck erstellt werden, um ein einzelnes hexadezi-
males Zeichen zu finden. Schließlich soll noch eine dritte Regex erstellt werden, um ein
einzelnes Zeichen zu finden, das kein hexadezimales Zeichen ist.

Lösung
Kalender mit Schreibfehlern
K[ae]l[ae]nd[ae]r
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimales Zeichen
[a-fA-F0-9]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Nicht hexadezimales Zeichen


[^a-fA-F0-9]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Die Notation mit eckigen Klammern wird als Zeichenklasse bezeichnet. Eine Zeichen-
klasse passt zu einem einzelnen Zeichen aus einer Liste möglicher Zeichen. Die drei Klas-
sen in der ersten Regex passen entweder zu einem a oder einem e, und zwar unabhängig
voneinander. Wenn Sie Kalender mit dieser Regex testen, passt die erste Zeichenklasse zu
a, die zweite zu e und die dritte ebenfalls zu e.

Außerhalb von Zeichenklassen sind zwölf Sonderzeichen Metazeichen. Innerhalb einer


Zeichenklasse haben nur vier Zeichen eine besondere Bedeutung: \, ^, - und ]. Wenn Sie
Java oder .NET nutzen, ist die öffnende eckige Klammer [ ebenfalls ein Metazeichen
innerhalb einer Zeichenklasse. Alle anderen Zeichen sind Literale und ergänzen die Zei-

2.3 Ein oder mehrere Zeichen finden | 33


chenklasse. Der reguläre Ausdruck ‹[$()*+.?{|]› passt zu jedem der neun Zeichen inner-
halb der eckigen Klammern.
Der Backslash maskiert immer das ihm folgende Zeichen, so wie er es auch außerhalb
von Zeichenklassen tut. Das maskierte Zeichen kann ein einzelnes Zeichen sein oder der
Anfang bzw. das Ende eines Bereichs. Die anderen vier Metazeichen spielen ihre spezielle
Bedeutung nur dann aus, wenn sie an einer bestimmten Position stehen. Es ist möglich,
sie als literale Zeichen in eine Zeichenklasse mit aufzunehmen, ohne sie zu maskieren.
Dazu muss man sie dort einfügen, wo sich ihre besondere Bedeutung nicht auswirkt.
‹[][^-]› macht das deutlich, wenn Sie nicht gerade eine JavaScript-Implementierung
nutzen, die sich strikt an den Standard hält. Aber wir empfehlen Ihnen, diese Metazei-
chen immer zu maskieren, sodass die vorige Regex so aussehen sollte: ‹[\]\[\^\-]›.
Durch das Maskieren der Metazeichen wird Ihr regulärer Ausdruck besser verständlich.
Alphanumerische Zeichen können nicht mit einem Backslash maskiert werden. Macht
man es trotzdem, führt das entweder zu einem Fehler, oder man erzeugt ein Regex-Token
(etwas mit einer speziellen Bedeutung in einem regulären Ausdruck). Wenn wir bestimmte
andere Regex-Tokens besprechen, wie zum Beispiel in Rezept 2.2, erwähnen wir, ob sie
innerhalb von Zeichenklassen genutzt werden können. Alle diese Tokens bestehen aus
einem Backslash und einem Zeichen, manchmal gefolgt von einer Reihe weiterer Zeichen.
Somit findet ‹[\r\n]› ein Carriage Return (\r) oder einen Line Break (\n).
Der Zirkumflex (^) negiert die Zeichenklasse, wenn Sie ihn direkt nach der öffnenden
eckigen Klammer platzieren. Dadurch wird durch die Zeichenklasse jedes Zeichen gefun-
den, das sich nicht in der Liste befindet. Eine negierte Zeichenklasse passt zu Zeilenum-
bruchzeichen, sofern Sie sie nicht zur negierten Zeichenklasse hinzufügen.
Der Bindestrich (-) erstellt einen Bereich, wenn er zwischen zwei Zeichen platziert wird. Der
Bereich besteht dann aus der Zeichenklasse mit dem Zeichen vor dem Bindestrich, dem
Zeichen nach dem Bindestrich und allen Zeichen, die in numerischer Reihenfolge dazwi-
schenliegen. Um zu wissen, welche Zeichen das sind, müssen Sie sich die ASCII- oder Uni-
code-Zeichentabelle anschauen. ‹[A-z]› enthält alle Zeichen in der ASCII-Tabelle
zwischen dem großen A und dem kleinen z. Der Bereich enthält auch einige Sonderzeichen,
daher sollte man eher die Zeichenklasse ‹[A-Z\[\\\]\^_`a-z]› nutzen, die die Zeichen
expliziter angibt. Wir empfehlen Ihnen, Bereiche nur zwischen zwei Ziffern oder zwischen
zwei Buchstaben, die beide entweder Groß- oder Kleinbuchstaben sind, zu erstellen
Umgekehrte Bereiche, wie zum Beispiel ‹[z-a]›, sind nicht erlaubt.

Variationen
Abkürzungen
[a-fA-F\d]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

34 | Kapitel 2: Grundlagen regulärer Ausdrücke


Sechs Regex-Tokens, die aus einem Backslash und einem Buchstaben bestehen, stehen
jeweils für Abkürzungen von Zeichenklassen. Sie können diese sowohl innerhalb als auch
außerhalb einer Zeichenklasse nutzen. ‹\d› und ‹[\d]› entsprechen beide einer einzelnen
Ziffer. Jedes Regex-Token mit einem Kleinbuchstaben hat auch ein entsprechendes
Token mit einem Großbuchstaben, der für das Gegenteil steht. Daher entspricht ‹\D›
jedem Zeichen, das keine Ziffer ist, ist also identisch mit ‹[^\d]›.
‹\w› findet ein einzelnes Wortzeichen. Ein Wortzeichen ist ein Zeichen, das als Teil eines
Worts vorkommen kann. Dazu gehören Buchstaben, Ziffern und der Unterstrich. Diese
Festlegung mutet ein bisschen seltsam an, aber sie wurde getroffen, da diese Zeichen
üblicherweise auch für Bezeichner in Programmiersprachen erlaubt sind. ‹\W› passt dem-
entsprechend zu jedem Zeichen, das nicht Teil eines Worts ist.
In Java, JavaScript, PCRE und Ruby entspricht ‹\w› immer ‹[a-zA-Z0-9_]›. In .NET und
Perl sind auch Zeichen und Ziffern aus allen anderen Schriftsystemen enthalten (Kyril-
lisch, Thailändisch und so weiter). In Python sind die anderen Schriftzeichen und -ziffern
nur enthalten, wenn Sie beim Erzeugen der Regex die Option UNICODE oder U mitgeben.
‹\d› folgt in allen Varianten den gleichen Regeln. In .NET und Perl sind Ziffern aus ande-
ren Schriftsystemen immer enthalten, während Python sie nur dann berücksichtigt, wenn
Sie die Option UNICODE oder U mitgeben.
‹\s› findet jedes Whitespace-Zeichen. Dazu gehören Leerzeichen, Tabs und Zeilenum-
brüche. In .NET, Perl und JavaScript passt ‹\s› auch zu jedem Zeichen, das durch den
Unicode-Standard als Whitespace definiert ist. Beachten Sie, dass JavaScript für ‹\s›
Unicode nutzt, für ‹\d› und ‹\w› aber ASCII. ‹\S› passt zu jedem Zeichen, das nicht von
‹\s› gefunden wird.
Das Ganze wird noch inkonsistenter, wenn wir ‹\b› hinzufügen. ‹\b› ist keine Abkür-
zung für eine Zeichenklasse, sondern eine Wortgrenze. Sie gehen vielleicht davon aus,
dass ‹\b› Unicode unterstützt, wenn dies auch ‹\w› tut, und nur ASCII, wenn auch ‹\w›
nur auf ASCII zurückgreift, aber das ist nicht immer der Fall. Der Abschnitt „Wortzei-
chen“ auf Seite 46 in Rezept 2.6 geht da näher drauf ein.

Groß- und Kleinschreibung ignorieren


(?i)[A-F0-9]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
(?i)[^A-F0-9]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Das Ignorieren von Groß- und Kleinschreibung beeinflusst auch Zeichenklassen, egal ob
durch eine externe Option gesetzt (siehe Rezept 3.4) oder durch einen Modus-Modifika-
tor innerhalb der Regex (siehe Rezept 2.1). Die oben gezeigten beiden Regexes verhalten
sich genau so wie die in der ursprünglichen Lösung.

2.3 Ein oder mehrere Zeichen finden | 35


JavaScript hält sich an die gleichen Regeln, unterstützt aber nicht ‹(?i)›. Um einen regu-
lären Ausdruck in JavaScript Groß- und Kleinschreibung ignorieren zu lassen, setzen Sie
beim Erstellen die Option /i.

Variantenspezifische Features
Zeichenklassendifferenz in .NET
[a-zA-Z0-9-[g-zG-Z]]

Dieser reguläre Ausdruck passt zu einem einzelnen hexadezimalen Zeichen, allerdings


auf recht umständlichen Wegen. Die grundlegende Zeichenklasse passt zu jedem alpha-
numerischen Zeichen, und eine eingebettete Klasse zieht dann davon die Buchstaben g
bis z ab. Diese eingebettete Klasse muss am Ende der Basisklasse erscheinen und durch
ein Minuszeichen (einen Bindestrich) von ihr „abgezogen“ werden: ‹[Klasse-[Subtra-
hend]]›.
Die Differenz von Zeichenklassen ist insbesondere im Zusammenhang mit Unicode-
Eigenschaften, -Blöcken und -Schriftsystemen nützlich. So findet zum Beispiel
‹\p{IsThai}› jedes Zeichen im Thai-Block. ‹\P{N}› findet jedes Zeichen, das keine
Nummerneigenschaft besitzt. Kombiniert man beides mithilfe der Differenz, findet
‹[\p{IsThai}-[\P{N}]]› jede der zehn thailändischen Ziffern.

Zeichenklassenvereinigung, -differenz und -schnittmenge in Java


[a-f[A-F][0-9]]
[a-f[A-F[0-9]]]

Java erlaubt es, eine Zeichenklasse in einer anderen einzubetten. Wenn die eingebettete
Klasse direkt enthalten ist, entspricht die Ergebnisklasse der Vereinigungsmenge beider
Klassen. Sie können so viele Klassen einbetten, wie Sie wollen. Beide obigen Regexes
haben den gleichen Effekt wie die ursprüngliche Regex ohne die zusätzlichen eckigen
Klammern.
[\w&&[a-fA-F0-9\s]]

Diese Regex könnte einen Preis in einem Regex-Verschleierungswettbewerb gewinnen.


Die grundlegende Zeichenklasse passt zu jedem Wortzeichen. Die eingebettete Klasse
findet jedes hexadezimale Zeichen und jeden Whitespace. Die Ergebnisklasse ist die
Schnittmenge von beiden, wodurch nur hexadezimale Zeichen gefunden werden und
sonst nichts. Da die Basisklasse kein Whitespace findet und die eingebettete Klasse nicht
die Zeichen ‹[g-zG-Z_]›, werden alle aus der Ergebnis-Zeichenklasse herausgenommen
und es verbleiben nur die hexadezimalen Zeichen.
[a-zA-Z0-9&&[^g-zG-Z]]

Dieser reguläre Ausdruck findet ein einzelnes hexadezimales Zeichen, aber ebenfalls
recht umständlich. Die grundlegende Zeichenklasse findet jedes alphanumerische Zei-
chen und eine eingebettete Klasse zieht dann davon die Buchstaben g bis z ab. Bei dieser

36 | Kapitel 2: Grundlagen regulärer Ausdrücke


eingebetteten Klasse muss es sich um eine negierte Zeichenklasse handeln, der zwei Kauf-
manns-Und-Zeichen vorangestellt sind: ‹[Klasse&&[^Subtrahend]]›.
Vereinigungen und Differenzen von Zeichenklassen sind besonders nützlich, wenn man
mit Unicode-Eigenschaften, -Blöcken und -Schriftsystemen arbeitet. So findet zum Bei-
spiel ‹\p{IsThai}› jedes Zeichen im Thai-Block. ‹\P{N}› findet jedes Zeichen, das keine
Nummerneigenschaft besitzt. Zusammen findet ‹[\p{IsThai}-[\P{N}]]› jede der zehn
thailändischen Ziffern.
Wenn Sie sich über die feinen Unterschiede des Regex-Tokens ‹\p› wundern, werden Sie
die Antworten darauf in Rezept 2.7 finden.

Siehe auch
Rezepte 2.1, 2.2 und 2.7.

2.4 Ein beliebiges Zeichen finden


Problem
Finden eines Zeichens in Anführungszeichen. Bereitstellen einer Lösung, die ein beliebi-
ges Zeichen zwischen den Anführungszeichen findet, mit Ausnahme eines Zeilenum-
bruchs. Bereitstellen einer anderen Regex, die wirklich jedes Zeichen zulässt, auch
Zeilenumbrüche.

Lösung
Jedes Zeichen mit Ausnahme von Zeilenumbrüchen
'.'
Regex-Optionen: Keine (die Option Punkt passt zu Zeilenumbruch darf nicht gesetzt sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Jedes Zeichen einschließlich Zeilenumbrüchen


'.'
Regex-Optionen: Punkt passt zu Zeilenumbruch
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
'[\s\S]'
Regex-Optionen: Keine
Regex-Varianten .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

2.4 Ein beliebiges Zeichen finden | 37


Diskussion
Jedes Zeichen außer Zeilenumbrüchen
Der Punkt ist eines der ältesten und einfachsten Elemente regulärer Ausdrücke. Seine
Bedeutung war schon immer die, ein beliebiges einzelnes Zeichen zu finden.
Allerdings ist nicht ganz klar, was beliebiges Zeichen genau bedeutet. Die allerersten
Tools, die reguläre Ausdrücke nutzten, verarbeiteten Dateien Zeile für Zeile, daher ent-
hielt der Ausgangstext auch nie Zeilenumbrüche. Die in diesem Buch besprochenen Pro-
grammiersprachen verarbeiten den Ausgangstext als Ganzes, egal ob er Zeilenumbrüche
enthält oder nicht. Wenn Sie wirklich eine zeilenbasierte Verarbeitung brauchen, müssen
Sie etwas Code schreiben, der Ihren Text in ein Array mit Zeilen aufteilt und die Regex
auf jede Zeile in diesem Array anwendet. Rezept 3.21 zeigt Ihnen, wie das geht.
Larry Wall, der Entwickler von Perl, wollte, dass Perl sich so verhält wie die klassischen
zeilenbasierten Tools, bei denen der Punkt niemals einen Zeilenumbruch (\n) fand. Alle
anderen in diesem Buch behandelten Varianten haben sich daran orientiert. ‹.› findet
daher jedes Zeichen mit Ausnahme eines Zeilenumbruchs.

Jedes Zeichen einschließlich Zeilenumbrüchen


Wenn Ihr regulärer Ausdruck auch über mehr als eine Zeile hinaus arbeiten soll, müssen
Sie die Option Punkt passt zu Zeilenumbruch aktivieren. Diese Option läuft unter ver-
schiedenen Namen. Perl und viele andere nennt sie verwirrenderweise „Single Line“-
Modus, während Java sie als „Dot All“-Modus bezeichnet. In Rezept 3.4 im nächsten
Kapitel finden Sie alle Details. Aber egal wie der Name dieser Option in Ihrer bevorzug-
ten Programmiersprache lautet – bezeichnen Sie sie am besten als „Punkt passt zu Zeilen-
umbruch“-Modus. Denn genau das tut die Option.
Bei JavaScript ist eine andere Lösung erforderlich, denn es besitzt diese Option nicht.
Wie in Rezept 2.3 erläutert, findet ‹\s› jedes Whitespace-Zeichen, während ‹\S› jedes
Zeichen findet, das nicht von ‹\s› gefunden wird. Kombiniert man das zu ‹[\s\S]›,
erhält man eine Zeichenklasse, die alle Zeichen enthält, einschließlich der Zeilenumbrü-
che. ‹[\d\D]› und ‹[\w\W]› haben den gleichen Effekt.

Punkt-Missbrauch
Der Punkt ist das am meisten missbrauchte Zeichen in regulären Ausdrücken.
‹\d\d.\d\d.\d\d› ist zum Beispiel kein guter regulärer Ausdruck, um ein Datum zu fin-
den. Er passt zwar zu 16-05-08, aber auch zu 99/99/99. Schlimmer noch ist, dass er auch
zu 12345678 passt.
Ein guter regulärer Ausdruck, der nur gültige Datumswerte findet, wird in einem späte-
ren Kapitel behandelt. Aber es ist sehr leicht, den Punkt durch eine passendere Zeichen-
klasse zu ersetzen. ‹\d\d[/.\-]\d\d[/.\-]\d\d› erlaubt einen Schrägstrich, einen Punkt
oder einen Bindestrich als Datumstrenner. Diese Regex findet zwar immer noch
99/99/99, aber nicht mehr 12345678.

38 | Kapitel 2: Grundlagen regulärer Ausdrücke


Verwenden Sie den Punkt nur, wenn Sie wirklich jedes Zeichen zulassen wollen. In allen
anderen Situationen sollten Sie eher eine Zeichenklasse oder eine negierte Zeichenklasse
verwenden.

Variationen
(?s)'.'
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python
(?m)'.'
Regex-Optionen: Keine
Regex-Varianten: Ruby
Wenn Sie den „Punkt passt zu Zeilenumbruch“-Modus nicht außerhalb des regulären
Ausdrucks anschalten können, haben Sie die Möglichkeit, einen Modus-Modifikator an
den Anfang des regulären Ausdrucks zu setzen. Wir erläutern das Konzept der Modus-
Modifikatoren und deren Fehlen in JavaScript in Abschnitt „Übereinstimmungen unab-
hängig von Groß- und Kleinschreibung“ auf Seite 29 unter Rezept 2.1.
‹(?s)› ist der Modus-Modifikator für den „Punkt passt zu Zeilenumbruch“-Modus in
.NET, Java, PCRE, Perl und Python. Das s steht für „Singe Line“-Modus, den etwas ver-
wirrenden Namen in Perl.
Die Terminologie ist so irritierend, dass der Entwickler von Rubys Regex-Engine sie
falsch kopiert hat. Ruby nutzt ‹(?m)›, um den „Punkt passt zu Zeilenumbruch“-Modus
einzuschalten. Abgesehen vom Buchstaben ist die Funktionalität vollkommen identisch.
Die neue Engine in Ruby 1.9 nutzt weiterhin ‹(?m)› für „Punkt passt zu Zeilenumbruch“.
Die Bedeutung von ‹(?m)› in Perl wird in Rezept 2.5 erläutert.

Siehe auch
Rezepte 2.3, 3.4 und 3.21.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden


Problem
Erstellen von vier regulären Ausdrücken. Finden des Worts Alpha, aber nur dann, wenn es
am Anfang des Ausgangstexts steht. Finden des Worts Omega, aber nur, wenn es ganz am
Ende des Ausgangstexts steht. Finden des Worts Anfang, aber nur, wenn es am Anfang
einer Zeile steht. Finden des Worts Ende, aber nur, wenn es am Ende einer Zeile steht.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 39


Lösung
Anfang des Texts
^Alpha
Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht gesetzt sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
\AAlpha
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Ende des Texts


Omega$
Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht gesetzt sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
Omega\Z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Zeilenanfang
^Anfang
Regex-Optionen: ^ und $ passen auf Zeilenumbrüche
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Zeilenende
Ende$
Regex-Optionen: ^ und $ passen auf Zeilenumbrüche
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Anker und Zeilen
Die Regex-Tokens ‹^›, ‹$›, ‹\A›, ‹\Z› und ‹\z› werden als Anker bezeichnet. Sie passen
zu keinem Zeichen. Stattdessen passen sie zu bestimmten Positionen, wodurch der regu-
läre Ausdruck an diesen Positionen verankert wird.
Eine Zeile ist der Teil des Ausgangstexts, der zwischen dem Anfang des Texts und einem
Zeilenumbruch, zwischen zwei Zeilenumbrüchen oder zwischen einem Zeilenumbruch
und dem Ende des Texts liegt. Wenn es im Text keine Zeilenumbrüche gibt, wird der

40 | Kapitel 2: Grundlagen regulärer Ausdrücke


ganze Text als eine Zeile angesehen. Daher besteht der folgende Text aus vier Zeilen,
jeweils einer für eins, zwei, einem leeren String und vier:
eins
zwei

vier

Der Text könnte in einem Programm als eins(LF) zwei (LF|LF) vier repräsentiert werden.

Anfang des Texts


Der Anker ‹\A› passt immer zum Anfang des Ausgangstexts, noch vor dem ersten Zei-
chen. Das ist der einzige Ort, an dem er passt. Setzen Sie ‹\A› an den Anfang Ihres regulä-
ren Ausdrucks, um zu prüfen, ob der Ausgangstext mit dem Text beginnt, den Sie finden
wollen. Das „A“ muss ein Großbuchstabe sein.
JavaScript unterstützt kein ‹\A›.
Der Anker ‹^› entspricht ‹\A›, solange Sie nicht die Option ^ und $ passen auf Zeilenum-
brüche aktiviert haben. Diese Option ist standardmäßig für alle Regex-Varianten außer
Ruby abgeschaltet. Ruby bietet auch keine Möglichkeit an, diese Option zu deaktivieren.
Sofern Sie nicht JavaScript nutzen, empfehlen wir Ihnen, immer ‹\A› statt ‹^› zu nutzen.
Die Bedeutung von ‹\A› ändert sich niemals, dadurch vermeidet man Irritationen oder
Fehler, wenn man die Regex-Optionen setzt.

Ende des Texts


Die Anker ‹\Z› und ‹\z› passen immer zum Ende des Ausgangstexts, und zwar nach dem
letzten Zeichen. Setzen Sie ‹\Z› oder ‹\z› an das Ende Ihres regulären Ausdrucks, um zu
prüfen, ob der Ausgangstext mit dem Text endet, den Sie finden wollen.
.NET, Java, PCRE, Perl und Ruby unterstützen sowohl ‹\Z› als auch ‹\z›. Python unter-
stützt nur ‹\Z›. JavaScript unterstützt weder ‹\Z› noch ‹\z›.
Der Unterschied zwischen ‹\Z› und ‹\z› wird dann relevant, wenn das letzte Zeichen Ihres
Ausgangstexts ein Zeilenumbruch ist. In diesem Fall passt ‹\Z› zum einen am Ende des
Ausgangstexts, aber auch direkt vor diesem Zeilenumbruch. Der Vorteil ist, dass Sie nach
‹Omega\Z› suchen können, ohne sich darum kümmern zu müssen, einen abschließenden
Zeilenumbruch zu entfernen. Wenn Sie eine Datei Zeile für Zeile einlesen, nehmen einige
Tools den Zeilenumbruch am Ende der Zeile mit auf, während andere ihn weglassen. ‹\Z›
übergeht diesen Unterschied, während ‹\z› nur ganz am Ende des Ausgangstexts passt,
sodass es nicht passt, wenn noch ein abschließender Zeilenumbruch folgt.
Der Anker ‹$› entspricht ‹\Z›, sofern Sie die Option ^ und $ passen auf Zeilenumbrüche
nicht eingeschaltet haben. Diese Option ist standardmäßig für alle Regex-Varianten
außer Ruby ausgeschaltet. Ruby bietet keine Möglichkeit an, diese Option zu deaktivie-
ren. Wie bei ‹\Z› passt ‹$› ganz am Ende des Ausgangstexts, aber auch direkt vor dem
letzten, abschließenden Zeilenumbruch, wenn es einen gibt.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 41


Um diese subtile und ein wenig konfuse Situation zu entwirren, wollen wir uns ein Bei-
spiel in Perl anschauen. Davon ausgehend, dass $/ (der aktuelle Datensatztrenner) auf
den Standardwert \n gesetzt ist, liest die folgende Perl-Anweisung eine einzelne Zeile vom
Terminal ein (Standardeingabe):
$line = <>;

Perl belässt den Zeilenumbruch im Inhalt der Variablen $line. Daher wird ein Ausdruck
wie ‹EndezderzEingabe.\z› nicht passen. Aber sowohl ‹EndezderzEingabe.\Z› als auch
‹EndezderzEingabe.$› werden gefunden, da sie den abschließenden Zeilenumbruch igno-
rieren.
Um die Verarbeitung zu vereinfachen, entfernen Perl-Programmierer häufig die Zeilen-
umbrüche mit:
chomp $line;

Danach werden alle drei Anker passen. (Technisch gesehen, entfernt chomp den aktuellen
Datensatzseperator vom Ende eines Strings.)
Sofern Sie nicht JavaScript nutzen, empfehlen wir, immer ‹\Z› statt ‹$› zu verwenden.
Die Bedeutung von ‹\Z› ändert sich niemals, dadurch vermeidet man Irritationen oder
Fehler, wenn man die Regex-Optionen setzt.

Anfang einer Zeile


Standardmäßig passt ‹^› nur am Anfang des Ausgangstexts, so wie ‹\A›. Lediglich in
Ruby passt ‹^› immer am Anfang einer Zeile. Bei allen anderen Varianten müssen Sie die
Option einschalten, mit der der Zirkumflex und das Dollarzeichen auch bei Zeilen-
umbrüchen passen. Diese Option wird üblicherweise als „Multiline“-Modus bezeichnet.
Verwechseln Sie das nicht mit dem „Singleline“-Modus, der besser als „Punkt passt zu
Zeilenumbruch“-Modus bezeichnet werden sollte. Der „Multiline“-Modus betrifft nur
den Zirkumflex und das Dollarzeichen, während der „Singleline“-Modus ausschließlich
den Punkt beeinflusst, wie in Rezept 2.4 beschrieben wurde. Es ist ohne Probleme mög-
lich, sowohl den „Singleline“- als auch den „Multiline“-Modus gleichzeitig einzuschal-
ten. Standardmäßig sind beide Optionen ausgeschaltet.
Mit den korrekten Optionen passt ‹^› am Anfang jeder Zeile des Ausgangstexts. Genauer
gesagt, passt er vor dem ersten Zeichen in der Datei, so wie er es immer tut, und nach
jedem Zeilenumbruchzeichen im Ausgangstext. Der Zirkumflex ist in ‹\n^› redundant,
weil ‹^› immer nach einem ‹\n› passt.

Ende einer Zeile


Standardmäßig passt ‹$› nur am Ende des Ausgangstexts oder vor dem letzten Zeilenum-
bruch, so wie ‹\Z›. Lediglich in Ruby passt ‹$› immer am Ende jeder Zeile. Bei allen
anderen Varianten müssen Sie die „Multiline“-Option aktivieren, damit Zirkumflex und
Dollarzeichen auch bei Zeilenumbrüchen passen.

42 | Kapitel 2: Grundlagen regulärer Ausdrücke


Mit den korrekt gesetzten Optionen passt ‹$› am Ende jeder Zeile im Ausgangstext.
(Natürlich passt es auch nach dem letzten Zeichen im Text, weil das immer das Ende
einer Zeile ist.) Das Dollarzeichen ist in ‹$\n› überflüssig, weil ‹$› immer vor ‹\n› passt.

Finden von Text ohne Inhalt


Ein regulärer Ausdruck kann problemlos aus nichts mehr als einem oder mehreren
Ankern bestehen. Solch ein regulärer Ausdruck hat dann an jeder Stelle, an der der Anker
passt, ein Suchergebnis der Länge null. Wenn Sie mehrere Anker kombinieren, müssen
alle Anker an der gleichen Stelle passen, damit die Regex etwas findet.
Sie können solch einen regulären Ausdruck beim Suchen und Ersetzen nutzen. Ersetzen
Sie ‹\A› oder ‹\Z›, um dem gesamten Ausgangstext etwas voranzustellen oder hinten
anzufügen. Ersetzen Sie ‹^› oder ‹$› im „^ und $ passen bei Zeilenumbruch“-Modus,
um jeder Zeile im Ausgangstext etwas voranzustellen oder am Ende anzufügen.
Kombinieren Sie zwei Anker, um auf leere Zeilen oder eine fehlende Eingabe zu testen.
‹\A\Z› passt beim leeren String, aber auch bei einem String, der nur einen einzelnen Zei-
lenumbruch enthält. ‹\A\z› passt nur beim leeren String, ‹^$› im „^ und $ passen bei
Zeilenumbruch“-Modus passt bei jeder leeren Zeile im Ausgangstext.

Variationen
(?m)^Anfang
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python
(?m)Ende$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python
Wenn sich der „^ und $ passen bei Zeilenumbruch“-Modus nicht außerhalb des regulä-
ren Ausdrucks aktivieren lässt, können Sie einen Modus-Modifikator an den Anfang des
regulären Ausdrucks setzen. Das Konzept von Modus-Modifikatoren und die fehlende
Unterstützung in JavaScript werden in Abschnitt „Übereinstimmungen unabhängig von
Groß- und Kleinschreibung“ auf Seite 29 in Rezept 2.1 erläutert.
‹(?m)› ist der Modus-Modifikator für den „^ und $ passen bei Zeilenumbruch“-Modus
in .NET, Java, PCRE, Perl und Python. Das m steht für „Multiline“-Modus. Das ist die
verwirrende Perl-Bezeichnung für „^ und $ passen bei Zeilenumbruch“.
Wie schon erläutert hat diese Terminologie so verwirrt, dass der Entwickler von Rubys
Regex-Engine es falsch kopiert hat. Ruby nutzt ‹(?m)›, um den „Punkt passt zu Zeilen-
umbruch“-Modus einzuschalten. Rubys ‹(?m)› hat nichts mit dem Zirkumflex- und Dol-
lar-Anker zu tun. In Ruby passen ‹^› und ‹$› immer am Anfang und Ende jeder Zeile.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 43


Abgesehen von der unglücklichen Verwechslung der Buchstaben ist die Entscheidung
von Ruby, ‹^› und ‹$› exklusiv für Zeilen zu nutzen, eine gute Entscheidung. Sofern Sie
nicht JavaScript nutzen, empfehlen wir Ihnen, das auch in Ihre eigenen regulären Aus-
drücke zu übernehmen.
Jan hat diese Idee beim Design von EditPad Pro und PowerGREP umgesetzt. Sie werden
kein Kontrollkästchen finden, das den Text „^ und $ passen bei Zeilenumbruch“ ent-
hält, aber es gibt natürlich eins für „Punkt passt zu Zeilenumbruch“. Sofern Sie Ihren
regulären Ausdruck nicht mit ‹(?-m)› beginnen, müssen Sie ‹\A› und ‹\Z› nutzen, um
Ihre Regex am Anfang oder Ende Ihrer Datei zu verankern.

Siehe auch
Rezepte 3.4 und 3.21.

2.6 Ganze Wörter finden


Problem
Erstellen einer Regex, die rot in Mein Auto ist rot findet, aber nicht in rotieren oder
Graubrot. Erstellen einer anderen Regex, die rot in Karotte findet, aber in keinem der
vorigen drei Strings.

Lösung
Wortgrenzen
\brot\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Nicht-Wortgrenzen
\Brot\B
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Wortgrenzen
Das Regex-Token ‹\b› wird als Wortgrenze bezeichnet. Es passt an den Anfang oder das
Ende eines Worts. Allein liefert es ein Ergebnis ohne Länge zurück. ‹\b› ist ein Anker, so
wie die Tokens, die im vorigen Abschnitt vorgestellt wurden.

44 | Kapitel 2: Grundlagen regulärer Ausdrücke


Genau genommen passt ‹\b› an diesen drei Stellen:
• Vor dem ersten Zeichen des Texts, wenn das erste Zeichen ein Wortzeichen ist.
• Nach dem letzten Zeichen des Texts, wenn das letzte Zeichen ein Wortzeichen ist.
• Zwischen zwei Zeichen im Text, wenn eines ein Wortzeichen und das andere kein
Wortzeichen ist.
Keine der in diesem Buch behandelten Varianten hat eigene Tokens, die nur vor oder nur
nach einem Wort passen. Sofern Sie keine Regexes erstellen wollen, die nur aus Wort-
grenzen bestehen, sind diese auch nicht notwendig. Die Tokens vor oder nach dem ‹\b›
in Ihrem regulären Ausdruck legen fest, wo ‹\b› passen kann. Das ‹\b› in ‹\bx› und
‹!\b› passt nur am Wortanfang. Das ‹\b› in ‹x\b› und ‹\b!› kann nur am Ende eines
Worts passen. ‹x\bx› und ‹!\b!› können niemals irgendwo passen.
Um eine Suche nach ganzen Wörtern mit regulären Ausdrücken durchzuführen, stecken
Sie das Wort einfach zwischen zwei Wortgrenzen, so wie wir es mit ‹\brot\b› gemacht
haben. Das erste ‹\b› benötigt das ‹r›, damit es am Anfang des Texts oder nach einem
Nicht-Wortzeichen passt. Das zweite ‹\b› benötigt das ‹t›, damit es entweder am Ende
des Strings oder vor einem Nicht-Wortzeichen passt.
Zeilenumbrüche sind Nicht-Wortzeichen. ‹\b› passt also nach einem Zeilenumbruch,
wenn diesem direkt ein Wortzeichen folgt. Auch passt es vor einem Zeilenumbruch,
wenn direkt davor ein Wortzeichen steht. So wird also ein Wort, das eine ganze Zeile ein-
nimmt, auch von einer „Wortsuche“ gefunden. ‹\b› wird nicht vom „Multiline“-Modus
oder ‹(?m)› beeinflusst. Das ist einer der Gründe dafür, dass dieses Buch den „Multi-
line“-Modus als „^ und $ passen bei Zeilenumbruch“-Modus bezeichnet.

Nicht-Wortgrenzen
‹\B› passt an jeder Position im Ausgangstext, an der ‹\b› nicht passt, und ‹\B› passt an
jeder Position, die nicht der Anfang oder das Ende eines Worts ist.
Genauer gesagt, passt ‹\B› an diesen fünf Stellen:
• Vor dem ersten Zeichen des Texts, wenn das erste Zeichen kein Wortzeichen ist.
• Nach dem letzten Zeichen des Texts, wenn das letzte Zeichen kein Wortzeichen ist.
• Zwischen zwei Wortzeichen.
• Zwischen zwei Nicht-Wortzeichen.
• Beim leeren String.
‹\Brot\B› passt zu rot in Karotte, aber nicht zu Mein Auto ist rot, rotieren oder
Graubrot.
Um das Gegenteil einer reinen Wortsuche durchzuführen (also Mein Auto ist rot auszu-
schließen, aber Karotte, rotieren und Graubrot zu finden), müssen Sie eine Alternation
nutzen, um ‹\Brot› und ‹rot\B› zu ‹\Brot|rot\B› zu kombinieren. ‹\Brot› findet rot in
Karotte und Graubrot. ‹rot\B› findet rot in rotieren (und Karotte, wenn sich ‹\Brot›
nicht schon darum gekümmert hat). Rezept 2.8 beschreibt die Alternation.

2.6 Ganze Wörter finden | 45


Wortzeichen
Jetzt haben wir die ganze Zeit über Wortgrenzen geredet, aber nicht darüber, was ein
Wortzeichen ist. Ein Wortzeichen ist ein Zeichen, das als Teil eines Worts vorkommen
kann. Der Abschnitt „Abkürzungen“ auf Seite 34 in Rezept 2.3 hat erklärt, welche Zei-
chen in ‹\w› enthalten sind, womit ein einzelnes Wortzeichen gefunden wird. Leider gilt
für ‹\b› nicht das Gleiche.
Auch wenn alle Varianten in diesem Buch ‹\b› und ‹\B› unterstützen, unterscheiden sie
sich darin, welche Zeichen für sie Wortzeichen sind.
.NET, JavaScript, PCRE, Perl, Python und Ruby lassen ‹\b› zwischen zwei Zeichen pas-
send sein, bei denen eines durch ‹\w› gefunden wird und das andere durch ‹\W›. ‹\B›
passt immer zwischen zwei Zeichen, die entweder durch ‹\w› oder ‹\W› gefunden wer-
den.
JavaScript, PCRE und Ruby betrachten nur ASCII-Zeichen als Wortzeichen. ‹\w› ent-
spricht ‹[a-zA-Z0-9_]›. Bei diesen Varianten können Sie eine Wortsuche in Sprachen vor-
nehmen, die nur die Buchstaben A bis Z, aber keine diakritischen Zeichen nutzen, wie
zum Beispiel Englisch. Es lassen sich dort aber keine Wortsuchen in anderen Sprachen
vornehmen, wie zum Beispiel im Spanischen oder im Russischen.
.NET und Perl behandeln Buchstaben und Ziffern aus allen Schriftsystemen als Wortzei-
chen. Bei diesen Varianten können Sie eine Wortsuche in jeder Sprache durchführen,
auch in solchen, die keine lateinischen Buchstaben nutzen.
Python lässt Ihnen die Wahl. Nicht-ASCII-Zeichen werden nur dann berücksichtigt,
wenn Sie beim Erstellen der Regex die Option UNICODE oder U mitgeben. Diese Option
beeinflusst ‹\b› und ‹\w› gleichermaßen.
Java verhält sich inkonsistent. ‹\w› passt nur auf ASCII-Zeichen, ‹\b› berücksichtigt
dagegen Unicode und funktioniert mit jeder Sprache. In Java findet ‹\b\w\b› einen einzel-
nen englischen Buchstaben, eine Ziffer oder den Unterstrich, der in keiner Sprache der
Welt als Teil eines Worts vorkommt. ‹\b кошка \b› findet das russische Wort für „rot”,
da ‹\b› Unicode unterstützt. Aber ‹\w+› wird kein russisches Wort finden, weil ‹\w› nur
für ASCII-Zeichen ausgelegt ist.

Siehe auch
Rezept 2.3.

46 | Kapitel 2: Grundlagen regulärer Ausdrücke


2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme
bei Unicode
Problem
Verwenden eines regulären Ausdrucks, um das Trademark-Zeichen (™) zu finden. Dazu
soll sein Unicode-Codepoint angegeben und nicht ein vorhandenes Trademark-Zeichen
kopiert werden. Wenn Sie kein Problem mit dem Kopieren haben, ist das Trademark-
Zeichen einfach nur ein weiteres literales Zeichen, selbst wenn Sie es nicht direkt über
Ihre Tastatur eingeben können. Literale Zeichen werden in Rezept 2.1 behandelt.
Erstellen eines regulären Ausdrucks, der jedes Zeichen mit der Unicode-Eigenschaft
„Währungssymbol“ findet. Unicode-Eigenschaften werden auch als Unicode-Kategorien
bezeichnet.
Erstellen eines regulären Ausdrucks, der jedes Zeichen im Unicode-Block „Greek Exten-
ded“ findet.
Erstellen eines regulären Ausdrucks, der jedes Zeichen findet, das laut Unicode-Standard
Teil des griechischen Schriftsystems ist.
Erstellen eines regulären Ausdrucks, der ein Graphem findet, also das, was üblicherweise
als Zeichen angesehen wird: ein Basiszeichen mit all seinen Ergänzungen.

Lösung
Unicode-Codepoint
\u2122
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, Python
Diese Regex funktioniert in Python nur, wenn sie als Unicode-String eingegeben wird:
u"\u2122".
\x{2122}
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl, Ruby 1.9
PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8-
Unterstützung durch den Muster-Modifikator /u aktiviert werden. Ruby 1.8 unterstützt
keine Unicode-Regexes.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 47


Unicode-Eigenschaft oder -Kategorie
\p{Sc}
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9
PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8-
Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python
unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine Unicode-
Regexes.

Unicode-Block
\p{IsGreekExtended}
Regex-Optionen: Keine
Regex-Varianten: .NET, Perl
\p{InGreekExtended}
Regex-Optionen: Keine
Regex-Varianten: Java, Perl
JavaScript, PCRE, Python und Ruby unterstützen keine Unicode-Blöcke.

Unicode-Schriftsystem
\p{Greek}
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl, Ruby 1.9
Für eine Unterstützung von Unicode-Schriftsystemen (Skripten) wird die PCRE 6.5 benö-
tigt, die mit UTF-8-Unterstützung kompiliert werden muss. In PHP wird die UTF-8-
Unterstützung durch den Muster-Modifikator /u aktiviert. .NET, JavaScript und Python
unterstützen keine Unicode-Schriftsysteme. Ruby 1.8 unterstützt gar keine Unicode-
Regexes.

Unicode-Graphem
\X
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl
PCRE und Perl haben ein eigenes Token für das Finden von Graphemen, sie unterstützen
aber auch den Workaround mit Unicode-Eigenschaften.
\P{M}\p{M}*
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

48 | Kapitel 2: Grundlagen regulärer Ausdrücke


PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8-
Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python
unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine Unicode-
Regexes.

Diskussion
Unicode-Codepoint
Ein Codepoint ist ein Eintrag in der Unicode-Zeichendatenbank, jedoch nicht unbedingt
das Gleiche wie ein Zeichen – abhängig davon, was Sie mit „Zeichen“ meinen. Was auf
dem Bildschirm erscheint, wird in Unicode als Graphem bezeichnet.
Der Unicode-Codepoint U+2122 steht für das „Trademark“-Zeichen. Sie können ihn,
abhängig von der genutzten Variante, mit ‹\u2122› oder ‹\x{2122}› finden.
Bei der ‹\u›-Syntax muss man genau vier hexadezimale Ziffern angeben. Sie können sie
also nur für die Unicode-Codepoints von U+0000 bis U+FFFF nutzen. Bei der ‹\x›-Syn-
tax lassen sich beliebig viele hexadezimale Ziffern angeben, wodurch alle Codepoints von
U+000000 bis U+10FFFF unterstützt werden. U+00E0 finden Sie zum Beispiel mit
‹\x{E0}› oder ‹\x{00E0}›. Codepoints ab U+100000 werden nur sehr selten genutzt und
auch durch Fonts und Betriebssysteme eher wenig unterstützt.
Codepoints können innerhalb und außerhalb von Zeichenklassen genutzt werden.

Unicode-Eigenschaften oder -Kategorien


Jeder Unicode-Codepoint hat genau eine Unicode-Eigenschaft oder passt zu einer einzel-
nen Unicode-Kategorie. Diese Begriffe haben dabei die gleiche Bedeutung. Es gibt 30 Uni-
code-Kategorien, die in sieben übergeordneten Kategorien zusammengefasst sind:
‹\p{L}›
Jegliche Buchstaben beliebiger Sprachen.
‹\p{Ll}›
Kleinbuchstaben, für die es auch Großbuchstaben gibt.
‹\p{Lu}›
Großbuchstaben, für die es auch Kleinbuchstaben gibt.
‹\p{Lt}›
Ein Buchstabe, der am Anfang eines Worts steht, wenn nur der erste Buchstabe
des Worts großgeschrieben ist.
‹\p{Lm}›
Ein Sonderzeichen, das als Buchstabe verwendet wird.
‹\p{Lo}›
Ein Buchstabe oder Ideogramm, das keine Varianten in Klein- oder Großschrei-
bung besitzt.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 49


‹\p{M}›
Ein Zeichen, das dafür gedacht ist, mit anderen Zeichen kombiniert zu werden
(Akzente, Umlaute, umschließende Kästchen und so weiter).
‹\p{Mn}›
Ein Zeichen, das dafür gedacht ist, mit einem anderen Zeichen kombiniert zu wer-
den, und keinen zusätzlichen Raum einnimmt (zum Beispiel Akzente und Umlaute).
‹\p{Mc}›
Ein Zeichen, das dafür gedacht ist, mit einem anderen Zeichen kombiniert zu
werden, und zusätzlichen Raum einnimmt (zum Beispiel Akzente in vielen östli-
chen Sprachen).
‹\p{Me}›
Ein Zeichen, das andere Zeichen umschließt (Kreise, Quadrate, Tastenkappen
und so weiter).
‹\p{Z}›
Jeglicher Whitespace oder unsichtbare Trenner.
‹\p{Zs}›
Ein Whitespace-Zeichen, das unsichtbar ist, aber Raum einnimmt.
‹\p{Zl}›
Das Zeilentrennzeichen U+2028.
‹\p{Zp}›
Das Absatztrennzeichen U+2029.
‹\p{S}›
Mathematische Symbole, Währungssymbole, Dingbats, Zeichen für Kästen und so
weiter.
‹\p{Sm}›
Mathematische Symbole.
‹\p{Sc}›
Währungssymbole.
‹\p{Sk}›
Ein Modifikator als eigenes Zeichen.
‹\p{So}›
Verschiedene Symbole, die keine mathematischen Symbole, Währungssymbole
oder Modifikatorzeichen sind.
‹\p{N}›
Numerische Zeichen in allen Schriftsystemen.
‹\p{Nd}›
Eine Ziffer von 0 bis 9 in jeglichem Schriftsystem mit Ausnahme ideografischer
Schriften.
‹\p{Nl}›
Eine Zahl, die wie ein Buchstabe aussieht, zum Beispiel eine römische Ziffer.
‹\p{No}›
Eine hoch- oder tiefgestellte Ziffer oder eine Zahl, die keine Ziffer von 0 bis 9 ist
(außer Zahlen aus ideografischen Schriften).

50 | Kapitel 2: Grundlagen regulärer Ausdrücke


‹\p{P}›
Jegliches Satzzeichen.
‹\p{Pd}›
Striche (Bindestriche, Gedankenstriche und so weiter).
‹\p{Ps}›
Öffnende Klammern.
‹\p{Pe}›
Schließende Klammern.
‹\p{Pi}›
Öffnende Anführungszeichen.
‹\p{Pf}›
Schließende Anführungszeichen.
‹\p{Pc}›
Ein Satzzeichen, das Wörter verbindet, wie zum Beispiel ein Unterstrich.
‹\p{Po}›
Satzzeichen, die kein Strich, keine Klammer, kein Anführungszeichen und kein
Verbindungszeichen sind.
‹\p{C}›
Unsichtbare Steuerzeichen und ungenutzte Codepoints.
‹\p{Cc}›
Ein Steuerzeichen im Bereich ASCII 0x00…0x1F oder Latin-1 0x80…0x9F.
‹\p{Cf}›
Eine unsichtbare Formatanweisung.
‹\p{Co}›
Codepoints, die für eigene Anwendungen reserviert sind.
‹\p{Cs}›
Eine Hälfte eines Stellvertreterpaars in UTF-16-Kodierung.
‹\p{Cn}›
Codepoints, denen kein Zeichen zugewiesen wurde.
‹\p{Ll}› passt zu einem einzelnen Codepoint, der die Eigenschaft Ll oder „Kleinbuch-
stabe“ besitzt. ‹\p{L}› ist eine einfachere Schreibweise der Zeichenklasse ‹[\p{Ll}\p{Lu}\
p{Lt}\p{Lm}\p{Lo}]›. Diese findet einen einzelnen Codepoint in einer der „Buchstaben“-
Kategorien.
‹\P› ist die negierte Version von ‹\p›. ‹\P{Ll}› passt zu einem einzelnen Codepoint,
der nicht die Eigenschaft Ll besitzt. ‹\P{L}› passt zu einem einzelnen Codepoint, der
keine der „Buchstaben“-Eigenschaften besitzt. Das ist nicht das Gleiche wie
‹[\P{Ll}\P{Lu}\P{Lt}\P{Lm}\P{Lo}]›, da mit Letzterem alle Codepoints gefunden wer-
den. ‹\P{Ll}› passt zu den Codepoints mit der Eigenschaft Lu (und jeder anderen Eigen-
schaft außer Ll), während ‹\P{Lu}› die Codepoints mit Ll enthält. Kombiniert man beide
in einer Codepoint-Klasse, werden immer alle möglichen Codepoints gefunden.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 51


Unicode-Block
Die Zeichendatenbank von Unicode teilt alle Codepoints in Blöcke auf. Jeder Block
besteht aus einem zusammenhängenden Bereich von Codepoints. Die Codepoints
U+0000 bis U+FFFF sind in 105 Blöcke unterteilt:
U+0000…U+007F ‹\p{InBasic_Latin}›
U+0080…U+00FF ‹\p{InLatin-1_Supplement}›
U+0100…U+017F ‹\p{InLatin_Extended-A}›
U+0180…U+024F ‹\p{InLatin_Extended-B}›
U+0250…U+02AF ‹\p{InIPA_Extensions}›
U+02B0…U+02FF ‹\p{InSpacing_Modifier_Letters}›
U+0300…U+036F ‹\p{InCombining_Diacritical_Marks}›
U+0370…U+03FF ‹\p{InGreek_and_Coptic}›
U+0400…U+04FF ‹\p{InCyrillic}›
U+0500…U+052F ‹\p{InCyrillic_Supplementary}›
U+0530…U+058F ‹\p{InArmenian}›
U+0590…U+05FF ‹\p{InHebrew}›
U+0600…U+06FF ‹\p{InArabic}›
U+0700…U+074F ‹\p{InSyriac}›
U+0780…U+07BF ‹\p{InThaana}›
U+0900…U+097F ‹\p{InDevanagari}›
U+0980…U+09FF ‹\p{InBengali}›
U+0A00…U+0A7F ‹\p{InGurmukhi}›
U+0A80…U+0AFF ‹\p{InGujarati}›
U+0B00…U+0B7F ‹\p{InOriya}›
U+0B80…U+0BFF ‹\p{InTamil}›
U+0C00…U+0C7F ‹\p{InTelugu}›
U+0C80…U+0CFF ‹\p{InKannada}›
U+0D00…U+0D7F ‹\p{InMalayalam}›
U+0D80…U+0DFF ‹\p{InSinhala}›
U+0E00…U+0E7F ‹\p{InThai}›
U+0E80…U+0EFF ‹\p{InLao}›
U+0F00…U+0FFF ‹\p{InTibetan}›
U+1000…U+109F ‹\p{InMyanmar}›
U+10A0…U+10FF ‹\p{InGeorgian}›
U+1100…U+11FF ‹\p{InHangul_Jamo}›
U+1200…U+137F ‹\p{InEthiopic}›

52 | Kapitel 2: Grundlagen regulärer Ausdrücke


U+13A0…U+13FF ‹\p{InCherokee}›
U+1400…U+167F ‹\p{InUnified_Canadian_Aboriginal_Syllabics}›
U+1680…U+169F ‹\p{InOgham}›
U+16A0…U+16FF ‹\p{InRunic}›
U+1700…U+171F ‹\p{InTagalog}›
U+1720…U+173F ‹\p{InHanunoo}›
U+1740…U+175F ‹\p{InBuhid}›
U+1760…U+177F ‹\p{InTagbanwa}›
U+1780…U+17FF ‹\p{InKhmer}›
U+1800…U+18AF ‹\p{InMongolian}›
U+1900…U+194F ‹\p{InLimbu}›
U+1950…U+197F ‹\p{InTai_Le}›
U+19E0…U+19FF ‹\p{InKhmer_Symbols}›
U+1D00…U+1D7F ‹\p{InPhonetic_Extensions}›
U+1E00…U+1EFF ‹\p{InLatin_Extended_Additional}›
U+1F00…U+1FFF ‹\p{InGreek_Extended}›
U+2000…U+206F ‹\p{InGeneral_Punctuation}›
U+2070…U+209F ‹\p{InSuperscripts_and_Subscripts}›
U+20A0…U+20CF ‹\p{InCurrency_Symbols}›
U+20D0…U+20FF ‹\p{InCombining_Diacritical_Marks_for_Symbols}›
U+2100…U+214F ‹\p{InLetterlike_Symbols}›
U+2150…U+218F ‹\p{InNumber_Forms}›
U+2190…U+21FF ‹\p{InArrows}›
U+2200…U+22FF ‹\p{InMathematical_Operators}›
U+2300…U+23FF ‹\p{InMiscellaneous_Technical}›
U+2400…U+243F ‹\p{InControl_Pictures}›
U+2440…U+245F ‹\p{InOptical_Character_Recognition}›
U+2460…U+24FF ‹\p{InEnclosed_Alphanumerics}›
U+2500…U+257F ‹\p{InBox_Drawing}›
U+2580…U+259F ‹\p{InBlock_Elements}›
U+25A0…U+25FF ‹\p{InGeometric_Shapes}›
U+2600…U+26FF ‹\p{InMiscellaneous_Symbols}›
U+2700…U+27BF ‹\p{InDingbats}›
U+27C0…U+27EF ‹\p{InMiscellaneous_Mathematical_Symbols-A}›
U+27F0…U+27FF ‹\p{InSupplemental_Arrows-A}›
U+2800…U+28FF ‹\p{InBraille_Patterns}›
U+2900…U+297F ‹\p{InSupplemental_Arrows-B}›

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 53


U+2980…U+29FF ‹\p{InMiscellaneous_Mathematical_Symbols-B}›
U+2A00…U+2AFF ‹\p{InSupplemental_Mathematical_Operators}›
U+2B00…U+2BFF ‹\p{InMiscellaneous_Symbols_and_Arrows}›
U+2E80…U+2EFF ‹\p{InCJK_Radicals_Supplement}›
U+2F00…U+2FDF ‹\p{InKangxi_Radicals}›
U+2FF0…U+2FFF ‹\p{InIdeographic_Description_Characters}›
U+3000…U+303F ‹\p{InCJK_Symbols_and_Punctuation}›
U+3040…U+309F ‹\p{InHiragana}›
U+30A0…U+30FF ‹\p{InKatakana}›
U+3100…U+312F ‹\p{InBopomofo}›
U+3130…U+318F ‹\p{InHangul_Compatibility_Jamo}›
U+3190…U+319F ‹\p{InKanbun}›
U+31A0…U+31BF ‹\p{InBopomofo_Extended}›
U+31F0…U+31FF ‹\p{InKatakana_Phonetic_Extensions}›
U+3200…U+32FF ‹\p{InEnclosed_CJK_Letters_and_Months}›
U+3300…U+33FF ‹\p{InCJK_Compatibility}›
U+3400…U+4DBF ‹\p{InCJK_Unified_Ideographs_Extension_A}›
U+4DC0…U+4DFF ‹\p{InYijing_Hexagram_Symbols}›
U+4E00…U+9FFF ‹\p{InCJK_Unified_Ideographs}›
U+A000…U+A48F ‹\p{InYi_Syllables}›
U+A490…U+A4CF ‹\p{InYi_Radicals}›
U+AC00…U+D7AF ‹\p{InHangul_Syllables}›
U+D800…U+DB7F ‹\p{InHigh_Surrogates}›
U+DB80…U+DBFF ‹\p{InHigh_Private_Use_Surrogates}›
U+DC00…U+DFFF ‹\p{InLow_Surrogates}›
U+E000…U+F8FF ‹\p{InPrivate_Use_Area}›
U+F900…U+FAFF ‹\p{InCJK_Compatibility_Ideographs}›
U+FB00…U+FB4F ‹\p{InAlphabetic_Presentation_Forms}›
U+FB50…U+FDFF ‹\p{InArabic_Presentation_Forms-A}›
U+FE00…U+FE0F ‹\p{InVariation_Selectors}›
U+FE20…U+FE2F ‹\p{InCombining_Half_Marks}›
U+FE30…U+FE4F ‹\p{InCJK_Compatibility_Forms}›
U+FE50…U+FE6F ‹\p{InSmall_Form_Variants}›
U+FE70…U+FEFF ‹\p{InArabic_Presentation_Forms-B}›
U+FF00…U+FFEF ‹\p{InHalfwidth_and_Fullwidth_Forms}›
U+FFF0…U+FFFF ‹\p{InSpecials}›

54 | Kapitel 2: Grundlagen regulärer Ausdrücke


Ein Unicode-Block ist ein zusammenhängender, mit anderen Blöcken nicht überlappen-
der Bereich von Codepoints. Auch wenn viele Blöcke die Namen von Unicode-Schriftsys-
temen und Unicode-Kategorien tragen, entsprechen sie ihnen nicht zu 100% Prozent.
Der Name eines Blocks beschreibt nur seine wichtigsten Anwendungen.
Der Währungsblock enthält nicht die Symbole für Dollar und Yen. Diese finden sich aus
historischen Gründen in den Blöcken Basic_Latin und Latin-1_Supplement. Um ein
beliebiges Währungssymbol zu finden, müssen Sie \p{Sc} statt \p{InCurrency} nutzen.
Die meisten Blöcke enthalten auch nicht zugewiesene Codepoints, die durch die Eigen-
schaft ‹\p{Cn}› gekennzeichnet sind. Keine andere Unicode-Eigenschaft und keines der
Unicode-Schriftsysteme enthält nicht zugewiesene Codepoints.
Die Syntax mit ‹\p{InBlockName}› funktioniert bei .NET und Perl. Java verwendet eine
Syntax mit ‹\p{IsBlockName}›.
Perl unterstützt ebenfalls die Variante mit Is, aber wir empfehlen, bei In zu bleiben, um
nicht mit Unicode-Schriftsystemen durcheinanderzukommen. Bei Schriftsystemen unter-
stützt Perl nämlich ‹\p{Script}› und ‹\p{IsScript}›, aber nicht ‹\p{InScript}›.

Unicode-Schriftsysteme
Jeder Unicode-Codepoint ist Teil genau eines Unicode-Schriftsystems (abgesehen von
den nicht zugewiesenen Codepoints, die zu keinem Schriftsystem gehören). Die zugewie-
senen Codepoints bis U+FFFF gehören zu einem der folgenden Schriftsysteme (Skrip-
ten):
‹\p{Common}› ‹\p{Katakana}›
‹\p{Arabic}› ‹\p{Khmer}›
‹\p{Armenian}› ‹\p{Lao}›
‹\p{Bengali}› ‹\p{Latin}›
‹\p{Bopomofo}› ‹\p{Limbu}›
‹\p{Braille}› ‹\p{Malayalam}›
‹\p{Buhid}› ‹\p{Mongolian}›
‹\p{CanadianAboriginal}› ‹\p{Myanmar}›
‹\p{Cherokee}› ‹\p{Ogham}›
‹\p{Cyrillic}› ‹\p{Oriya}›
‹\p{Devanagari}› ‹\p{Runic}›
‹\p{Ethiopic}› ‹\p{Sinhala}›
‹\p{Georgian}› ‹\p{Syriac}›
‹\p{Greek}› ‹\p{Tagalog}›
‹\p{Gujarati}› ‹\p{Tagbanwa}›
‹\p{Gurmukhi}› ‹\p{Taile}›
‹\p{Han}› ‹\p{Tamil}›
‹\p{Hangul}› ‹\p{Telugu}›
‹\p{Hanunoo}› ‹\p{Thaana}›

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 55


‹\p{Hebrew}› ‹\p{Thai}›
‹\p{Hiragana}› ‹\p{Tibetan}›
‹\p{Inherited}› ‹\p{Yi}›
‹\p{Kannada}›

Ein Schriftsystem ist eine Gruppe von Codepoints, die von einem bestimmten menschli-
chen Schreibsystem genutzt wird. Manche Schriftsysteme, wie zum Beispiel Thai, ent-
sprechen einer einzelnen Sprache. Andere, wie Latin, umfassen mehrere Sprachen. Einige
Sprachen bestehen aber auch aus mehreren Schriftsystemen. So gibt es zum Beispiel kein
japanisches Unicode-Schriftsystem, stattdessen stellt Unicode die Schriftsysteme
Hiragana, Katakana, Han und Latin bereit, aus denen japanische Dokumente normaler-
weise erstellt werden.
Wir haben das Schriftsystem Common an erster Stelle aufgeführt, auch wenn es damit aus
der alphabetischen Reihenfolge herausfällt. Dieses Schriftsystem beinhaltet alle Arten
von Zeichen, die für viele Schriftsysteme genutzt werden, wie zum Beispiel Satzzeichen,
Whitespace und verschiedene Symbole.

Unicode-Graphem
Der Unterschied zwischen Codepoints und Zeichen wird dann relevant, wenn es sich um
Kombinationszeichen handelt. Der Unicode-Codepoint U+0061 steht für „Latin small let-
ter a“ (also das kleine „a“), während U+00E0 für „Latin small letter a with grave accent“
steht (also das kleine „à“). Beide stellen etwas dar, was die meisten Leute als Buchstaben
beschreiben würden.
U+0300 ist das Kombinationszeichen „combining grave accent“. Es kann nur nach
einem Buchstaben sinnvoll verwendet werden. Ein String, der aus den Unicode-Code-
points U+0061 und U+0300 besteht, wird als à dargestellt, genauso wie U+00E0. Das
Kombinationszeichen U+0300 wird dabei über dem Zeichen U+0061 angezeigt.
Der Grund für diese beiden unterschiedlichen Vorgehensweisen beim Anzeigen eines
Buchstaben mit Akzent liegt darin, dass viele alte Zeichensätze „a mit Gravis“ als einzelnes
Zeichen kodieren. Die Designer von Unicode dachten, es wäre nützlich, sowohl alte Zei-
chensätze eins zu eins abbilden als auch den Unicode-Weg gehen zu können, bei dem
Akzente und Grundbuchstaben getrennt sind. Mit Letzterem lassen sich nämlich beliebige
Kombinationen erzeugen, die nicht von den alten Zeichensätzen unterstützt wurden.
Für Sie als Regex-Anwender ist es wichtig zu wissen, dass alle in diesem Buch behandel-
ten Regex-Varianten mit Codepoints und nicht mit grafischen Zeichen arbeiten. Wenn
wir sagen, dass der reguläre Ausdruck ‹.› zu einem einzelnen Zeichen passt, passt er in
Wirklichkeit nur zu einem einzelnen Codepoint. Besteht Ihre Text aus den beiden Code-
points U+0061 und U+0300, die zum Beispiel in einer Programmiersprache wie Java als
String-Literal "\u0061\u0300" dargestellt werden können, wird der Punkt nur den Code-
point U+0061, also das a finden, ohne dabei den Akzent U+0300 zu berücksichtigen. Die
Regex ‹..› wird beides finden.

56 | Kapitel 2: Grundlagen regulärer Ausdrücke


Perl und PCRE bieten ein spezielles Regex-Token ‹\X› an, das zu jedem einzelnen Uni-
code-Graphem passt. Im Endeffekt ist es die Unicode-Version des altehrwürdigen
Punkts. Es passt zu jedem Unicode-Codepoint, der kein Kombinationszeichen ist, und
schließt alle direkt darauffolgenden Kombinationszeichen ein, wenn es welche gibt. Mit
‹\P{M}\p{M}*› kann man das Gleiche mit der Unicode-Eigenschaftssyntax erreichen. ‹\X›
findet zwei Übereinstimmungen im Text àà, egal wie dieser kodiert wurde. Wenn er als
"\u00E0\u0061\u0300" kodiert ist, wird die erste Übereinstimmung "\u00E0" und die
zweite "\u0061\u0300" sein.

Variationen
Negierte Form
Das großgeschriebene ‹\P› ist die negierte Form des kleinen ‹\p›. So passt zum Beispiel
‹\P{Sc}› zu jedem Zeichen, das nicht die Unicode-Eigenschaft „Währungssymbol“
besitzt. ‹\P› wird von allen Varianten für alle Eigenschaften, Blöcke und Schriftsprachen
unterstützt, die auch ‹\p› anbieten.

Zeichenklassen
Alle Varianten lassen Tokens der Form ‹\u›, ‹\x›, ‹\p› und ‹\P› auch innerhalb von Zei-
chenklassen zu – sofern sie sie überhaupt unterstützen. Das vom Codepoint repräsen-
tierte Zeichen oder die Zeichen in der Kategorie, dem Block oder dem Schriftsystem
werden der Zeichenklasse hinzugefügt. So können Sie zum Beispiel ein Zeichen finden,
das entweder ein öffnendes Anführungszeichen, ein schließendes Anführungszeichen
oder das Trademark-Symbol (U+2122) ist, indem Sie folgende Zeichenklasse nutzen:
[\p{Pi}\p{Pf}\x{2122}]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

Alle Zeichen auflisten


Wenn die von Ihnen genutzte Regex-Variante keine Kategorien, Blöcke oder Schriftsys-
teme von Unicode unterstützt, können Sie die Zeichen, die sich in der Kategorie, im
Block oder im Schriftsystem befinden, in einer Zeichenklasse aufführen. Bei Blöcken ist
das sehr einfach – jeder Block besteht einfach aus einem Bereich zwischen zwei Code-
points. Der Block „Greek Extended“ besteht aus den Zeichen U+1F00 bis U+1FFF:
[\u1F00-\u1FFF]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, Python
[\x{1F00}-\x{1FFF}]
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl, Ruby 1.9

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 57


Bei den meisten Kategorien und in vielen Schriftsystemen muss man für die Zeichen-
klasse eine lange Liste einzelner Codepoints und kurzer Bereiche angeben. Die Zeichen
jeder Kategorie und vieler Schriftsysteme sind über die ganze Unicode-Tabelle verstreut.
Dies ist das griechische Schriftsystem:
[\u0370-\u0373\u0375\u0376-\u0377\u037A\u037B-\u037D\u0384\u0386
\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03F5\u03F6
\u03F7-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15
\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D
\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBD\u1FBE\u1FBF-\u1FC1
\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FCD-\u1FCF\u1FD0-\u1FD3\u1FD6-\u1FDB
\u1FDD-\u1FDF\u1FE0-\u1FEC\u1FED-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFC
\u1FFD-\u1FFE\u2126]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, Python
Wir haben diesen regulären Ausdruck aufgebaut, indem wir die Liste für das griechische
Schriftsystem aus http://www.unicode.org/Public/UNIDATA/Scripts.txt kopierten und
dann drei reguläre Ausdrücke nutzten, um sie per Suchen und Ersetzen anzupassen:
1. Suchen nach dem regulären Ausdruck ‹;.*› und Ersetzen seiner Übereinstimmun-
gen durch leeren Text. Das entfernt die Kommentare. Wenn unabsichtlich jeglicher
Text gelöscht wird, machen Sie den Vorgang rückgängig und schalten die Option
Punkt passt zu Zeilenumbruch ab.
2. Suchen nach ‹^› mit aktiver Option ^ und $ passen zu Zeilenumbruch und Ersetzen
durch «\u». Das versieht die Codepoints mit \u. Mit einem Ersetzen von ‹\.\.›
durch «-\u» werden die Bereiche angepasst.
3. Schließlich wird ‹\s+› durch leeren Text ersetzt, um die Zeilenumbrüche zu entfer-
nen. Ergänzt man jetzt noch die eckigen Klammern, ist die Regex fertig. Eventuell
müssen Sie zusätzlich ganz am Anfang der Zeichenklasse ein \u ergänzen und/oder
es am Ende entfernen. Das hängt davon ab, ob Sie führende oder abschließende
Leerzeilen aus Scripts.txt mitkopiert haben.
Das mag recht aufwendig erscheinen, aber Jan brauchte dafür weniger als eine Minute.
Das Erstellen der Beschreibung erforderte mehr Zeit. Für die \x{}-Syntax ist es genauso
einfach:
1. Suchen nach dem regulären Ausdruck ‹;.*› und Ersetzen seiner Übereinstimmun-
gen durch leeren Text. Das entfernt die Kommentare. Wenn unabsichtlich jeglicher
Text gelöscht wird, machen Sie den Vorgang rückgängig und schalten die Option
Punkt passt zu Zeilenumbruch ab.
2. Suchen nach ‹^› mit aktiver Option ^ und $ passen zu Zeilenumbruch und Ersetzen
durch «\x{». Das versieht die Codepoints mit \x{. Mit einem Ersetzen von ‹\.\.›
durch «}-\x{» werden die Bereiche angepasst.
3. Schließlich wird ‹\s+› durch «}» ersetzt, um die schließende geschweifte Klammer
zu ergänzen und die Zeilenumbrüche zu entfernen. Fügt man jetzt noch die eckigen
Klammern hinzu, ist die Regex fertig. Eventuell müssen Sie ganz am Anfang der Zei-

58 | Kapitel 2: Grundlagen regulärer Ausdrücke


chenklasse ein \x{ ergänzen und/oder es am Ende entfernen. Das hängt davon ab,
ob Sie führende oder abschließende Leerzeilen aus Scripts.txt mitkopiert haben.
Das Ergebnis ist:
[\x{0370}-\x{0373}\x{0375}\x{0376}-\x{0377}\x{037A}\x{037B}-\x{037D}
\x{0384}\x{0386}\x{0388}-\x{038A}\x{038C}\x{038E}-\x{03A1}
\x{03A3}-\x{03E1}\x{03F0}-\x{03F5}\x{03F6}\x{03F7}-\x{03FF}
\x{1D26}-\x{1D2A}\x{1D5D}-\x{1D61}\x{1D66}-\x{1D6A}\x{1DBF}
\x{1F00}-\x{1F15}\x{1F18}-\x{1F1D}\x{1F20}-\x{1F45}\x{1F48}-\x{1F4D}
\x{1F50}-\x{1F57}\x{1F59}\x{1F5B}\x{1F5D}\x{1F5F}-\x{1F7D}
\x{1F80}-\x{1FB4}\x{1FB6}-\x{1FBC}\x{1FBD}\x{1FBE}\x{1FBF}-\x{1FC1}
\x{1FC2}-\x{1FC4}\x{1FC6}-\x{1FCC}\x{1FCD}-\x{1FCF}\x{1FD0}-\x{1FD3}
\x{1FD6}-\x{1FDB}\x{1FDD}-\x{1FDF}\x{1FE0}-\x{1FEC}\x{1FED}-\x{1FEF}
\x{1FF2}-\x{1FF4}\x{1FF6}-\x{1FFC}\x{1FFD}-\x{1FFE}\x{2126}
\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}
\x{1018A}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}]
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl, Ruby 1.9

Siehe auch
http://www.unicode.org ist die offizielle Website des Unicode Consortium, von der Sie alle
offiziellen Unicode-Dokumente, Zeichentabellen und so weiter herunterladen können.
Unicode ist ein umfangreiches Thema, zu dem ganze Bücher geschrieben wurden. Eines
davon ist Unicode Explained von Jukka K. Korpela (O’Reilly).
Wir können nicht alles, was Sie über die Codepoints, Eigenschaften, Blöcke und Schrift-
systeme wissen sollten, in einem Abschnitt beschreiben. Wir haben noch nicht einmal
erklärt, warum Sie sich damit beschäftigen sollten – Sie sollten es einfach. Die bequeme
Einfachheit der erweiterten ASCII-Tabelle ist in der heutigen globalisierten Welt ein ein-
sames Plätzchen.

2.8 Eine von mehreren Alternativen finden


Problem
Erstellen eines regulären Ausdrucks, der beim wiederholten Anwenden auf den Text
Maria, Julia und Susanne gingen zu Marias Haus nacheinander Maria, Julia, Susanne und
dann wieder Maria findet. Weitere Suchen sollten kein Ergebnis mehr liefern.

Lösung
Maria|Julia|Susanne
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

2.8 Eine von mehreren Alternativen finden | 59


Diskussion
Der vertikale Balken, auch Pipe-Symbol genannt, teilt den regulären Ausdruck in mehrere
Alternativen auf. ‹Maria|Julia|Susanne› passt zu Maria oder Julia oder Susanne. Bei jeder
Suche passt immer nur ein Name, aber es kann immer ein anderer sein.
Alle Varianten regulärer Ausdrücke, die in diesem Buch behandelt werden, nutzen eine
Regex-gesteuerte Engine. Die Engine ist einfach die Software, die dafür sorgt, dass der
reguläre Ausdruck genutzt werden kann. Regex-gesteuert1 heißt, dass alle möglichen Per-
mutationen des regulären Ausdrucks an jeder Stelle des Ausgangstexts ausprobiert wer-
den, bevor die Regex mit dem nächsten Zeichen fortfährt.
Wenn Sie ‹Maria|Julia|Susanne› auf Maria, Julia und Susanne gingen zu Marias Haus
anwenden, wird die Übereinstimmung Maria direkt am Anfang des Strings gefunden.
Wenden Sie die gleiche Regex auf den Rest des Strings an – indem Sie zum Beispiel in
Ihrem Texteditor auf Weitersuchen klicken –, versucht die Regex-Engine, ‹Maria› auf das
erste Komma im String anzuwenden. Das geht schief. Dann versucht sie, ‹Julia› an der
gleichen Stelle zu finden, was ebenfalls misslingt, genauso wie der Versuch, ‹Susanne› am
Komma zu finden. Erst dann springt die Regex-Engine zum nächsten Zeichen im String
weiter. Aber auch beim ersten Leerzeichen wird keine der drei Alternativen gefunden.
Geht die Engine nun zum J weiter, wird die erste Alternative ‹Maria› nicht gefunden.
Dann wird versucht, die zweite Alternative ‹Julia› beim J zu finden. Das passt zu Julia.
Die Regex-Engine hat endlich Erfolg.
Beachten Sie, dass Julia gefunden wurde, obwohl es im Ausgangstext ein weiteres Vor-
kommen von Maria gibt und ‹Maria› in der Regex vor ‹Julia› steht. Zumindest in diesem
Fall ist die Reihenfolge der Alternativen im regulären Ausdruck egal. Der reguläre Aus-
druck findet die am weitesten links stehende Übereinstimmung. Der Text wird von links
nach rechts überprüft, und es wird bei jedem Schritt versucht, alle Alternativen im regu-
lären Ausdruck anzuwenden. Sobald eine der Alternativen passt, wird an der ersten Posi-
tion abgebrochen.
Wenn wir im restlichen Text weitersuchen, wird Susanne gefunden. Die vierte Suche fin-
det dann erneut Maria. Bitten Sie die Engine jedoch um eine fünfte Suche, wird diese fehl-
schlagen, da keine der drei Alternativen zum verbleibenden String s Haus passt.
Die Reihenfolge der Alternativen im regulären Ausdruck ist nur dann von Belang, wenn
zwei von ihnen an der gleichen Position im Text passen können. Die Regex
‹Julia|Juliane› besitzt zwei Alternativen, die an der gleichen Position im Text Ihr Name
ist Juliane passen. Es gibt im regulären Ausdruck keine Wortgrenzen. Die Tatsache, dass
‹Julia› das Wort Juliane in Ihr Name ist Juliane nur teilweise abdeckt, spielt keine Rolle.

1 Es gibt auch textgesteuerte Engines. Der Hauptunterschied liegt darin, dass eine textgesteuerte Engine jedes Zei-
chen im Text nur einmal aufsucht, während eine Regex-gesteuerte Engine jedes Zeichen eventuell mehrfach
anfasst. Textgesteuerte Engines sind viel schneller, lassen sich aber nur mit regulären Ausdrücken im echten
mathematischen Sinn nutzen (beschrieben am Anfang von Kapitel 1). Die tollen regulären Ausdrücke im Perl-
Stil, die dieses Buch so interessant machen, lassen sich nur durch eine Regex-gesteuerte Engine umsetzen.

60 | Kapitel 2: Grundlagen regulärer Ausdrücke


‹Julia|Juliane› passt zu Julia in Ihr Name ist Juliane, weil eine Regex-gesteuerte
Engine ungeduldig ist. Während sie den Text von links nach rechts durchforstet, um die
am weitesten links stehende Übereinstimmung zu finden, sucht sie auch in den Alternati-
ven der Regex von links nach rechts. Sobald sie eine Alternative findet, die passt, bricht
sie ab.
Wenn ‹Julia|Juliane› das J in Ihr Name ist Juliane erreicht, passt die erste Alternative
‹Julia›. Die zweite Alternative wird gar nicht ausprobiert. Weisen wir die Engine an,
nach einer zweiten Übereinstimmung zu suchen, ist vom Ausgangstext nur noch das ne
übrig. Darauf passt keine der beiden Alternativen.
Es gibt zwei Möglichkeiten, Julia davon abzuhalten, Juliane die Show zu stehlen. Eine ist,
die längere Alternative an den Anfang zu stellen: ‹Juliane|Julia›. Besser und zuverlässi-
ger ist es allerdings, genauer zu sagen, was wir wollen: Wir suchen nach Namen, und
Namen sind vollständige Wörter. Reguläre Ausdrücke kümmern sich nicht um Wörter,
aber sie können auf Wortgrenzen Rücksicht nehmen.
Daher werden sowohl ‹\bJulia\b|\bJuliane\b› als auch ‹\bJuliane\b|\bJulia\b› den
Namen Juliane in Ihr Name ist Juliane finden. Aufgrund der Wortgrenzen passt nur
eine Alternative. Die Reihenfolge ist dabei unwichtig.
Rezept 2.12 erklärt, warum ‹\bJuliane?\b› die beste Lösung ist.

Siehe auch
Rezept 2.9.

2.9 Gruppieren und Einfangen von Teilen


des gefundenen Texts
Problem
Verbessern des regulären Ausdrucks zum Finden von Maria, Julia oder Susanne, indem
die Übereinstimmung ein ganzes Wort sein soll. Durch Gruppieren soll ein Paar Wort-
grenzen für die ganze Regex reichen, anstatt dass für jede Alternative Paare genutzt wer-
den müssen.
Erstellen eines regulären Ausdrucks, der jedes Datum im Format yyyy-mm-dd findet und
dabei das Jahr, den Monat und den Tag getrennt einfängt. Ziel ist, mit diesen drei Wer-
ten im Code, der die Regex-Auswertung startet, arbeiten zu können. Es soll davon ausge-
gangen werden, dass alle Datumswerte in Ausgangstext gültig sind. Der reguläre
Ausdruck muss sich nicht mit Werten wie 9999-99-99 herumschlagen, da sie nicht vor-
kommen.

2.9 Gruppieren und Einfangen von Teilen des gefundenen Texts | 61


Lösung
\b(Maria|Julia|Susanne)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
\b(\d\d\d\d)-(\d\d)-(\d\d)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Der Alternationsoperator, der im vorigen Abschnitt beschrieben wurde, hat (nahezu) den
höchsten Rang aller Regex-Operatoren. Wenn Sie probieren, ‹\bMaria|Julia|Susanne\b›
zu nutzen, sind die drei Alternativen ‹\bMaria›, ‹Julia› und ‹Susanne\b›. Diese Regex
findet Julia in Ihr Name ist Julia.
Möchten Sie, dass in Ihrer Regex etwas von der Alternation ausgeschlossen wird, müssen
Sie die Alternativen gruppieren. Das geschieht durch (normale, runde) Klammern. Sie
haben, wie in den meisten Programmiersprachen, den allerhöchsten Rang.
‹\b(Maria|Julia|Susanne)\b› hat drei Alternativen – ‹Maria›, ‹Julia› und ‹Susanne› –
zwischen zwei Wortgrenzen. Diese Regex passt nicht zu Ihr Name ist Juliane.
Wenn die Regex-Engine im Ausgangstext das J in Juliane erreicht, passt die erste Wort-
grenze. Dann kümmert sich die Engine um die Gruppe. Die erste Alternative in der
Gruppe, ‹Maria›, schlägt fehl. Die zweite Alternative ‹Julia› ist erfolgreich. Die Engine
verlässt die Gruppe. Nun bleibt noch das ‹\b›. Die Wortgrenze passt aber nicht zwischen
das a und das ne am Ende des Texts. Somit gibt es bei J keine Übereinstimmung.
Ein Klammernpaar ist nicht nur eine Gruppe, sondern sogar eine einfangende Gruppe. Bei
der Regex für Maria, Julia und Susanne ist das Einfangen nicht so nützlich, da es nur um
das Finden selbst geht. Das Einfangen lässt sich dann gebrauchen, wenn es nur um Teile
der Regex geht, wie zum Beispiel bei ‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b›.
Dieser reguläre Ausdruck passt zu einem Datum im Format yyyy-mm-dd. Die Regex
‹\b\d\d\d\d-\d\d-\d\d\b› macht das Gleiche. Da dieser reguläre Ausdruck keine Alterna-
tion oder Wiederholung verwendet, wird die Gruppierungsfunktion der Klammern nicht
benötigt. Aber das Einfangen ist sehr praktisch.
Die Regex ‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b› hat drei Gruppen zum Einfangen. Gruppen
werden durchnummeriert, indem die öffnenden Klammern von links nach rechts gezählt
werden. Somit ist ‹(\d\d\d\d)› die Gruppe 1, ‹(\d\d)› Gruppe 2, und das zweite
‹(\d\d)› ist Gruppe 3.
Während des Findens speichert die Regex-Engine den Inhalt der so gefundenen Grup-
pen, sobald die schließende Klammer erreicht wird. Findet unsere Regex den Wert 2008-
05-24, wird 2008 für die erste Gruppe gespeichert, 05 für die zweite Gruppe und 24 für die
dritte Gruppe.

62 | Kapitel 2: Grundlagen regulärer Ausdrücke


Es gibt drei Möglichkeiten, den „gefangenen“ Text zu verwenden. Rezept 2.10 in diesem
Kapitel erklärt, wie Sie den gefangenen Text nochmals innerhalb der gleichen Regex zum
Suchen nutzen können. Rezept 2.21 zeigt, wie Sie den gefangenen Text in den Erset-
zungstext einfügen, wenn Sie suchen und ersetzen. Rezept 3.9 im nächsten Kapitel erläu-
tert, wie Ihre Anwendung die Teile der Regex selbst nutzen kann.

Variationen
Nicht-einfangende Gruppen
In der Regex ‹\b(Maria|Julia|Susanne)\b› benötigten wir die Klammern nur für das
Gruppieren. Anstatt eine einfangende Gruppe zu verwenden, können wir auch eine
nicht-einfangende Gruppe nutzen:
\b(?:Maria|Julia|Susanne)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Die drei Zeichen ‹(?:› öffnen eine nicht-einfangende Gruppe. Die Klammer ‹)› schließt
sie. Die nicht-einfangende Gruppe bietet die gleiche Funktionalität zum Gruppieren,
fängt aber nichts ein.
Wenn die öffnenden Klammern einfangender Gruppen gezählt werden, um ihre Position
zu bestimmen, werden die Klammern nicht-einfangender Gruppen nicht mitgezählt. Das
ist der Hauptvorteil nicht-einfangender Gruppen: Sie können sie einer bestehenden
Regex hinzufügen, ohne die Referenzen auf die durchnummerierten einfangenden Grup-
pen durcheinanderzubringen.
Ein weiterer Vorteil nicht-einfangender Gruppen ist eine höhere Geschwindigkeit. Wenn
Sie für eine bestimmte Gruppe keine Rückwärtsreferenzen nutzen (Rezept 2.10), sie nicht
in den Ersetzungstext einfügen (Rezept 2.21) und sie auch nicht in Ihrem Quellcode nut-
zen wollen (Rezept 3.9), erzeugt eine einfangende Gruppe nur unnötigen Overhead, den
Sie durch eine nicht-einfangende Gruppe vermeiden können. In der Praxis werden Sie die
Performanceunterschiede kaum spüren, sofern Sie die Regex nicht in einer großen
Schleife und/oder mit vielen Daten verwenden.

Gruppen mit Modus-Modifikatoren


In der Variation „Übereinstimmungen unabhängig von Groß- und Kleinschreibung“ von
Rezept 2.1 haben wir erklärt, dass .NET, Java, PCRE, Perl und Ruby lokale Modus-
Modifikatoren unterstüzen, indem sie Modusschalter nutzen: ‹empfindlich(?i)
unempfindlich(?-i)empfindlich›. Auch wenn diese Syntax ebenfalls Klammern enthält,
geht es bei Schaltern wie ‹(?i)› auch nicht um Gruppierungen.

2.9 Gruppieren und Einfangen von Teilen des gefundenen Texts | 63


Statt Schalter zu nutzen, können Sie Modus-Modifikatoren in einer nicht-einfangenden
Gruppe verwenden:
\b(?i:Maria|Julia|Susanne)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
empfindlich(?i:unempfindlich)empfindlich
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
Durch das Hinzufügen von Modus-Modifikatoren zu einer nicht-einfangenden Gruppe
wird der Modus für den Teil des regulären Ausdrucks innerhalb der Gruppe gesetzt. Die
vorigen Einstellungen werden mit der schließenden Klammer wieder zurückgesetzt. Da
das Berücksichtigen von Groß- und Kleinschreibung das Standardverhalten ist, wird so
nur dieser Teil innerhalb der Gruppe unempfindlich für die Schreibweise:
(?i:...)

Sie können mehrere Modifikatoren kombinieren: ‹(?ism:Gruppe)›. Mit einem Minuszei-


chen schalten Sie die Modifikatoren ab: ‹(?-ism:Gruppe)› schaltet alle drei Optionen ab.
‹(?i-sm)› schaltet die Unempfindlichkeit für Groß- und Kleinschreibung ein (i) und
sowohl Punkt passt zu Zeilenumbruch (s) als auch Zirkumflex und Dollar passen zu Zeilen-
umbruch (m) ab. Diese Optionen werden in den Rezepten 2.4 und 2.5 erläutert.

Siehe auch
Rezepte 2.10, 2.11, 2.21 und 3.9.

2.10 Vorher gefundenen Text erneut finden


Problem
Erstellen eines regulären Ausdrucks, der „magische“ Datumswerte im Format yyyy-mm-
dd findet. Ein Datum ist magisch, wenn Jahr (ohne Jahrhundert), Monat und Tag den
gleichen Wert haben. So ist zum Beispiel 2008-08-08 ein magisches Datum. Sie können
davon ausgehen, dass alle Datumswerte im Ausgangstext gültig sind. Der reguläre Aus-
druck muss sich nicht mit Werten wie 9999-99-99 herumschlagen. Es müssen nur die
magischen Datumswerte gefunden werden.

Lösung
\b\d\d(\d\d)-\1-\1\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

64 | Kapitel 2: Grundlagen regulärer Ausdrücke


Diskussion
Um schon vorher gefundenen Text weiter hinten in einer Regex nochmals zu finden,
müssen wir zunächst den ersten Text einfangen. Das machen wir mit einer einfangenden
Gruppe, wie es in Rezept 2.9 gezeigt wurde. Danach können wir den gleichen Text
irgendwo in der Regex mithilfe einer Rückwärtsreferenz erneut suchen. Sie können die
ersten neun einfangenden Gruppen referenzieren, indem Sie einen Backslash, gefolgt von
einer einzelnen Ziffer zwischen eins und neun nutzen. Für die Gruppen 10 bis 99 nutzen
Sie ‹\10› bis ‹\99›.

Verwenden Sie nicht ‹\01›. Das ist entweder eine oktale Maskierung oder
ein Fehler. Wir verwenden in diesem Buch keine oktalen Maskierungen,
da die hexadezimalen Maskierungen wie ‹\xFF› viel einfacher zu verste-
hen sind.

Wenn der reguläre Ausdruck ‹\b\d\d(\d\d)-\1-\1\b› den Wert 2008-08-08 vorfindet,


passen die ersten ‹\d\d› zu 20. Die Regex-Engine betritt dann die einfangende Gruppe
und merkt sich die Position, die sie im Ausgangstext erreicht hat.
Die ‹\d\d› innerhalb der Gruppe passen zu 08, und die Engine erreicht das Ende der
Gruppe. Damit wird die erste Teilübereinstimmung 08 als einfangende Gruppe 1 abge-
speichert.
Das nächste Token ist der Bindestrich, der als Literal passt. Dann kommt die Rückwärts-
referenz. Die Regex-Engine holt sich den Inhalt der ersten einfangenden Gruppe: 08. Sie
versucht nun, diesen Text als Literal zu finden. Wenn sich der reguläre Ausdruck nicht
um Groß- und Kleinschreibung kümmert, wird der eingefangene Text dementsprechend
gefunden. Somit war die Rückwärtsreferenz erfolgreich. Der nächste Bindestrich und die
weitere Rückwärtsreferenz passen ebenfalls. Schließlich passt die Wortgrenze zum Ende
des Ausgangstexts, und das Gesamtergebnis ist gefunden: 2008-08-08. Die einfangende
Gruppe hat immer noch den Wert 08 gespeichert.
Wenn eine eingefangene Gruppe wiederholt wird – sei es durch einen Quantor
(Rezept 2.12) oder durch eine Rückwärtsreferenz (Rezept 2.13) –, wird der gespeicherte
Wert jedes Mal überschrieben, wenn die einfangende Gruppe etwas Passendes findet.
Eine Rückwärtsreferenz auf die Gruppe passt nur zu dem Text, der als Letztes von der
Gruppe eingefangen wurde.
Wenn die gleiche Regex auf 2008-05-24 2007-07-07 angewandt wird, passt zunächst
‹\b\d\d(\d\d)› auf 2008, womit 08 für die erste (und einzige) Gruppe abgespeichert wird.
Als Nächstes passt der Bindestrich. Die Rückwärtsreferenz versucht, ‹08› zu finden, ent-
deckt aber nur 05.
Da es keine weiteren Alternativen im regulären Ausdruck gibt, beendet die Engine den
Versuch. Damit werden die Ergebnisse aller einfangenden Gruppen gelöscht. Wenn die
Engine anschließend einen neuen Versuch startet und mit der ersten 0 im Text beginnt,
enthält ‹\1› gar keinen Text.

2.10 Vorher gefundenen Text erneut finden | 65


Bei der Verarbeitung von 2008-05-24 2007-07-07 passt die Gruppe das nächste Mal, wenn
‹\b\d\d(\d\d)› den Wert 2007 findet. Nun wird 07 abgelegt. Dann passt der Bindestrich.
Jetzt versucht die Rückwärtsreferenz, ‹07› zu finden. Das ist erfolgreich, genauso wie der
nächste Bindestrich, die Rückwärtsreferenz und die Wortgrenze. Es wurde also 2007-07-
07 gefunden.
Da die Regex-Engine von links nach rechts vorgeht, müssen Sie die einfangenden Klam-
mern vor die Rückwärtsreferenz setzen. Die regulären Ausdrücke ‹\b\d\d\1-(\d\d)-\1›
und ‹\b\d\d\1-\1-(\d\d)\b› werden niemals etwas finden. Da die Rückwärtsreferenz vor
der einfangenden Gruppe ausgewertet wird, besitzt er noch keinen Wert. Wenn Sie nicht
gerade mit JavaScript arbeiten, schlägt eine Rückwärtsreferenz immer fehl, wenn er auf
eine Gruppe verweist, die noch nicht ausgewertet wurde.
Eine Gruppe, die noch nicht ausgewertet wurde, ist nicht das Gleiche wie eine Gruppe,
die eine Übereinstimmung der Länge null eingefangen hat. Eine Rückwärtsreferenz auf
eine Gruppe mit der Länge null ist immer erfolgreich. Wenn ‹(^)\1› am Anfang des
Texts erfolgreich gefunden wurde, enthält die erste einfangende Gruppe die Übereinstim-
mung des Zirkumflex mit Null-Länge. Damit ist auch ‹\1› erfolgreich. In der Praxis kann
das passieren, wenn der Inhalt einer einfangenden Gruppe vollständig optional ist.

JavaScript ist die einzige uns bekannte Variante, die mit der jahrzehntelan-
gen Tradition der Rückwärtsreferenzen bricht. In JavaScript, zumindest in
Implementierungen, die dem JavaScript-Standard folgen, ist eine Rück-
wärtsreferenz auf eine Gruppe, die noch nicht ausgewertet wurde, immer
erfolgreich – so wie eine Rückwärtsreferenz auf eine Gruppe mit Null-
Länge. Daher passt ‹\b\d\d\1-\1-(\d\d)\b› in JavaScript zu 12--34.

Siehe auch
Rezepte 2.9, 2.11, 2.21 und 3.9.

2.11 Teile des gefundenen Texts einfangen und benennen


Problem
Erstellen eines regulären Ausdrucks, der jedes Datum im Format yyyy-mm-dd findet.
Bereitstellen separater einfangender Gruppen für das Jahr, den Monat und den Tag. Ziel
ist es, mit diesen Werten im Code bequem arbeiten zu können. Dazu sollen dem einge-
fangenen Text die beschreibenden Namen „Jahr“, „Monat“ und „Tag“ zugewiesen wer-
den.
Erstellen eines weiteren regulären Ausdrucks, der „magische“ Datumswerte im Format
yyyy-mm-dd findet. Ein Datum ist magisch, wenn Jahr (ohne Jahrhundert), Monat und
Tag den gleichen Wert haben. So ist zum Beispiel 2008-08-08 ein magisches Datum. Ein-
fangen der magischen Zahl (in diesem Beispiel 08) und Benennen mit „magisch“.

66 | Kapitel 2: Grundlagen regulärer Ausdrücke


Es kann davon ausgegangen werden, dass alle Datumswerte im Ausgangstext gültig sind.
Die regulären Ausdrücke müssen sich nicht mit Werten wie 9999-99-99 herumschlagen.

Lösung
Benannte Captures
\b(?<Jahr>\d\d\d\d)-(?<Monat>\d\d)-(?<Tag>\d\d)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b(?'Jahr'\d\d\d\d)-(?'Monat'\d\d)-(?'Tag'\d\d)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b(?P<Jahr>\d\d\d\d)-(?P<Monat>\d\d)-(?P<Tag>\d\d)\b
Regex-Optionen: Keine
Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Benannte Rückwärtsreferenzen
\b\d\d(?<magisch>\d\d)-\k<magisch>-\k<magisch>\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b\d\d(?'magisch'\d\d)-\k'magisch'-\k'magisch'\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b\d\d(?P<magisch>\d\d)-(?P=magisch)-(?P=magisch)\b
Regex-Optionen: Keine
Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Diskussion
Benannte Captures
Die Rezepte 2.9 und 2.10 beschreiben einfangende Gruppen und Rückwärtsreferenzen.
Genauer gesagt, haben diese Rezepte nummerierte einfangende Gruppen und numme-
rierte Rückwärtsreferenzen genutzt. Jede Gruppe erhält automatisch eine Zahl, die Sie für
die Rückwärtsreferenz nutzen können.
Moderne Regex-Varianten unterstützen neben den nummerierten auch benannte einfan-
gende Gruppen. Der einzige Unterschied zwischen benannten und nummerierten Grup-
pen ist, dass Sie den erstgenannten einen beschreibenden Namen zuweisen können, statt

2.11 Teile des gefundenen Texts einfangen und benennen | 67


sich mit automatisch vorgegebenen Zahlen herumschlagen zu müssen. Benannte Grup-
pen lassen Ihren regulären Ausdruck lesbarer und wartbarer werden. Fügt man eine ein-
fangende Gruppe in eine bestehende Regex ein, können sich die Zahlen ändern, die den
einfangenden Gruppen zugewiesen sind. Selbstvergebene Namen bleiben dagegen beste-
hen.
Python nutzte die erste Regex-Variante, die benannte Captures unterstützte. Dort wurde
die Syntax ‹(?P<Name>Regex)› genutzt. Der Name musste aus Wortzeichen bestehen, die
durch ‹\w› gefunden werden können. ‹(?P<Name>› ist die öffnende Klammer der Gruppe,
während ‹)› die schließende Klammer ist.
Die Designer der .NET-Klasse Regex entwickelten ihre eigene Syntax für benannte Cap-
tures, die zwei Versionen ermöglicht. ‹(?<Name>Regex)› simuliert die Python-Syntax, nur
ohne das P. Der Name muss aus Wortzeichen bestehen, die durch ‹\w› gefunden werden
können. ‹(?<Name>› ist die öffnende Klammer der Gruppe, während ‹)› die schließende
Klammer ist.
Die spitzen Klammern in der Syntax sind nervig, wenn Sie in XML kodieren oder dieses
Buch per DocBook XML schreiben wollen. Das ist der Grund für die Alternative in .NET:
‹(?'Name'Regex)›. Die spitzen Klammern wurden durch einfache Anführungszeichen
ersetzt. Sie können sich aussuchen, welche Syntax Sie nutzen wollen. Die Funktionalität
ist identisch.
Vielleicht aufgrund der größeren Beliebtheit von .NET gegenüber Python scheint die
.NET-Syntax diejenige zu sein, die Entwickler von anderen Regex-Bibliotheken lieber
kopieren. Perl 5.10 nutzt sie genauso wie die Oniguruma-Engine in Ruby 1.9.
PCRE hat die Python-Syntax vor langer Zeit kopiert, als Perl noch keine benannten Cap-
tures unterstützte. PCRE 7, die Version, die in Perl 5.10 neue Features ergänzt hat, unter-
stützt sowohl die .NET-Syntax als auch die Python-Syntax. Vielleicht als Anerkennung
des Erfolgs von PCRE unterstützt Perl 5.10 in einer Art umgekehrter Kompatibilität auch
die Python-Syntax. In PCRE und Perl 5.10 ist die Funktionalität der .NET-Syntax und
der Python-Syntax für benannte Captures identisch.
Wählen Sie die Syntax aus, die für Sie am nützlichsten ist. Wenn Sie in PHP entwickeln
und wollen, dass Ihr Code auch mit älteren Versionen von PHP arbeitet, die dementspre-
chend ältere Versionen von PCRE nutzen, verwenden Sie am besten die Python-Syntax.
Wenn Sie keine Kompatibilität zu älteren Versionen brauchen und auch mit .NET oder
Ruby arbeiten, erleichtert die .NET-Syntax das Kopieren zwischen all diesen Sprachen.
Sind Sie unsicher, nutzen Sie die Python-Syntax für PHP/PCRE. Leute, die Ihren Code
mit einer älteren Version von PCRE kompilieren, werden nicht so glücklich sein, wenn
die Regexes in Ihrem Code plötzlich nicht mehr funktionieren. Wenn Sie eine Regex
nach .NET oder Ruby kopieren, ist das Löschen von ein paar P kein so großer Aufwand.
Die Dokumentationen zu PCRE 7 und Perl 5.10 erwähnen die Python-Syntax kaum, aber
sie ist auf jeden Fall nicht veraltet. Bei PCRE und PHP empfehlen wir sie sogar.

68 | Kapitel 2: Grundlagen regulärer Ausdrücke


Benannte Rückwärtsreferenzen
Mit den benannten Captures kommen auch benannte Rückwärtsreferenzen. So, wie
benannte einfangende Gruppe funktional identisch mit nummerierten einfangenden
Gruppen sind, sind auch benannte Rückwärtsreferenzen funktional identisch mit num-
merierten Rückwärtsreferenzen. Sie lassen sich nur einfacher lesen und warten.
Python nutzt die Syntax ‹(?P=Name)›, um eine Rückwärtsreferenz für die Gruppe Name zu
erzeugen. Auch wenn diese Syntax Klammern nutzt, ist die Rückwärtsreferenz keine
Gruppe. Sie können zwischen den Namen und die schließende Klammer nichts anderes
schreiben. Eine Rückwärtsreferenz ‹(?P=Name)› ist genauso wie ‹\1› ein singuläres Regex-
Token.
.NET nutzt die Syntax ‹\k<Name>› und ‹\k'Name'›. Beide Versionen sind funktional iden-
tisch und können beliebig kombiniert werden. Eine benannte Gruppe, die in der Syntax
mit spitzen Klammern erstellt wurde, kann mit der Anführungszeichensyntax für die
Rückwärtsreferenz genutzt werden und umgekehrt.
Wir empfehlen Ihnen dringend, benannte und nummerierte Gruppen nicht zusammen in
einer Regex zu nutzen. Die verschiedenen Varianten haben unterschiedliche Regeln für
das Durchnummerieren unbenannter Gruppen, die zwischen benannten Gruppen auf-
tauchen. Perl 5.10 und Ruby 1.9 haben die .NET-Syntax übernommen, folgen aber nicht
den Regeln von .NET für das Nummerieren benannter Gruppen oder das Mischen num-
merierter Gruppen mit benannten Gruppen. Anstatt die Unterschiede zu erläutern, emp-
fehle ich einfach, benannte und nummerierte Gruppen nicht zusammen zu nutzen.
Vermeiden Sie Chaos und geben Sie entweder allen unbenannten Gruppen einen Namen
oder sorgen Sie dafür, dass sie nicht einfangend sind.

Siehe auch
Rezepte 2.9, 2.10, 2.21 und 3.9.

2.12 Teile der Regex mehrfach wiederholen


Problem
Erstellen von regulären Ausdrücken, die zu folgenden Arten von Zahlen passen:
• einem Googol (eine Dezimalzahl mit 100 Stellen),
• einer Hexadezimalzahl mit 32 Bit,
• einer Hexadezimalzahl mit 32 Bit und einem optional Suffix h,
• einer Gleitkommazahl mit einem optionalen ganzzahligen Anteil, einem verpflich-
tenden Nachkommateil und einem optionalen Exponenten. Jeder Teil kann eine
beliebige Zahl von Stellen haben.

2.12 Teile der Regex mehrfach wiederholen | 69


Lösung
Googol
\b\d{100}\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimale Zahl
\b[a-f0-9]{1,8}\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimale Zahl mit optionalem Suffix


\b[a-f0-9]{1,8}h?\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Gleitkommazahl
\d*\.\d+(e\d+)?
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Feste Wiederholung
Der Quantor ‹{n}› wiederholt das vorige Regex-Token n Mal, wobei n eine positive Zahl
ist. Das ‹\d{100}› in ‹\b\d{100}\b› findet also einen Text mit 100 Ziffern. Sie könnten
das Gleiche auch erreichen, indem Sie 100 Mal ‹\d› eingeben.
‹{1}› wiederholt das vorige Token ein Mal, so als gäbe es keinen Quantor. ‹ab{1}c› ist
die gleiche Regex wie ‹abc›.
‹{0}› wiederholt das vorige Token null Mal, womit es im Endeffekt aus dem regulären
Ausdruck entfernt wird. ‹ab{0}c› ist die gleiche Regex wie ‹ac›.

Variable Wiederholung
Bei der variablen Wiederholung nutzen wir den Quantor ‹{n,m}›, wobei n eine positive
Zahl und m größer als n ist. ‹\b[a-f0-9]{1,8}\b› passt zu einer hexadezimalen Zahl mit
einer bis acht Stellen. Bei einer variablen Wiederholung wird die Reihenfolge der Alterna-
tiven wichtig. Rezept 2.13 geht an dieser Stelle mehr ins Detail.

70 | Kapitel 2: Grundlagen regulärer Ausdrücke


Wenn n und m gleich sind, haben wir wieder die feste Wiederholung. ‹\b\d{100,100}\b›
ist die gleiche Regex wie ‹\b\d{100}\b›.

Unendliche Wiederholung
Der Quantor ‹{n,}›, bei dem n eine positive Zahl ist, ermöglicht unendlich viele Wieder-
holungen. Im Prinzip ist eine unendliche Wiederholung eine variable Wiederholung ohne
Obergrenze.
‹\d{1,}› passt zu einer oder mehr Ziffern und ist das Gleiche wie ‹\d+›. Ein Plus nach
einem Regex-Token, bei dem es sich nicht um einen Quantor handelt, bedeutet „eins
oder mehr“. Rezept 2.13 erklärt die Bedeutung eines Pluszeichens nach einem Quantor.
‹\d{0,}› passt zu null oder mehr Ziffern und ist das Gleiche wie ‹\d*›. Der Stern bedeu-
tet immer „null oder mehr“. Neben der unendlichen Wiederholung sorgen ‹{0,}› und
der Stern auch dafür, dass das vorige Token optional wird.

Etwas optional machen


Wenn wir eine variable Wiederholung nutzen, bei der n auf null gesetzt wurde, machen
wir damit das Token, das vor dem Quantor steht, optional. ‹h{0,1}› passt zu einem ‹h› –
oder gar keinem. Wenn es kein h gibt, führt ‹h{0,1}› zu einer Übereinstimmung mit
Null-Länge. Nutzen Sie ‹h{0,1}› als regulären Ausdruck allein, findet dieser eine Über-
einstimmung mit Null-Länge vor jedem Zeichen im Text, das kein h ist. Jedes h führt
dagegen zu einer Übereinstimmung mit einem Zeichen (dem h).
‹h?› ist das Gleiche wie ‹h{0,1}›. Ein Fragezeichen nach einem gültigen und vollständi-
gen Regex-Token, das kein Quantor ist, bedeutet „null oder ein Mal“. Das nächste
Rezept beschreibt die Bedeutung eines Fragezeichens nach einem Quantor.

Ein Fragezeichen oder ein anderer Quantor direkt nach einer öffnenden
Klammer führt zu einem Syntaxfehler. Perl und die darauf aufbauenden
Varianten nutzen diese Form, um der Regex-Syntax „Perl-Erweiterungen“
hinzuzufügen. Vorige Rezepte haben nicht-einfangende Gruppen und
benannte einfangende Gruppen vorgestellt, die alle ein Fragezeichen nach
einer öffnenden Klammer als Teil ihrer Syntax verwenden. Diese Fragezei-
chen sind keine Quantoren, sondern einfach Teil der Syntax für nicht-ein-
fangende und benannte einfangende Gruppen. Die folgenden Rezepte
werden noch mehr Arten von Gruppen vorstellen, die eine Syntax mit ‹(?›
nutzen.

Wiederholende Gruppen
Wenn Sie einen Quantor nach der schließenden Klammer einer Gruppe setzen, wird die
ganze Gruppe wiederholt. ‹(?:abc){3}› ist das Gleiche wie ‹abcabcabc›.

2.12 Teile der Regex mehrfach wiederholen | 71


Quantoren können verschachtelt werden. ‹(e\d+)?› passt zu einem e, gefolgt von einer
oder mehreren Ziffern, oder zu einer leeren Übereinstimmung. Bei unserer Regex für die
Gleitkommazahl ist dies der optionale Exponent.
Einfangende Gruppen können wiederholt werden. Wie in Rezept 2.9 beschrieben, wird
die Übereinstimmung der Gruppe jedes Mal gespeichert, wenn die Engine die Gruppe
verlässt. Dabei wird der alte Wert immer überschrieben. ‹(\d\d){1,3}› passt zu einem
String mit zwei, vier oder sechs Ziffern. Die Engine verlässt diese Gruppe drei Mal. Wenn
die Regex 123456 findet, wird die einfangende Gruppe den Wert 56 enthalten, da 56 bei
der letzten Iteration der Gruppe gespeichert wurde. Die anderen beiden Übereinstim-
mungen durch die Gruppe, 12 und 34, können nicht ausgelesen werden.
‹(\d\d){3}› findet den gleichen Text wie ‹\d\d\d\d(\d\d)›. Wenn Sie alle zwei, vier oder
sechs Ziffern einfangen wollen und nicht nur die letzten beiden, müssen Sie die einfan-
gende Gruppe um den Quantor herum einrichten und nicht die einfangende Gruppe wie-
derholen: ‹((?:\d\d){1,3})›. Hier nutzen wir eine nicht-einfangende Gruppe, um die
Gruppierungsfunktion von der einfangenden Gruppe zu übernehmen. Wir hätten auch
zwei einfangende Gruppen verwenden können: ‹((\d\d){1,3})›. Wenn diese letzte
Regex 123456 findet, enthält ‹\1› den Wert 123456 und ‹\2› den Wert 56.
Die Regex-Engine von .NET ist die einzige, die es Ihnen erlaubt, alle Iterationen einer
wiederholten einfangenden Gruppe auszulesen. Wenn Sie direkt die Eigenschaft Value
der Gruppe abfragen, die einen String zurückliefert, werden Sie 56 erhalten, so wie bei
allen anderen Regex-Engines auch. Rückwärtsverweise im regulären Ausdruck und im
Ersetzungstext nutzen ebenfalls die 56, aber wenn Sie die CaptureCollection der Gruppe
verwenden, erhalten Sie einen Stack mit 56, 34 und 12.

Siehe auch
Rezepte 2.9, 2.13, 2.14.

2.13 Minimale oder maximale Wiederholung auswählen


Problem
Ein Paar XHTML-Tags der Form <p> und </p> und den Text dazwischen finden. Der
Text zwischen den Tags kann andere XHTML-Tags enthalten.

Lösung
<p>.*?</p>
Regex-Optionen: Punkt passt zu Zeilenumbruch
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

72 | Kapitel 2: Grundlagen regulärer Ausdrücke


Diskussion
Alle in Rezept 2.12 behandelten Quantoren sind gierig (greedy), das heißt, sie versuchen,
so häufig wie möglich wiederholt zu werden und erst dann mit der Regex weiterzuma-
chen, wenn es keine weitere Übereinstimmung mehr gibt.
Damit kann es schwer werden, Tags in XHTML (von XML abgeleitet; damit braucht
jedes öffnende Tag ein schließendes) paarweise zu behandeln. Schauen Sie sich den fol-
genden einfachen Ausschnitt eines XHTML-Texts an:
<p>
Die <em>Aufgabe</em> ist es, den Anfang eines Absatzes zu finden.
</p>
<p>
Dann müssen Sie das Ende des Absatzes finden.
</p>

Es gibt hier zwei öffnende <p>-Tags und zwei schließende </p>-Tags. Sie wollen das erste
<p> mit dem ersten </p> finden, da beide zusammen einen einzelnen Absatz auszeichnen.
Beachten Sie, dass dieser Absatz ein verschachteltes <em>-Tag enthält, daher kann die
Regex nicht einfach abbrechen, wenn sie einem <-Zeichen begegnet.
Schauen Sie sich eine falsche Lösung für das Problem in diesem Rezept an:
<p>.*</p>
Regex-Optionen: Punkt passt zu Zeilenumbruch
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Der einzige Unterschied besteht darin, dass bei dieser falschen Lösung das zusätzliche
Fragezeichen nach dem Stern fehlt. Die falsche Lösung nutzt den gleichen gierigen Stern,
der auch in Rezept 2.12 erläutert wurde.
Nachdem das erste <p>-Tag im Text gefunden wurde, gelangt die Engine zu ‹.*›. Der
Punkt passt zu jedem Zeichen, auch zum Zeilenumbruch. Der Stern wiederholt ihn null
Mal oder häufiger. Er ist gierig, daher passt ‹.*› zu allem bis zum Ende des Texts. Um es
nochmals deutlich zu sagen: ‹.*› frisst Ihre gesamte XHTML-Datei, vom ersten Absatz
an.
Wenn sich ‹.*› den Bauch vollgeschlagen hat, versucht die Engine, das ‹<› am Ende des
Texts zu finden. Das geht schief. Aber es ist noch nicht das Ende der Geschichte – die
Regex-Engine nutzt Backtracking.
Der Stern versucht, so viel Text wie möglich an sich zu reißen, aber er ist auch zufrieden,
wenn er gar nichts findet (null Wiederholungen). Mit jeder Wiederholung eines Quan-
tors über dessen Minimum hinaus speichert der reguläre Ausdruck eine Backtracking-
Position. Das sind Stellen, an die die Engine zurückspringen kann, falls der dem Quantor
folgende Teil der Regex keinen Erfolg mehr hat.
Wenn ‹<› nicht gefunden wird, springt die Engine per Backtracking zurück, indem sie
‹.*› dazu bringt, ein Zeichen seiner Übereinstimmung wieder herzugeben. Dann wird

2.13 Minimale oder maximale Wiederholung auswählen | 73


erneut versucht, ‹<› zu finden – diesmal beim letzten Zeichen der Datei. Wieder geht es
schief, die Engine springt noch ein Zeichen zurück und versucht, ‹<› an der vorletzten
Position der Datei zu finden. Das wiederholt sich, bis ‹<› erfolgreich gefunden wurde.
Wenn ‹<› nie gefunden wird, hat ‹.*› schließlich keine Backtracking-Positionen mehr,
und der gesamte Vorgang wird erfolglos abgebrochen.
Wird während des Backtracking ‹<› gefunden, macht die Regex mit ‹/› weiter. Ist das
nicht erfolgreich, geht das Backtracking weiter. Das wiederholt sich, bis ‹</p>› vollstän-
dig gefunden werden kann.
Was ist jetzt das Problem? Da der Stern gierig ist, findet der falsche reguläre Ausdruck
alles vom ersten <p> in der XHTML-Datei bis zum letzten </p>. Aber um einen XHTML-
Absatz korrekt zu finden, müssen wir das erste <p> zusammen mit dem ersten folgenden
</p> finden.
Hier kommen die genügsamen (lazy) Quantoren ins Spiel. Sie können jeden Quantor
genügsam machen, indem Sie ihm ein Fragezeichen nachstellen: ‹*?›, ‹+?›, ‹??› und
‹{7,42}?› – alle sind damit nun genügsame Quantoren.
Genügsame Quantoren machen auch Backtracking, aber dieses Mal andersherum. Ein
genügsamer Quantor wiederholt so wenig wie möglich. Er speichert eine Backtracking-
Position und ermöglicht dann der Regex, fortzufahren. Wenn der Rest der Regex keinen
Erfolg hat und die Engine erneut per Backtracking ihr Glück versucht, wiederholt sich
der genügsame Quantor ein weiteres Mal. Solange die Regex per Backtracking durchlau-
fen wird, wird sich der Quantor erweitern, bis er seine maximale Anzahl an Wiederho-
lungen erreicht hat oder bis das wiederholte Regex-Token nicht mehr passt.
‹<p>.*?</p>› nutzt einen genügsamen Quantor, um einen XHTML-Absatz korrekt zu fin-
den. Wenn ‹<p>› passt, macht ‹.*?› nichts außer abzuwarten. Wenn direkt nach dem
<p> ein ‹</p>› kommt, wird ein leerer Absatz gefunden. Wenn nicht, springt die Engine
zurück zu ‹.*?›, das dann ein Zeichen findet. Wenn ‹</p>› immer noch fehlschlägt,
greift ‹.*?› auf das nächste Zeichen zurück. Das geht so weiter, bis entweder ‹</p>›
gefunden wird oder ‹.*?› nichts mehr findet. Da der Punkt zu allem passt, wird es keinen
Fehlschlag geben, bis ‹.*?› alles bis zum Ende der XHTML-Datei abgearbeitet hat.
Die Quantoren ‹*› und ‹*?› können beide das Gleiche finden. Der einzige Unterschied
ist die Reihenfolge, in der mögliche Übereinstimmungen ausprobiert werden. Der gierige
Quantor wird die längste mögliche Übereinstimmung finden. Der genügsame Quantor
wird dagegen die kürzeste mögliche Übereinstimmung finden.
Wenn es geht, sollte man sicherstellen, dass es nur eine mögliche Übereinstimmung gibt.
Die regulären Ausdrücke in Rezept 2.12, mit denen Zahlen gefunden werden sollen, wer-
den immer noch die gleichen Zahlen finden, wenn Sie alle ihre Quantoren genügsam
machen. Der Grund dafür ist, dass die Teile dieser regulären Ausdrücke, die Quantoren
haben, und die dann folgenden Teile sich gegenseitig ausschließen. ‹\d› passt zu einer
Ziffer, und ‹\b› passt nach dem ‹\d› nur, wenn das nächste Zeichen keine Ziffer (und
auch kein Buchstabe) ist.

74 | Kapitel 2: Grundlagen regulärer Ausdrücke


Um besser zu verstehen, wie die gierige und die genügsame Wiederholung funktionieren,
kann es helfen, sich anzuschauen, wie die regulären Ausdrücke ‹\d+\b› und ‹\d+?\b› für
verschiedene Texte arbeiten. Die gierigen und genügsamen Versionen sorgen für die glei-
chen Ergebnisse, aber der Text wird in unterschiedlicher Folge ausgewertet.
Wenn wir ‹\d+\b› auf 1234 anwenden, wird ‹\d+› alle vier Ziffern finden. Dann passt
‹\b›, und es gibt eine Gesamtübereinstimmung. Wenn wir ‹\d+?\b› nutzen, passt ‹\d+?›
zunächst nur zu 1. ‹\b› ist aber zwischen 1 und 2 nicht erfolgreich. ‹\d+?› wird erweitert
zu 12, aber ‹\b› ist immer noch nicht erfolgreich. Das setzt sich fort, bis ‹\d+?›zu 1234
passt und ‹\b› endlich erfolgreich ist.
Hat unser Ausgangstext den Inhalt 1234X, findet die erste Regex ‹\d+\b› durch ‹\d+›
immer noch 1234. Aber dann passt ‹\b› nicht. ‹\d+› geht also per Backtracking zurück
auf 123. ‹\b› passt immer noch nicht. Das setzt sich fort, bis ‹\d+› auf das Minimum 1
zurückgesetzt wurde und ‹\b› immer noch nicht passt. Damit wird der gesamte Vorgang
ohne Ergebnis abgebrochen.
Wenn wir die Regex ‹\d+?\b› für 1234X nutzen, passt ‹\d+?› zunächst nur zu 1. ‹\b› passt
nicht zwischen 1 und 2. ‹\d+?› erweitert auf 12. ‹\b› passt immer noch nicht. Das setzt
sich fort, bis ‹\d+?› zu 1234 passt und ‹\b› immer noch nicht passt. Die Regex-Engine
versucht, ‹\d+?› ein weiteres Mal zu expandieren, aber ‹\d› passt nicht zu X. Der gesamte
Suchvorgang ist somit erfolglos.
Setzen wir ‹\d+› zwischen Wortgrenzen, müssen alle Ziffern im Text passen, da es ansons-
ten kein positives Ergebnis gibt. Wird der Quantor genügsam gemacht, beeinflusst dies das
Endergebnis nicht. Tatsächlich sollte man ‹\b\d+\b› eher ganz ohne Backtracking nutzen.
Das nächste Rezept beschreibt, wie Sie einen possessiven Quantor ‹\b\d++\b› nutzen kön-
nen, um dieses Ziel zu erreichen – zumindest mit ein paar Varianten.

Siehe auch
Rezepte 2.8, 2.9, 2.12, 2.14 und 2.15.

2.14 Unnötiges Backtracking vermeiden


Problem
Das vorige Rezept beschreibt das Backtracking und den Unterschied zwischen gierigen
und genügsamen Quantoren. In manchen Situationen ist Backtracking aber unnötig.
‹\b\d+\b› nutzt einen gierigen Quantor, ‹\b\d+?\b› einen genügsamen. Beide passen zum
gleichen Objekt – einer Ganzzahl. Bei gleichem Ausgangstext werden beide die gleichen
Übereinstimmungen finden. Jegliches Backtracking ist unnötig. Es soll nun dieser regu-
läre Ausdruck so umgeschrieben werden, dass explizit jedes Backtracking vermieden und
der reguläre Ausdruck damit effizienter wird.

2.14 Unnötiges Backtracking vermeiden | 75


Lösung
\b\d++\b
Regex-Optionen: Keine
Regex-Varianten: Java, PCRE, Perl 5.10, Ruby 1.9
Die einfachste Lösung ist die Verwendung eines possessiven Quantors. Aber das ist nur
in ein paar wenigen Regex-Varianten möglich.
\b(?>\d+)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
Eine atomare Gruppe stellt die gleiche Funktionalität bereit, allerdings mit einer etwas
weniger lesbaren Syntax. Die Unterstützung atomarer Gruppen ist etwas weitreichender
als die von possessiven Quantoren.
JavaScript und Python unterstützen weder possessive Quantoren noch atomare Grup-
pen. Dort gibt es keine Möglichkeit, ein unnötiges Backtracking zu vermeiden.

Diskussion
Ein possessiver Quantor ähnelt einem gierigen Quantor: Er versucht, sich so häufig wie
möglich anzuwenden. Der Unterschied ist, dass ein possessiver Quantor niemals etwas
zurückgibt, selbst wenn nur auf diesem Weg der Rest des regulären Ausdrucks passen
könnte. Possessive Quantoren merken sich keine Backtracking-Positionen.
Sie können jeden Quantor possessiv machen, indem Sie ein Pluszeichen hinter ihm ein-
setzen. So sind zum Beispiel ‹*+›, ‹++›, ‹?+› und ‹{7,42}+› allesamt possessiv.
Possessive Quantoren werden von Java 4 und neuer unterstützt – also seit dem ersten
Java-Release, das das Paket java.util.regex enthielt. Alle Versionen von PCRE, die in
diesem Buch behandelt werden (die Versionen 4 bis 7) unterstützen possessive Quanto-
ren. Perl unterstützt sie seit Perl 5.10. Die klassischen regulären Ausdrücke in Ruby
unterstützen sie nicht, aber die Oniguruma-Engine, die in Ruby 1.9 standardmäßig
genutzt wird, kann sie verwenden.
Verpackt man einen gierigen Quantor in einer atomaren Gruppe, hat dies den gleichen
Effekt wie die Verwendung eines possessiven Quantors. Wenn die Regex-Engine die ato-
mare Gruppe verlässt, werden alle Backtracking-Positionen, die sich Quantoren oder
Alternationen innerhalb der Gruppe gemerkt haben, verworfen. Die Syntax ist
‹(?>Regex)›, wobei Regex ein beliebiger regulärer Ausdruck ist. Eine atomare Gruppe ist
im Prinzip eine nicht-einfangende Gruppe, die zusätzlich noch ein Backtracking ablehnt.
Das Fragezeichen ist kein Quantor, die öffnende Klammer besteht einfach aus den drei
Zeichen ‹(?>›.

76 | Kapitel 2: Grundlagen regulärer Ausdrücke


Wenn Sie die Regex ‹\b\d++\b› (possessiv) auf 123abc 456 anwenden, passt ‹\b› am
Anfang des Texts und ‹\d++› findet 123. So weit gibt es noch keinen Unterschied zum gie-
rigen ‹\b\d+\b›. Aber dann schlägt das zweite ‹\b› zwischen 3 und a fehl.
Der possessive Quantor hat keine Backtracking-Positionen gespeichert. Da es keine wei-
teren Quantoren oder Alternationen gibt, werden auch keine anderen Versuche gestartet.
Die Regex-Engine geht somit direkt davon aus, dass es bei 1 keine Übereinstimmung
gibt.
Nun versucht die Regex-Engine, die Regex an der nächsten Zeichenposition anzuwen-
den, was sich durch einen possessiven Quantor nicht ändert. Wenn die Regex den gan-
zen Text finden muss, nutzen Sie Anker, wie es in Rezept 2.5 beschrieben wird.
Schließlich wendet die Regex-Engine den regulären Ausdruck beginnend bei 4 an. Dort
findet sie die Übereinstimmung 456.
Der Unterschied zum gierigen Quantor liegt darin, dass dieser beim erfolglos angewand-
ten zweiten ‹\b› ein Backtracking durchführt. Die Regex-Engine wird dann (unnötiger-
weise) ‹\b› zwischen 2 und 3 und zwischen 1 und 2 testen.
Nutzt man atomare Gruppen, ist das Vorgehen der Regex-Engine im Prinzip die gleiche
wie bei possessiven Quantoren. Wenn Sie die Regex ‹\b(?>\d+)\b› (possessiv) auf 123abc
456 anwenden, passt die Wortgrenze auf den Anfang des Texts. Die Regex-Engine
erreicht dann die atomare Gruppe, und ‹\d+› passt zu 123. Jetzt verlässt die Engine die
atomare Gruppe. Alle in der Gruppe von ‹\d+› gemerkten Backtracking-Positionen wer-
den verworfen. Wenn das zweite ‹\b› nicht passt, wird die Suche an dieser Stelle ohne
Erfolg beendet. Wie beim possessiven Quantor wird dann aber schließlich noch 456
gefunden.
Wir haben den possessiven Quantor so beschrieben, dass er sich keine Backtracking-
Positionen merken kann, während die atomare Gruppe sie verwirft. Das erleichtert das
Verständnis des Vorgehens der Regex-Engine, aber kümmern Sie sich nicht so sehr um
den Unterschied, da er in der von Ihnen genutzten Variante vielleicht gar nicht vorhan-
den ist. In vielen Varianten ist ‹x++› nicht mehr als eine syntaktische Variante von
‹(?>x+)›, und beide Versionen sind identisch implementiert. Ob sich die Engine die
Backtracking-Positionen merkt, um sie später wieder zu verwerfen, oder sie von Anfang
an gar nicht erst abspeichert, ist für das Ergebnis irrelevant.
Es gibt allerdings trotzdem einen Unterschied zwischen possessiven Quantoren und ato-
maren Gruppen. Ein possessiver Quantor ist nur für ein einzelnes Regex-Token gültig,
während eine atomare Gruppe einen ganzen regulären Ausdruck umschließen kann.
‹\w++\d++› und ‹(?>\w+\d+)› sind nicht das Gleiche. ‹\w++\d++›, das ‹(?>\w+)(?>\d+)›
entspricht, passt nicht auf abc123. ‹\w++› findet abc123 vollständig. Dann versucht die
Regex-Engine, ‹\d++› am Ende des Texts zu finden. Da es aber keine weiteren Buchsta-
ben gibt, schlägt ‹\d++› fehl. Ohne sich Backtracking-Positionen zu merken, ist diese
Suche dann nicht erfolgreich.

2.14 Unnötiges Backtracking vermeiden | 77


‹(?>\w+\d+)› hat zwei gierige Quantoren innerhalb der gleichen atomaren Gruppe. In der
Gruppe wird das Backtracking normal durchgeführt. Die Backtracking-Positionen wer-
den erst verworfen, wenn die Engine die gesamte Gruppe verlässt. Lautet der Text
abc123, passt ‹\w+› zu abc123. Der gierige Quantor merkt sich Backtracking-Positionen.
Wenn ‹\d+› nichts findet, rückt ‹\w+› ein Zeichen heraus. ‹\d+› passt dann zu 3. Jetzt
verlässt die Engine die atomare Gruppe und verwirft alle Backtracking-Positionen, die sie
sich für ‹\w+› und ‹\d+› gemerkt hat. Da das Ende der Regex erreicht wurde, macht das
hier auch keinen Unterschied. Es gibt aber ein Suchergebnis.
Falls das Ende noch nicht erreicht ist, so wie in ‹(?>\w+\d+)\d+›, hätten wir die gleiche
Situation wie bei ‹\w++\d++›. Für das zweite ‹\d+› wäre am Ende des Texts nichts mehr
übrig. Da die Backtracking-Positionen weggeworfen wurden, kann die Regex-Engine nur
aufgeben.
Possessive Quantoren und atomare Gruppen helfen nicht nur beim Optimieren regulärer
Ausdrücke, sie können durchaus auch zu anderen Übereinstimmungen führen, da die
Zeichen herausfallen, die durch das Backtracking erreicht worden wären.
Dieses Rezept zeigt Ihnen, wie Sie possessive Quantoren und atomare Gruppen nutzen,
um kleinere Optimierungen vorzunehmen. Die wirken sich eventuell nicht einmal mess-
bar auf die Geschwindigkeit aus. Das nächste Rezept wird aber zeigen, wie man durch
atomare Gruppen auch für drastische Unterschiede sorgen kann.

Siehe auch
Rezepte 2.12 und 2.15.

2.15 Aus dem Ruder laufende Wiederholungen verhindern


Problem
Mit einem einzelnen regulären Ausdruck eine komplette HTML-Datei abdecken und auf
korrekt verschachtelte html-, head-, title- und body-Tags prüfen. Der reguläre Ausdruck
muss bei HTML-Dateien mit nicht korrekt genutzten Tags effizient fehlschlagen.

Lösung
<html>(?>.*?<head>)(?>.*?<title>)(?>.*?</title>)
(?>.*?</head>)(?>.*?<body[^>]*>)(?>.*?</body>).*?</html>
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenum-
bruch
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
JavaScript und Python unterstützen keine atomaren Gruppen. Es gibt daher bei diesen
beiden Varianten keine Möglichkeit, ein unnötiges Backtracking zu vermeiden. Wenn Sie

78 | Kapitel 2: Grundlagen regulärer Ausdrücke


in diesen Sprachen programmieren, können Sie das Problem umgehen, indem Sie für
jedes dieser Tags eine literale Textsuche durchführen und dabei nach dem jeweils nächs-
ten Tag vom Endpunkt der vorherigen Suche aus suchen.

Diskussion
Die korrekte Lösung für dieses Problem lässt sich einfacher verstehen, wenn wir mit fol-
gender naiven Lösung beginnen:
<html>.*?<head>.*?<title>.*?</title>
.*?</head>.*?<body[^>]*>.*?</body>.*?</html>
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenum-
bruch
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Wenn Sie diese Regex mit einer korrekten HTML-Datei testen, funktioniert sie wunder-
bar. ‹.*?› springt über alles andere hinweg, weil wir Punkt passt zu Zeilenumbruch akti-
viert haben. Der genügsame Stern stellt sicher, dass die Regex immer nur ein Zeichen
weitergeht und jedes Mal prüft, ob das nächste Tag passt. Die Rezepte 2.4 und 2.13
erläutern das genauer.
Aber wenn der Ausgangstext nicht alle diese HTML-Tags besitzt, kommen Sie mit dieser
Regex in Schwierigkeiten. Am schlimmsten ist es, wenn </html> fehlt.
Stellen Sie sich vor, die Regex-Engine hat alle vorigen Tags gefunden und ist nun damit
beschäftigt, das letzte ‹.*?› zu erweitern. Da ‹</html>› nie passen kann, wird ‹.*?›
immer weiter bis zum Ende der Datei expandiert. Ist das Ende der Datei erreicht, schlägt
die Regex fehl.
Aber das ist noch nicht das Ende der Geschichte. Alle anderen sechs ‹.*?› haben sich
eine Backtracking-Position gemerkt, die es ihnen erlaubt, weiter zu wachsen. Wenn das
letzte ‹.*?› erfolglos war, wird das vorherige erweitert, das dann Schritt für Schritt
</body> findet. Der gleiche Text war schon vorher durch das literale ‹</body>› in der
Regex gefunden worden. Dieses ‹.*?› wird aber auch bis zum Ende der Datei erweitert,
ebenso wie alle vorherigen genügsamen Punkte. Erst wenn das erste ‹.*?› das Ende der
Datei erreicht, wird die Regex-Engine erkennen, dass die Suche erfolglos war.
Dieser reguläre Ausdruck hat im schlimmsten Fall eine Komplexität von O(n7), also die
Länge des Ausgangstexts in siebter Potenz. Es gibt sieben genügsame Punkte, die poten-
ziell bis zum Ende der Datei erweitert werden. Wenn die Datei doppelt so groß ist, kann
die Regex 128 Mal mehr Schritte brauchen, um zu erkennen, dass sie nicht passt.
Wir nennen das katastrophales Backtracking. Es wird so viel Backtracking durchgeführt,
dass die Regex entweder ewig läuft oder Ihre Anwendung zum Absturz bringt. Manche
Regex-Implementierungen sind schlau und brechen solche aus dem Ruder laufenden
Versuche ab, aber selbst dann wird die Regex für die Performance Ihrer Anwendung ver-
nichtend sein.

2.15 Aus dem Ruder laufende Wiederholungen verhindern | 79


Das katastrophale Backtracking ist eine Form eines Phänomens, das als
kombinatorische Explosion bekannt ist. Viele orthogonale Bedingungen
treffen zusammen, und alle Kombinationen müssen ausprobiert werden.
Sie könnten auch sagen, dass die Regex ein kartesisches Produkt der ver-
schiedenen Wiederholungsoperatoren ist.

Die Lösung ist, atomare Gruppen zu nutzen, um das überflüssige Backtracking zu unter-
binden. Es gibt für das sechste ‹.*?› keine Notwendigkeit, sich weiter auszudehnen,
wenn ‹</body>› gefunden wurde. Wird ‹</html>› nicht gefunden, wird auch ein Erwei-
tern des sechsten genügsamen Punkts kein schließendes html-Tag aus dem Hut zaubern
können.
Damit sich ein Quantor-Regex-Token nicht weiter ausdehnt, wenn das darauffolgende
Element passt, stecken Sie beides – den Quantor-Teil der Regex und das folgende Ele-
ment – zusammen in eine atomare Gruppe: ‹(?>.*?</body>)›. Jetzt verwirft die Regex-
Engine alle Positionen für ‹.*?</body>›, wenn ‹</body>› gefunden wird. Wird später
‹</html>› nicht gefunden, hat die Regex-Engine schon den Teil ‹.*?</body>› vergessen,
und es gibt keine zusätzlichen Erweiterungen.
Wenn wir das Gleiche für alle anderen ‹.*?› in der Regex machen, wird sich keine von
ihnen erweitern. Obwohl es immer noch sieben genügsame Punkte in der Regex gibt,
werden sie sich nie gegenseitig überlappen. Damit verringert sich die Komplexität des
regulären Ausdrucks auf O(n), ist also linear im Verhältnis zur Länge des Ausgangstexts.
Ein regulärer Ausdruck kann nie effizienter sein.

Variationen
Möchten Sie wirklich einmal ein katastrophales Backtracking beobachten, wenden Sie
die Regex ‹(x+x+)+y› auf den Text xxxxxxxxxx an. Wenn schnell einen Misserfolg gemel-
det wird, fügen Sie dem Text ein x hinzu. Wiederholen Sie das so lange, bis die Regex
sehr langsam wird oder Ihre Anwendung abstürzt. Es werden nicht viele zusätzliche x
benötigt werden, sofern Sie nicht Perl nutzen.
Von den in diesem Buch behandelten Regex-Varianten ist nur Perl in der Lage, zu erken-
nen, dass der reguläre Ausdruck zu komplex ist, um anschließend die Suche ohne einen
Crash abzubrechen.
Die Komplexität dieser Regex ist O(2n). Wenn ‹y› nicht gefunden wird, wird die Regex-
Engine alle möglichen Wiederholungspermutationen für jedes ‹x+› und die sie enthal-
tende Gruppe durchprobieren. Eine solche Permutation kann so sein, dass ‹x+› auf xxx
passt, das zweite ‹x+› auf x und die Gruppe dann drei weitere Male wiederholt wird,
wobei jedes ‹x+› zu x passt. Bei zehn x gibt es 1.024 solcher Permutationen. Wenn wir die
Anzahl auf 32 erhöhen, kommen wir auf über 4 Milliarden Permutationen. Damit wird
jede Regex-Engine straucheln, sofern sie keine Sicherheitssysteme besitzt, mit deren Hilfe
sie die Suche abbrechen und melden kann, dass sie zu kompliziert ist.

80 | Kapitel 2: Grundlagen regulärer Ausdrücke


In diesem Fall lässt sich der nicht sehr sinnhafte reguläre Ausdruck leicht umschreiben zu
‹xx+y›, womit genau die gleichen Übereinstimmungen mit linearem Zeitaufwand gefun-
den werden. In der Praxis ist die Lösung aber nicht so augenscheinlich, wenn die Regexes
komplizierter sind.
Im Prinzip müssen Sie aufpassen, ob zwei oder mehr Teile des regulären Ausdrucks auf
den gleichen Text passen können. In diesen Fällen brauchen Sie eventuell atomare Grup-
pen, um sicherzustellen, dass die Regex-Engine nicht alle Möglichkeiten ausprobiert, den
Text zwischen den beiden Teilen der Regex aufzuteilen. Testen Sie Ihre Regex immer mit
(langen) Testtexten, in denen sich Abschnitte befinden, die zwar teilweise zur Regex pas-
sen, aber eben nicht ganz.

Siehe auch
Rezepte 2.13 und 2.14.

2.16 Etwas auf Übereinstimmung prüfen, ohne es


dem Gesamtergebnis hinzuzufügen
Problem
Finden eines beliebigen Worts, das zwischen einem Paar HTML-Bold-Tags steht, ohne
die Tags in das Regex-Suchergebnis mit aufzunehmen. Wenn der Text zum Beispiel
Meine <b>Katze</b> ist flauschig lautet, soll als Ergebnis nur Katze herauskommen.

Lösung
(?<=<b>)\w+(?=</b>)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby 1.9
JavaScript und Ruby 1.8 unterstützen das Lookahead ‹(?=</b>)›, aber nicht das Look-
behind ‹(?<=<b>)›.

Diskussion
Lookaround
Die vier Arten von Lookaround-Gruppen, die von modernen Regex-Varianten angeboten
werden, haben die gemeinsame Eigenschaft, den Text wieder aufzugeben, der von dem
Regex-Teil innerhalb des Lookaround gefunden wurde. Im Prinzip prüfen Lookarounds
also, ob ein bestimmter Text gefunden werden kann, ohne ihn im weiteren Verlauf zu
berücksichtigen.

2.16 Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen | 81


Lookarounds, die rückwärts schauen, werden als Lookbehinds bezeichnet. Das ist das
einzige Konstrukt in regulären Ausdrücken, das den Text von rechts nach links abarbei-
tet statt von links nach rechts. Die Syntax für positive Lookbehinds ist ‹(?<=Text)›. Die
vier Zeichen ‹(?<=› bilden die öffnende Klammer. Es hängt von der genutzten Regex-
Variante ab, was Sie innerhalb der Klammern unterbringen können (hier durch Text dar-
gestellt). Aber einfacher literaler Text, wie zum Beispiel ‹(?<=<b>)›, funktioniert immer.
Beim Lookbehind wird geprüft, ob der Text innerhalb der Lookbehind-Klammern direkt
links von der Position im Text vorhanden ist, an der sich die Regex-Engine gerade befin-
det. Wenn Sie ‹(?<=<b>)› auf Meine <b>Katze</b> ist flauschig anwenden, wird das
Lookbehind erst dann erfolgreich sein, wenn der reguläre Ausdruck versucht, den Buch-
staben K im Ausgangstext zu überprüfen. Die Regex-Engine steigt dann in die Look-
behind-Gruppe ein und erfährt damit, dass sie nach links schauen soll. ‹<b>› passt links
des K. Die Engine verlässt die Lookbehind-Gruppe wieder und verwirft jeglichen Text,
der dadurch gefunden worden war. Anders ausgedrückt: Der Inhalt des gefundenen
Texts ist wieder dort, wo die Engine ihn vor der Lookbehind-Gruppe zurückgelassen
hatte. In diesem Fall ist der bisher gefundene Text noch leer. Durch das Lookbehind wird
nur sichergestellt, dass ‹<b>› gefunden werden kann, aber das (positive) Ergebnis wird
nicht in das Suchergebnis übernommen. Lookaround-Konstrukte werden daher als Zusi-
cherungen der Länge null bezeichnet.
Nachdem das Lookbehind erfolgreich war, wird versucht, mit ‹\w+› eines oder mehrere
Wortzeichen zu finden. Das passt auf Katze. ‹\w+› ist nicht innerhalb eines Lookaround
oder einer Gruppe, daher wird der Text Katze normal gefunden. Wir sagen, dass ‹\w+› zu
Katze passt und es konsumiert, während Lookarounds zwar auf etwas passen können,
aber niemals etwas konsumieren.
Ein Lookaround, das vorwärts schaut, also in die gleiche Richtung, in die der reguläre
Ausdruck den Text normalerweise abarbeitet, wird als Lookahead bezeichnet. Look-
aheads werden von allen Regex-Varianten in diesem Buch gleichermaßen unterstützt.
Die Syntax für positive Lookaheads ist ‹(?=Regex)›. Die drei Zeichen ‹(?=› stellen die öff-
nende Klammer der Gruppe dar. Sie können in einem Lookahead alle Elemente regulärer
Ausdrücke nutzen.
Wenn das ‹\w+› in ‹(?<=<b>)\w+(?=</b>)› die Katze in Meine <b>Katze</b> ist flauschig
gefunden hat, steigt die Regex-Engine in das Lookahead ein. An dieser Stelle ist die ein-
zige Besonderheit des Lookahead, dass sich die Regex-Engine merkt, welchen Teil des
Texts sie bisher schon gefunden hat, und ihn mit dem Lookahead verbindet. Dann passt
die Regex innerhalb des Lookahead und somit auch das gesamte Lookahead. Danach
verwirft die Regex-Engine den vom Lookahead gefundenen Text, indem sie den vor dem
Lookahead gefundenen Text wieder zurückholt. Jetzt ist der bisher gefundene Text
Katze. Da dies auch das Ende unseres regulären Ausdrucks ist, wird Katze damit zum
abschließenden Suchergebnis.

82 | Kapitel 2: Grundlagen regulärer Ausdrücke


Negatives Lookaround
‹(?!regex)›, mit einem Ausrufezeichen statt mit einem Gleichheitszeichen, ist ein negati-
ves Lookahead. Negative Lookaheads funktionieren wie positive Lookaheads. Allerdings
passen sie genau dann, wenn die Regex innerhalb des Lookahead gerade nicht passt.
Das Finden von Übereinstimmungen ist identisch. Die Engine merkt sich das aktuelle
Suchergebnis, wenn sie das negative Lookahead betritt, und versucht, die Regex inner-
halb des Lookahead normal zu finden. Wenn der Unterausdruck passt, ist das Look-
ahead falsch, und die Regex-Engine arbeitet per Backtracking weiter. Wird der
Unterausdruck aber nicht gefunden, holt die Engine das zwischengespeicherte Sucher-
gebnis hervor und fährt mit der Verarbeitung der Regex fort.
Genauso gibt es (?<!text) als negatives Lookbehind. Negative Lookbehinds passen dann,
wenn keine der Alternativen innerhalb des Lookbehind – von der Position der Regex
innerhalb des Texts aus rückwärts gesehen – passt.

Unterschiedliche Lookbehind-Ebenen
Lookahead ist einfach. Alle Regex-Varianten, die in diesem Buch behandelt werden, las-
sen einen kompletten regulären Ausdruck innerhalb des Lookahead zu. Sie können sogar
andere Lookahead- und Lookbehind-Gruppen innerhalb eines Lookahead unterbringen.
Das bringt zwar vielleicht Ihre Synapsen zur Verzweiflung, aber die Regex-Engine wird
alles ganz wunderbar ausführen.
Beim Lookbehind ist das etwas anderes. Software zur Verarbeitung regulärer Ausdrücke
wurde immer dafür entworfen, den Text nur von links nach rechts zu durchsuchen. Eine
Rückwärtssuche ist häufig eher als Hack implementiert – die Regex-Engine bestimmt,
wie viele Zeichen Sie innerhalb des Lookbehind nutzen, springt so weit zurück und ver-
gleicht dann den Text im Lookbehind mit dem entsprechenden Bereich des Ausgangs-
texts von links nach rechts.
Aus diesem Grund erlaubten die frühen Implementierungen innerhalb einer Lookbehind-
Gruppe auch nur literale Texte mit fester Länge. Perl, Python und Ruby 1.9 gehen einen
Schritt weiter und erlauben auch Alternation und Zeichenklassen, um mehrere Strings
fester Länge im Lookbehind unterzubringen. So etwas wie ‹(?<=eins|zwei|drei|
zweiundvierzig|R[äi]nder)› ist schon alles, was sie verarbeiten können.
Intern wandeln Perl, Python und Ruby 1.9 dies in sechs Lookbehind-Tests um. Zuerst
springen sie vier Zeichen zurück, um auf ‹eins|zwei|drei› zu prüfen, dann sechs Zei-
chen, um auf ‹Ränder|Rinder› zu prüfen und schließlich 14 Zeichen, um auf
‹zweiundvierzig› zu testen.
PCRE und Java gehen beim Lookbehind noch einen Schritt weiter. Sie erlauben in der
Gruppe reguläre Ausdrücke mit begrenzter Länge. Das heißt, Sie können alle Tokens
nutzen, außer den (potenziell) unendlichen Quantoren ‹*›, ‹+› und ‹{42,}›. Intern
berechnen PCRE und Java die minimale und maximale Länge des Texts, der eventuell
von der Regex im Lookbehind gefunden werden könnte. Dann springen sie die minimale

2.16 Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen | 83


Anzahl an Zeichen zurück und wenden die Regex im Lookbehind von links nach rechts
an. Wenn es da keine Übereinstimmung gibt, springen sie ein weiteres Zeichen zurück
und versuchen es erneut, bis das Lookbehind entweder eine Übereinstimmung erzielt hat
oder die maximale Anzahl an Zeichen erreicht wurde.
Wenn das für Sie alles ziemlich ineffizient klingt, liegen Sie richtig. Lookbehinds sind
sehr praktisch, aber Weltrekorde stellen Sie damit nicht auf. Weiter unten stellen wir
noch eine Lösung für JavaScript und Ruby 1.8 vor, die ja gar keine Lookbehinds unter-
stützen. Diese Lösung ist dafür aber viel effizienter als die Verwendung von Look-
behinds.
Die Regex-Engine im .NET Framework ist die weltweit einzige,2 die tatsächlich einen
vollständigen regulären Ausdruck von rechts nach links anwenden kann. .NET erlaubt es
Ihnen, beliebige Regexes innerhalb von Lookbehinds zu verwenden, die dann auch wirk-
lich von rechts nach links angewandt werden. Sowohl der reguläre Ausdruck innerhalb
des Lookbehind als auch der Ausgangstext werden von rechts nach links durchgearbei-
tet.

Den gleichen Text zwei Mal finden


Wenn Sie am Anfang der Regex ein Lookbehind oder am Ende ein Lookahead verwen-
den, wollen Sie schlussendlich, dass vor oder nach der Regex-Übereinstimmung etwas
steht, ohne dass es im gefundenen Text enthalten ist. Wenn Sie ein Lookaround mitten
in Ihrem regulären Ausdruck nutzen, können Sie mehrere Überprüfungen des gleichen
Texts durchführen.
In „Variantenspezifische Features“ auf Seite 36 (ein Abschnitt aus Rezept 2.3) haben wir
gezeigt, wie man Zeichenklassen voneinander subtrahieren kann, um eine Thai-Ziffer zu
finden. Nur .NET und Java unterstützen das Subtrahieren von Zeichenklassen.
Ein Zeichen ist eine Thai-Ziffer, wenn es sich sowohl um ein Thai-Zeichen (beliebiger
Art) als auch um eine Ziffer (aus einem beliebigen Schriftsystem) handelt. Mit einem
Lookahead können Sie beide Anforderungen für das gleiche Zeichen prüfen:
(?=\p{Thai})\p{N}
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl, Ruby 1.9
Diese Regex funktioniert nur mit den drei Varianten, die Unicode-Schriftsysteme unter-
stützen, wie wir in Rezept 2.7 erklärt haben. Aber ein Lookahead zu nutzen, um das glei-
che Zeichen mehr als einmal zu prüfen, funktioniert mit allen in diesem Buch
behandelten Varianten.

2 Die Regex-Engine des RegexBuddy erlaubt ebenfalls eine vollständige Regex innerhalb eines Lookbehind, aber
es gibt (noch) kein Feature, das RegexOptions.RightToLeft von .NET gleicht, um den gesamten regulären Aus-
druck umzukehren.

84 | Kapitel 2: Grundlagen regulärer Ausdrücke


Sucht die Regex-Engine nach ‹(?=\p{Thai})\p{N}›, nutzt sie das Lookahead an jeder
Position im String, an der sie eine Überprüfung beginnt. Wenn das Zeichen an der Posi-
tion nicht aus dem Thai-Schriftsystem stammt (‹\p{Thai}› also keine Übereinstimmung
zeigt), schlägt das Lookahead fehl. Damit ist die Suche an dieser Stelle komplett erfolg-
los, und die Regex-Engine muss beim nächsten Zeichen weitermachen.
Wenn die Regex ein Thai-Zeichen erreicht, passt ‹\p{Thai}›. Damit passt auch das
Lookaround ‹(?=\p{Thai})›. Verlässt die Engine das Lookaround, greift sie wieder auf
den vorherigen Status der gefundenen Zeichen zurück. In diesem Fall sind noch keine
Zeichen gefunden. Als Nächstes kommt ‹\p{N}› dran. Da das Lookahead seine gefunde-
nen Zeichen verworfen hat, wird ‹\p{N}› mit dem gleichen Zeichen verglichen, für das
‹\p{Thai}› bereits gültig war. Wenn dieses Zeichen die Unicode-Eigenschaft Number
besitzt, passt auch ‹\p{N}›. Da sich ‹\p{N}› nicht innerhalb eines Lookaround befindet,
konsumiert es dieses Zeichen, und wir haben eine Thai-Ziffer gefunden.

Ein Lookaround ist atomar


Wenn die Regex-Engine eine Lookaround-Gruppe verlässt, verwirft sie den Text, der
durch das Lookaround gefunden wurde. Damit werden auch alle Backtracking-Positio-
nen, die durch Alternation oder Quantoren innerhalb des Lookaround entstanden,
gelöscht. Damit ist ein Lookahead oder Lookbehind im Endeffekt atomar. Rezept 2.15
erläutert, was atomare Gruppen sind.
In den meisten Situationen ist die atomare Natur von Lookarounds uninteressant. Ein
Lookaround ist nicht mehr als eine Zusicherung, dass geprüft wird, ob die Regex inner-
halb des Lookaround passt. Auf wie vielen verschiedenen Wegen sie passen könnte, ist
unwichtig, und sie konsumiert auch keine Teile des Ausgangstexts.
Aber die atomare Natur kommt dann ins Spiel, wenn Sie einfangende Gruppen innerhalb
von Lookaheads (und Lookbehinds, wenn Ihre Regex-Variante das zulässt) nutzen.
Obwohl ein Lookahead keinen Text konsumiert, wird sich die Regex-Engine merken,
welcher Teil des Texts durch eine einfangende Gruppe innerhalb des Lookahead passte.
Wenn sich ein Lookahead am Ende der Regex befindet, haben Sie doch einfangende
Gruppen mit gefundenem Text, der durch den regulären Ausdruck selbst gar nicht
gefunden wurde. Befindet sich das Lookahead in der Mitte der Regex, kann es sein, dass
Sie einfangende Gruppen haben, die überlappende Teile des Texts gefunden haben.
Die einzige Situation, in der die atomare Natur eines Lookaround das abschließende
Suchergebnis einer Regex verändern kann, ergibt sich durch das Verwenden einer Rück-
wärtsreferenz auf eine einfangende Gruppe, die innerhalb des Lookaround „definiert“,
aber außerhalb verwendet wird. Schauen Sie sich diesen regulären Ausdruck an:
(?=(\d+))\w+\1
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

2.16 Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen | 85


Auf den ersten Blick würden Sie vielleicht vermuten, dass diese Regex auf 123x12 passt.
‹\d+› würde die 12 in die erste einfangende Gruppe übernehmen, dann würde ‹\w+› auf
3x passen, und schließlich würde ‹\1› wieder zu 12 passen.
Aber das wird so nie passieren. Der reguläre Ausdruck betritt das Lookaround und die
einfangende Gruppe. Das gierige ‹\d+› passt auf 123. Diese Übereinstimmung wird für
die erste einfangende Gruppe gespeichert. Dann verlässt die Engine das Lookahead, setzt
den gefundenen Text zurück auf den Wert vor der Gruppe – also auf den Anfang des
Strings – und verwirft die Backtracking-Positionen, die sie sich für das gierige Plus
gemerkt hat, aber sie merkt sich weiterhin die 123, die für die erste einfangende Gruppe
gespeichert wurden.
Jetzt wird das gierige ‹\w+› auf den Anfang des Strings angesetzt und frisst gleich den
ganzen Wert 123x12 auf. ‹\1› bezieht sich auf 123, kann das aber nun am Ende des Strings
nicht mehr finden. ‹\w+› geht ein Zeichen zurück. ‹\1› findet wieder nichts. ‹\w+› geht
noch mehr Schritte zurück, bis es alles außer der ersten 1 im Text wieder hergegeben hat.
Aber auch jetzt noch findet ‹\1› keine Übereinstimmung nach der ersten 1.
Die abschließende 12 könnte zu ‹\1› passen, wenn die Regex-Engine zum Lookahead
zurückkehren und den Wert 123 für 12 aufgeben könnte. Das tut sie aber nicht.
Die Regex-Engine hat nun keine weiteren Backtracking-Positionen mehr zur Verfügung.
‹\w+› ist ganz zurückgegangen, und das Lookaround hat ‹\d+› dazu gezwungen, seine
Backtracking-Positionen aufzugeben. Damit schlägt die Suche fehl.

Lösung ohne Lookbehind


All die schönen Erklärungen und Hinweise helfen Ihnen leider nicht, wenn Sie Python
oder JavaScript nutzen, da es dort kein Lookbehind gibt. Es gibt keine Möglichkeit, das
Problem so, wie es beschrieben wurde, mit diesen Regex-Varianten zu lösen. Aber es gibt
einen anderen Weg: die Verwendung von einfangenden Gruppen. Diese Version funk-
tioniert auch mit den anderen Regex-Varianten:
(<b>)(\w+)(?=</b>)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Statt eines Lookbehind haben wir eine einfangende Gruppe für das öffnende Tag ‹<b>›
genutzt. Zudem haben wir den Teil der Übereinstimmung, an dem wir interessiert sind –
das ‹\w+› – ebenfalls in eine einfangende Gruppe gesteckt.
Wenn Sie diesen regulären Ausdruck auf Meine <b>Katze</b> ist flauschig anwenden,
wird das komplette Suchergebnis <b>Katze sein. Die erste einfangende Gruppe wird das
<b> enthalten, die zweite das Wort Katze.
Wenn es darum geht, nur Katze zu erhalten (das Wort zwischen den <b>-Tags), können
Sie nun einfach den von der zweiten einfangenden Gruppe ermittelten Text verwenden,
statt auf das vollständige Suchergebnis zuzugreifen.

86 | Kapitel 2: Grundlagen regulärer Ausdrücke


Möchten Sie eigentlich nur das Wort zwischen den Tags per Suchen und Ersetzen aus-
tauschen, verwenden Sie einfach eine Rückwärtsreferenz auf die erste einfangende
Gruppe, um das öffnende Tag in den Ersetzungstext einzufügen. In dem hier genutzten
Beispiel brauchen Sie die erste einfangende Gruppe eigentlich nicht, da das öffnende Tag
immer das gleiche ist. Aber wenn es variabel ist, fügt die einfangende Gruppe genau das
ein, das gefunden wurde. Rezept 2.21 erklärt das genauer.
Wenn Sie schließlich wirklich ein Lookbehind simulieren wollen, können Sie das über
zwei reguläre Ausdrücke machen. Zunächst suchen Sie nach Ihrer Regex ohne Look-
behind. Wenn sie passt, kopieren Sie den Teil des Ausgangstexts vor der Übereinstim-
mung in eine neue String-Variable. Führen Sie nun den Test, den Sie eigentlich innerhalb
des Lookbehind machen wollten, mit einer zweiten Regex durch, wobei Sie einen Ende-
Anker anfügen (‹\z› oder ‹$›). Der Anker stellt sicher, dass die Übereinstimmung der
zweiten Regex am Ende des Strings liegt. Da Sie den String dort abgeschnitten haben, wo
die erste Regex passte, ist auf diese Weise dafür gesorgt, dass die zweite Übereinstim-
mung direkt links von der ersten liegt.
In JavaScript könnten Sie das mit folgendem Code erreichen:
var mainregexp = /\w+(?=<\/b>)/;
var lookbehind = /<b>$/;
if (match = mainregexp.exec("Meine <b>Katze</b> ist flauschig")) {
// Wort vor einem schließenden Tag </b> gefunden
var potentialmatch = match[0];
var leftContext = match.input.substring(0, match.index);
if (lookbehind.exec(leftContext)) {
// Lookbehind passte:
// potentialmatch steht zwischen <b>-Tags
} else {
// Lookbehind passte nicht: potentialmatch ist nicht das Gewünschte
}
} else {
// Kein Wort vor einem schließenden Tag </b> gefunden
}

Siehe auch
Rezepte 5.5 und 5.6.

2.17 Abhängig von einer Bedingung eine von zwei


Alternativen finden
Problem
Erstellen eines regulären Ausdrucks, der eine durch Kommata getrennte Liste mit den
Worten eins, zwei und drei findet. Jedes Wort kann beliebig häufig vorkommen, muss
aber mindestens ein Mal vorhanden sein.

2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden | 87
Lösung
\b(?:(?:(eins)|(zwei)|(drei))(?:,|\b)){3,}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))
Regex-Optionen: Keine
Regex-Varianten: .NET, JavaScript, PCRE, Perl, Python
Java und Ruby unterstützen keine bedingten Ausdrücke. Wenn Sie in Java oder Ruby
programmieren (oder in einer anderen Sprache), können Sie den regulären Ausdruck
ohne die Bedingungen nutzen und mit zusätzlichem Code prüfen, ob jede der drei einfan-
genden Gruppen etwas enthält.
\b(?:(?:(eins)|(zwei)|(drei))(?:,|\b)){3,}
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
.NET, JavaScript, PCRE, Perl und Python unterstützen bedingte Ausdrücke mithilfe num-
merierter einfangender Gruppen. ‹(?(1)then|else)› ist ein bedingter Ausdruck, der
prüft, ob die erste einfangende Gruppe schon etwas gefunden hat. Wenn dem so ist, ver-
sucht die Regex-Engine, das ‹then› zu finden. Enthält die einfangende Gruppe bisher
noch keine Übereinstimmung, wird versucht, den ‹else›-Teil zu finden.
Die Klammern, das Fragezeichen und der vertikale Balken gehören alle zur Syntax für
bedingte Ausdrücke. Sie haben an dieser Stelle nicht ihre normale Bedeutung. Sie können
für die ‹then›- und ‹else›-Teile beliebige Regex-Ausdrücke nutzen. Die einzige Ein-
schränkung besteht darin, dass Sie bei der Verwendung von Alternationen eine Gruppe
nutzen müssen, um sie zusammenzuhalten. Im bedingten Ausdruck selbst ist nur ein ein-
ziger vertikaler Balken direkt erlaubt.
Wenn Sie möchten, können Sie einen der beiden Teile ‹then› oder ‹else› auch weglassen.
Die leere Regex findet immer eine Übereinstimmung der Länge null. Die Lösung für dieses
Rezept nutzt drei bedingte Ausdrücke, die alle einen leeren ‹then›-Teil haben. Wenn die
einfangende Gruppe etwas enthält, ist der bedingte Ausdruck direkt erfolgreich.
Im ‹else›-Teil steht ein leeres negatives Lookahead ‹(?!)›. Da die leere Regex immer
passt, schlägt ein negatives Lookahead mit einer leeren Regex immer fehl. Damit schlägt
auch der bedingte Ausdruck ‹(?(1)|(?!))› immer fehl, wenn die erste einfangende
Gruppe nichts Passendes enthält.
Indem jede der drei erforderlichen Alternativen in ihrer eigenen einfangenden Gruppe
untergebracht ist, können wir am Ende der Regex drei bedingte Ausdrücke nutzen, um
zu prüfen, ob alle einfangenden Gruppen etwas enthalten.
.NET unterstützt auch benannte bedingte Ausdrücke. ‹(?(Name)then|else)› prüft, ob die
benannte einfangende Gruppe Name schon etwas enthält.

88 | Kapitel 2: Grundlagen regulärer Ausdrücke


Um besser verstehen zu können, wie bedingte Ausdrücke funktionieren, wollen wir uns
den regulären Ausdruck ‹(a)?b(?(1)c|d)› anschauen. Dieser ist im Prinzip nichts ande-
res als eine komplizierte Form von ‹abc|bd›.
Beginnt der Ausgangstext mit einem a, wird dieses in der ersten einfangenden Gruppe
gesichert. Wenn nicht, enthält diese Gruppe auch nichts. Es ist wichtig, dass sich das
Fragezeichen außerhalb der einfangenden Gruppe befindet, da damit die gesamte
Gruppe optional wird. Wenn es kein a gibt, wird die Gruppe null Mal wiederholt und
erhält auch keine Chance, etwas anderes einzufangen. Sie kann keinen String der Länge
null abspeichern.
Wenn Sie ‹(a?)› nutzen, nimmt die Gruppe immer an den Übereinstimmungsversuchen
teil. Es gibt dann nach der Gruppe keinen Quantor, daher wird sie genau ein Mal ausge-
führt. Die Gruppe wird entweder ein a oder gar nichts einfangen.
Unabhängig davon, ob ein ‹a› gefunden wurde oder nicht, ist das nächste Token ein ‹b›.
Dann kommt der bedingte Ausdruck. Wenn die einfangende Gruppe etwas einfangen
konnte – selbst einen String der Länge null (was hier nicht geht) –, wird mit ‹c› weiterge-
macht. Wenn nicht, nutzt die Regex-Engine stattdessen ‹d›.
Einfach gesagt, passt ‹(a)?b(?(1)c|d)› entweder zu ab, gefolgt von c, oder zu b, gefolgt
von d.
Bei .NET, PCRE und Perl, aber nicht bei Python, können bedingte Ausdrücke auch
Lookarounds nutzen. ‹(?(?=if)then|else)› prüft zunächst ‹(?=if)› als normales Look-
ahead. Rezept 2.16 beschreibt dazu die Details. Wenn das Lookaround erfolgreich war,
wird mit dem ‹then›-Teil weitergemacht, ansonsten mit dem ‹else›-Teil. Da ein Look-
around die Länge null hat, werden die ‹then›- und ‹else›-Regexes an der gleichen Posi-
tion im Ausgangstext gesucht – egal ob ‹if› erfolgreich war oder fehlschlug.
Sie können im bedingten Ausdruck Lookbehinds statt Lookaheads nutzen. Negative
Lookarounds sind ebenfalls möglich, auch wenn wir das eher nicht empfehlen, da man
dann nur mit den verschiedenen Zweigen der bedingten Ausführung durcheinandergerät.

Ein bedingter Ausdruck, der Lookarounds nutzt, kann auch ohne den
bedingten Ausdruck mittels ‹(?=if)then|(?!if)else› geschrieben werden.
Wenn das positive Lookahead erfolgreich ist, wird ‹then› getestet. Wenn
es keinen Erfolg hatte, springt die Alternation ein. Das negative Look-
ahead führt den gleichen Test durch. Wenn ‹if› erfolglos geprüft wird –
was sichergestellt ist, weil ‹(?=if)› fehlschlug –, ist es erfolgreich. Daher
wird der ‹else›-Zweig getestet. Setzt man das Lookahead in einen beding-
ten Ausdruck, spart man allerdings Zeit, weil ‹if› damit nur ein Mal aus-
getestet wird.

Siehe auch
Rezepte 2.9 und 2.16.

2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden | 89
2.18 Kommentare für einen regulären Ausdruck
Problem
‹\d{4}-\d{2}-\d{2}› passt zu einem Datum im Format yyyy-mm-dd, allerdings ohne eine
Überprüfung der Zahlen auf Sinnhaftigkeit. Solch ein einfacher regulärer Ausdruck ist
dann passend, wenn Sie wissen, dass Ihre Daten keine ungültigen Werte enthalten.
Durch Hinzufügen von Kommentaren zum regulären Ausdruck soll deutlich gemacht
werden, was jeder Teil des Ausdrucks tut.

Lösung
\d{4} # Jahr
- # Trennzeichen
\d{2} # Monat
- # Trennzeichen
\d{2} # Tag
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion
Freiform-Modus
Reguläre Ausdrücke können schnell unübersichtlich und schwer verständlich werden.
Analog zum Kommentieren von Quellcode sollten Sie auch alle regulären Ausdrücke
kommentieren, die nicht ganz trivial sind.
Alle in diesem Buch behandelten Regex-Varianten, mit Ausnahme von JavaScript, bieten
eine alternative Syntax für reguläre Ausdrücke an, die es sehr leicht macht, Ihre regulären
Ausdrücke sauber zu kommentieren. Sie können diese Syntax aktivieren, indem Sie die
Freiform-Option einschalten. In den verschiedenen Programmiersprachen hat sie aller-
dings unterschiedliche Namen.
In .NET setzen Sie die Option RegexOptions.IgnorePatternWhitespace. In Java übergeben
Sie die Option Pattern.COMMENTS. Python erwartet re.VERBOSE. PHP, Perl und Ruby nut-
zen die Option /x.
Schaltet man den Freiform-Modus ein, hat das zwei Effekte. Das Hash-Symbol (#) wird
(außerhalb von Zeichenklassen) zu einem Metazeichen. Mit dem Hash wird ein Kom-
mentar eingeleitet, der bis zum Ende der Zeile oder bis zum Ende der Regex läuft (je
nachdem, was zuerst eintritt). Das Hash und alles danach wird von der Regex-Engine
schlicht ignoriert. Um ein literales Hash-Zeichen zu finden, stecken Sie es entweder in
eine Zeichenklasse ‹[#]› oder maskieren es per ‹\#›.

90 | Kapitel 2: Grundlagen regulärer Ausdrücke


Der andere Effekt ist, dass Leerraum – also Leerzeichen, Tabs und Zeilenumbrüche –
ebenfalls außerhalb von Zeichenklassen ignoriert werden. Um ein literales Leerzeichen
zu finden, müssen Sie es entweder innerhalb einer Zeichenklasse unterbringen ‹[z]›
oder es maskieren ‹\z›. Wenn Sie sich Sorgen um die Lesbarkeit machen, können Sie
stattdessen auch die hexadezimale Markierung ‹\x20› oder die Unicode-Maskierung
‹\u0020› bzw. ‹\x{0020}› nutzen. Um ein Tab-Zeichen zu finden, nutzen Sie ‹\t›, für
Zeilenumbrüche ‹\r\n› (Windows) oder ‹\n› (Unix/Linux/OS X).
Innerhalb von Zeichenklassen ändert sich durch den Freiform-Modus nichts. Eine Zei-
chenklasse ist ein einzelnes Token. Jegliche Whitespace-Zeichen oder Hashes innerhalb
von Zeichenklassen sind literale Zeichen und Bestandteile der Zeichenklasse. Sie können
diese Klassen nicht aufbrechen, um Kommentare darin unterzubringen.

Java hat Freiform-Zeichenklassen


Reguläre Ausdrücke würden ihrem Ruf nicht gerecht, wenn nicht wenigstens eine Vari-
ante inkompatibel zu den anderen wäre. In diesem Fall ist Java der Ausreißer.
In Java werden Zeichenklassen nicht als einzelnes Token verarbeitet. Wenn Sie den Frei-
form-Modus aktivieren, ignoriert Java Leerraum in Zeichenklassen, und Hashes begin-
nen auch dort einen Kommentar. Das bedeutet, dass Sie ‹[z]› und ‹[#]› nicht nutzen
können, um diese literalen Zeichen zu finden. Greifen Sie stattdessen auf ‹\u0020› und
‹\#› zurück.

Variationen
(?#Jahr)\d{4}(?#Trennzeichen)-(?#Monat)\d{2}-(?#Tag)\d{2}
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE, Perl, Python, Ruby
Wenn Sie aus irgendeinem Grund keine Freiformsyntax nutzen können oder wollen,
haben Sie immer noch die Möglichkeit, per ‹(?#Kommentar)› Kommentare einzufügen.
Alle Zeichen zwischen ‹(?#› und ‹)› werden ignoriert.
Leider ist JavaScript die einzige Variante in diesem Buch, die weder den Freiform-Modus
noch die hier beschriebene Kommentarsyntax kennt. Java unterstützt Letztere ebenfalls
nicht.
(?x)\d{4} # Jahr
- # Trennzeichen
\d{2} # Monat
- # Trennzeichen
\d{2} # Tag
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Wenn Sie den Freiform-Modus außerhalb des regulären Ausdrucks nicht einschalten
können, haben Sie die Möglichkeit, den Modus-Modifikator ‹(?x)› an den Anfang des

2.18 Kommentare für einen regulären Ausdruck | 91


regulären Ausdrucks zu setzen. Stellen Sie sicher, dass es vor dem ‹(?x)› keinen Leer-
raum gibt. Der Freiform-Modus beginnt erst mit dem Modus-Modifikator, jeglicher Leer-
raum vorher wird für die Regex berücksichtigt.
Modus-Modifikatoren werden detailliert in „Übereinstimmungen unabhängig von Groß-
und Kleinschreibung“ auf Seite 29, einem Abschnitt von Rezept 2.1, erläutert.

2.19 Literalen Text im Ersetzungstext nutzen


Problem
Suchen und Ersetzen des Suchergebnisses eines regulären Ausdrucks durch die acht lite-
ralen Zeichen $%\*$1\1.

Lösung
$%\*$$1\1
Ersetzungstextvarianten: .NET, JavaScript
\$%\\*\$1\\1
Ersetzungstextvariante: Java
$%\*\$1\\1
Ersetzungstextvariante: PHP
\$%\*\$1\\1
Ersetzungstextvariante: Perl
$%\*$1\\1
Ersetzungstextvarianten: Python, Ruby

Diskussion
Welche Zeichen wo im Ersetzungstext zu maskieren sind
Dieses Rezept zeigt Ihnen die verschiedenen Maskierungsregeln, die von den unter-
schiedlichen Ersetzungstextvarianten genutzt werden. Die einzigen beiden Zeichen, die
Sie im Ersetzungstext je maskieren müssen, sind das Dollarzeichen und der Backslash.
Die Maskierungszeichen sind ebenfalls das Dollarzeichen und der Backslash.
Das in diesem Beispiel genutzte Prozentzeichen und der Stern sind immer literale Zei-
chen, auch wenn ein davor gesetzter Backslash als Maskierungszeichen statt als literaler
Backslash behandelt werden kann. «$1» und/oder «\1» sind Rückwärtsreferenzen auf eine
einfangende Gruppe. Rezept 2.21 erklärt, welche Variante welche Syntax für Rückwärts-
referenzen nutzt.

92 | Kapitel 2: Grundlagen regulärer Ausdrücke


Anhand der Tatsache, dass dieses Problem fünf verschiedene Lösungen für sieben Erset-
zungstextvarianten hat, sieht man, dass es wirklich keinen Standard für die Ersetzungs-
textsyntax gibt.

.NET und JavaScript


.NET und JavaScript behandeln einen Backslash immer als literales Zeichen. Maskieren
Sie ihn niemals mit einem anderen Backslash, da Sie ansonsten im Ersetzungstext zwei
Backslashs vorfinden werden.
Ein allein stehendes Dollarzeichen ist ein literales Zeichen. Dollarzeichen müssen nur
dann maskiert werden, wenn ihnen eine Ziffer, ein Kaufmanns-Und, ein Gravis (Back-
tick), ein einfaches Anführungszeichen, ein Unterstrich, ein Pluszeichen oder ein weiteres
Dollarzeichen folgt. Um ein Dollarzeichen zu maskieren, nutzen Sie ein zweites Dollar-
zeichen. Sie können alle Dollarzeichen verdoppeln, wenn Sie das Gefühl haben, dass Ihr
Ersetzungstext dadurch lesbarer wird. Diese Lösung ist daher gleichwertig:
$$%\*$$1\1
Ersetzungstextvarianten: .NET, JavaScript
.NET fordert zudem, dass Dollarzeichen, auf die eine öffnende geschweifte Klammer
folgt, maskiert werden. «${Gruppe}» ist in .NET ein benannter Rückwärtsverweis. Java-
Script unterstützt diese nicht.

Java
In Java wird der Backslash genutzt, um Backslashs und Dollarzeichen im Ersetzungstext
zu maskieren. Alle literalen Backslashs und Dollarzeichen müssen maskiert werden.
Wenn Sie das nicht tun, wirft Java eine Exception.

PHP
PHP braucht für Backslashs, auf die eine Ziffer folgt, und für Dollarzeichen, auf die eine
Ziffer oder eine öffnende geschweifte Klammer folgt, eine Maskierung durch einen Back-
slash.
Ein Backslash maskiert auch andere Backslashs. So müssen Sie also «\\\\» schreiben, um
das Suchergebnis durch zwei literale Backslashs zu ersetzen. Alle andere Backslashs wer-
den als literale Backslashs behandelt.

Perl
Perl unterscheidet sich ein wenig von den anderen Ersetzungstextvarianten. Es hat
eigentlich gar keine Ersetzungstextvariante. Wo andere Programmiersprachen eine
besondere Logik haben, mit der die Routinen zum Suchen und Ersetzen Elemente substi-
tuieren, wie zum Beispiel «$1», gibt es in Perl einfach die normale Variablenersetzung. Im
Ersetzungstext müssen Sie alle literalen Dollarzeichen mit einem Backslash maskieren, so
wie Sie es in jedem normalen String in doppelten Anführungszeichen machen würden.

2.19 Literalen Text im Ersetzungstext nutzen | 93


Eine Ausnahme ist bei Perl, dass es die Syntax «\1» für Rückwärtsreferenzen unterstützt.
Daher müssen Sie einen Backslash, auf den eine Ziffer folgt, maskieren, wenn der Back-
slash ein Literal sein soll. Ein Backslash, dem ein Dollarzeichen folgt, muss auch maskiert
werden, um zu verhindern, dass der Backslash das Dollarzeichen maskiert.
Ein Backslash maskiert zudem einen anderen Backslash. So müssen Sie also «\\\\»
schreiben, um das Suchergebnis durch zwei literale Backslashs zu ersetzen. Alle anderen
Backslashs werden als literale Backslashs behandelt.

Python und Ruby


Das Dollarzeichen hat bei Python und Ruby keine besondere Bedeutung im Ersetzungs-
text. Backslashs müssen mit einem anderen Backslash maskiert werden, wenn ihnen ein
Zeichen folgt, das dem Backslash eine besondere Bedeutung zuweist.
Bei Python erzeugen «\1» bis «\9» und «\g<» Rückwärtsreferenzen. Diese Backslashs
müssen maskiert werden.
Bei Ruby müssen Sie einen Backslash, dem eine Ziffer, ein Kaufmanns-Und, ein Gravis
(Backtick), ein einfaches Anführungszeichen oder ein Pluszeichen folgt, maskieren.
In beiden Sprachen maskiert ein Backslash auch einen anderen Backslash. So müssen Sie
also «\\\\» schreiben, um das Suchergebnis durch zwei literale Backslashs zu ersetzen.
Alle andere Backslashs werden als literale Backslashs behandelt.

Weitere Maskierungsregeln für String-Literale


Denken Sie daran, dass wir uns in diesem Kapitel nur mit regulären Ausdrücken und
Ersetzungstexten befassen. Das nächste Kapitel behandelt die Programmiersprachen und
String-Literale.
Die weiter oben gezeigten Ersetzungstexte funktionieren, wenn die eigentliche String-
Variable, die Sie der Funktion replace() übergeben, diesen Text enthält. Wenn Ihre
Anwendung also ein Textfeld für den Anwender bereitstellt, damit er den Ersetzungstext
eingeben kann, zeigen diese Lösungen, was der Anwender einzugeben hätte, damit das
Suchen und Ersetzen wie gewünscht funktioniert. Probieren Sie Ihre Befehle zum Suchen
und Ersetzen mit RegexBuddy oder anderen Regex-Testprogrammen aus, werden die in
diesem Rezept angegebenen Ersetzungstexte wie gewünscht arbeiten.
Aber wenn Sie diese Texte so, wie sie sind, in Ihren Quellcode kopieren und nur Anfüh-
rungszeichen um sie herum einfügen, werden sie nicht funktionieren. String-Literale in
Programmiersprachen haben ihre eigenen Maskierungsregeln, und Sie müssen diesen
Regeln neben den Maskierungsregeln für den Ersetzungstext noch zusätzlich folgen. So
können am Ende durchaus eine ganze Menge Backslashs herauskommen.

Siehe auch
Rezept 3.14.

94 | Kapitel 2: Grundlagen regulärer Ausdrücke


2.20 Einfügen des Suchergebnisses in den Ersetzungstext
Problem
Suchen von URLs und Ersetzen durch HTML-Links, die auf die URL verweisen und die
URL selbst als Text für den Link nutzen. Für diese Übung soll eine URL durch „http:“
und alle darauffolgenden Zeichen, die kein Whitespace sind, definiert sein. So soll zum
Beispiel Besuchen Sie bitte http://www.regexcookbook.com durch Besuchen Sie bitte
<a href="http://www.regexcookbook.com">http://www.regexcookbook.com</a> ausgetauscht
werden.

Lösung
Regulärer Ausdruck
http:\S+
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzungstext
<azhref="$&">$&</a>
Ersetzungstextvarianten: .NET, JavaScript, Perl
<azhref="$0">$0</a>
Ersetzungstextvarianten: .NET, Java, PHP
<azhref="\0">\0</a>
Ersetzungstextvarianten: PHP, Ruby
<azhref="\&">\&</a>
Ersetzungstextvariante: Ruby
<azhref="\g<0>">\g<0></a>
Ersetzungstextvariante: Python

Diskussion
Durch das Einfügen des vollständigen Suchergebnisses der Regex in den Ersetzungstext
ist es einfach, vor und/oder hinter dem gefundenen Text zusätzlichen Text einzufügen.
Es lassen sich sogar mehrere Kopien des gefundenen Texts erzeugen. Sofern Sie nicht
Python nutzen, müssen Sie auch keine einfangenden Gruppen in Ihrem regulären Aus-
druck nutzen, um das gesamte Suchergebnis zu verwenden.
In Perl ist «$&» sogar eine Variable. Perl speichert darin nach jeder erfolgreichen Anwen-
dung einer Regex das Suchergebnis.

2.20 Einfügen des Suchergebnisses in den Ersetzungstext | 95


.NET und JavaScript haben die Perl-Syntax «$&» übernommen, um das Suchergebnis im
Ersetzungstext einzufügen. Ruby nutzt statt der Dollarzeichen Backslashs, daher wird
dort für das Suchergebnis «\&» verwendet.
Java, PHP und Python haben kein spezielles Token, mit dem das gesamte Suchergebnis
eingefügt werden kann, aber man kann einfangende Gruppen nutzen, um deren Inhalte
in den Ersetzungstext einzufügen. Das Gesamtsuchergebnis ist dabei eine implizite ein-
fangende Gruppe mit der Nummer 0. Bei Python müssen wir die Syntax für benannte
Captures nutzen, um auf die Gruppe null zu verweisen. Dort wird «\0» nicht unterstützt.
.NET und Ruby unterstützen ebenfalls eine einfangende Gruppe mit der Nummer 0. Das
Ergebnis ist dort unabhängig von der Syntax.

Siehe auch
„Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und Rezept 3.15.

2.21 Teile des gefundenen Texts in den Ersetzungstext


einfügen
Problem
Finden einer zusammenhängende Folge von zehn Ziffern, wie zum Beispiel 1234567890.
Umwandeln der Zahl in eine hübsch formatierte (amerikanische) Telefonnummer, zum
Beispiel (123) 456-7890.

Lösung
Regulärer Ausdruck
\b(\d{3})(\d{3})(\d{4})\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzungstext
($1)z$2-$3
Ersetzungstextvarianten: .NET, Java, JavaScript, PHP, Perl
(${1})z${2}-${3}
Ersetzungstextvarianten: .NET, PHP, Perl
(\1)z\2-\3
Ersetzungstextvarianten: PHP, Python, Ruby

96 | Kapitel 2: Grundlagen regulärer Ausdrücke


Diskussion
Verwendung einfangender Gruppen in Ersetzungstexten
Rezept 2.10 erklärt, wie Sie einfangende Gruppen in Ihrem regulären Ausdruck nutzen
können, um den gleichen Text mehr als ein Mal zu finden. Der von jeder einfangenden
Gruppe gefundene Text ist aber auch nach jeder erfolgreichen Übereinstimmung verfüg-
bar. Sie können ihn – in beliebiger Reihenfolge und auch mehr als ein Mal – in den Erset-
zungstext einfügen.
Manche Varianten, wie zum Beispiel Python und Ruby, nutzen die gleiche Syntax für
Rückwärtsreferenzen («\1») sowohl im regulären Ausdruck als auch im Ersetzungstext.
Andere Varianten greifen auf die Perl-Syntax «$1» zurück, bei der ein Dollarzeichen statt
eines Backslashs verwendet wird. PHP unterstützt beide Formen.
In Perl ist «$1», auch mit höheren Ziffern, eine echte Variable. Diese Variablen werden
nach jeder erfolgreichen Suche per Regex gesetzt. Sie können sie bis zur nächsten Regex-
Suche beliebig im Code nutzen. .NET, Java, JavaScript und PHP lassen «$1» nur im Erset-
zungstext selbst zu. Diese Programmiersprachen bieten andere Wege an, um im Code auf
den Inhalt von einfangenden Gruppen zuzugreifen. Kapitel 3 beschreibt das detailliert.

$10 und größer


Alle in diesem Buch behandelten Regex-Varianten unterstützen in ihren regulären Aus-
drücken bis zu 99 einfangende Gruppen. Im Ersetzungstext kann es dabei schwierig sein,
bei «$10» oder «\10» und noch höheren Gruppennummern zu entscheiden, was gemeint
ist. Man kann dies entweder als die zehnte einfangende Gruppe betrachten oder als erste
einfangende Gruppe, auf die eine literale Null folgt.
.NET, PHP und Perl ermöglichen es Ihnen, um die Zahl eine geschweifte Klammer zu
legen, um Ihr Anliegen deutlicher zu machen. «${10}» ist immer die zehnte einfangende
Gruppe, und «${1}0» ist immer die erste einfangende Gruppe, gefolgt von einer literalen
Null.
Java und JavaScript versuchen, bei «$10» schlau zu sein. Wenn in Ihrem regulären Aus-
druck eine einfangende Gruppe mit der angegebenen zweistelligen Zahl vorhanden ist,
werden beide Ziffern für die Gruppe genutzt. Gibt es weniger einfangende Gruppen, wird
nur die erste Ziffer für die Gruppe und die zweite als literale Ziffer genutzt. Damit ist
«$23» die 23. einfangende Gruppe, wenn es sie denn gibt. Wenn nicht, handelt es sich um
die zweite einfangende Gruppe, gefolgt von einer literalen «3».
.NET, PHP, Perl, Python und Ruby behandeln «$10» und «\10» immer als zehnte einfan-
gende Gruppe, egal ob sie existiert oder nicht. Wenn sie nicht vorhanden ist, kommt das
Verhalten bei nicht-existierenden Gruppen ins Spiel.

2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen | 97


Referenzen auf nicht-existierende Gruppen
Der reguläre Ausdruck in der Lösung für dieses Rezept enthält drei einfangende Grup-
pen. Wenn Sie im Ersetzungstext «$4» oder «\4» einfügen, ergänzen Sie damit eine Refe-
renz auf eine einfangende Gruppe, die gar nicht vorhanden ist. Damit wird eine von drei
verschiedenen Verhaltensweisen ausgelöst.
Java und Python werden laut aufschreien und eine Exception werfen oder eine Fehler-
meldung liefern. Bei diesen Varianten sollten Sie also auf keinen Fall ungültige Rück-
wärtsverweise nutzen. (Eigentlich sollten Sie bei keiner Variante ungültige Rückwärts-
verweise verwenden.) Wenn Sie «$4» oder «\4» als Literal einfügen wollen, müssen Sie
das Dollarzeichen oder den Backslash maskieren. Rezept 2.19 beschreibt das im Detail.
PHP, Perl und Ruby ersetzen alle Rückwärtsreferenzen im Ersetzungstext, auch solche,
die auf nicht vorhandene Gruppen zeigen. Solche Gruppen enthalten natürlich keinen
Text, daher werden die Referenzen auf diese Gruppen einfach durch nichts ersetzt.
.NET und JavaScript belassen schließlich Rückwärtsverweise auf Gruppen, die nicht vor-
handen sind, als literalen Text im Ersetzungstext.
Alle Varianten ersetzen Gruppen, die zwar im regulären Ausdruck vorhanden sind, aber
nichts gefunden haben. Dabei wird leerer Text eingefügt.

Lösung mit benannten Captures


Regulärer Ausdruck
\b(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b(?'area'\d{3})(?'exchange'\d{3})(?'number'\d{4})\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
\b(?P<area>\d{3})(?P<exchange>\d{3})(?P<number>\d{4})\b
Regex-Optionen: Keine
Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Ersetzungstext
(${area})z${exchange}-${number}
Ersetzungstextvariante: .NET
(\g<area>)z\g<exchange>-\g<number>
Ersetzungstextvariante: Python

98 | Kapitel 2: Grundlagen regulärer Ausdrücke


(\k<area>)z\k<exchange>-\k<number>
Ersetzungstextvariante: Ruby 1.9
(\k'area')z\k'exchange'-\k'number'
Ersetzungstextvariante: Ruby 1.9
($1)z$2-$3
Ersetzungstextvarianten: .NET, PHP, Perl 5.10
(${1})z${2}-${3}
Ersetzungstextvarianten: .NET, PHP, Perl 5.10
(\1)z\2-\3
Ersetzungstextvarianten: PHP, Python, Ruby 1.9

Varianten, die benannte Captures unterstützen


.NET, Python und Ruby 1.9 ermöglichen es Ihnen, benannte Rückwärtsreferenzen im
Ersetzungstext zu verwenden, wenn Sie benannte einfangende Gruppen in Ihrem regulä-
ren Ausdruck eingebaut haben.
Bei .NET und Python funktioniert die Syntax für benannte Rückwärtsreferenzen genau
so wie die von benannten und nummerierten einfangenden Gruppen. Geben Sie einfach
den Namen oder die Nummer der Gruppe zwischen den geschweiften oder spitzen Klam-
mern an.
Ruby nutzt für Rückwärtsreferenzen die gleiche Syntax im Ersetzungstext wie im regulä-
ren Ausdruck. Bei benannten einfangenden Gruppen ist die Syntax in Ruby 1.9
«\k<group>» oder «\k'group'». Die Wahl zwischen spitzen Klammern und einfachen
Anführungszeichen ist schlicht Geschmackssache.
Perl 5.10 und PHP (mit PCRE) unterstützen benannte einfangende Gruppen in regulären
Ausdrücken, aber nicht im Ersetzungstext. Dort können Sie nummerierte Rückwärtsrefe-
renzen verwenden, um auf die benannten einfangenden Gruppen aus dem regulären Aus-
druck zuzugreifen. Perl 5.10 und PCRE weisen auch benannten Gruppen Nummern zu –
von links nach rechts.
.NET, Python und Ruby 1.9 lassen ebenfalls nummerierte Referenzen auf benannte
Gruppen zu. Allerdings verwendet .NET ein anderes Nummerierungsschema für
benannte Gruppen, wie in Rezept 2.11 erläutert. Ein Mischen von Namen und Num-
mern ist in .NET, Python oder Ruby nicht empfehlenswert. Entweder geben Sie all Ihren
Gruppen Namen, oder Sie lassen es ganz. Nutzen Sie aber vor allem immer benannte
Rückwärtsreferenzen für benannte Gruppen.

Siehe auch
„Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und die Rezepte 2.9, 2.10,
2.11 und 3.15.

2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen | 99


2.22 Suchergebniskontext in den Ersetzungstext einfügen
Problem
Erstellen eines Ersetzungstexts, der das Suchergebnis durch den Text vor der Überein-
stimmung ersetzt, gefolgt vom kompletten Ausgangstext und dem Text nach der Über-
einstimmung. Wenn zum Beispiel das Wort Treffer in VorherTrefferNachher gefunden
wird, soll die Übereinstimmung durch VorherVorherTrefferNachherNachher ersetzt wer-
den, was zum Ergebnis VorherVorherVorherTrefferNachherNachherNachher führt.

Lösung
$`$_$'
Ersetzungstextvarianten: .NET, Perl
\`\`\&\'\'
Ersetzungstextvariante: Ruby
$`$`$&$'$'
Ersetzungstextvariante: JavaScript

Diskussion
Der Begriff Kontext bezieht sich auf den Ausgangstext, auf den der reguläre Ausdruck
angewandt wurde. Es gibt drei Elemente des Kontexts: den Ausgangstext vor dem Über-
einstimmungsbereich, den Ausgangstext nach dem Übereinstimmungsbereich und den
gesamten Ausgangstext. Der Text vor dem Übereinstimmungsbereich wird manchmal als
linker Kontext und der danach dementsprechend als rechter Kontext bezeichnet. Der
gesamte Ausgangstext setzt sich aus dem linken Kontext, dem Übereinstimmungsbereich
und dem rechten Kontext zusammen.
.NET und Perl unterstützen «$`», «$'» und «$_», um alle drei Kontextelemente im Erset-
zungstext einzufügen. In Perl handelt es sich sogar um Variablen, die nach einer erfolgrei-
chen Regex-Suche gefüllt werden und bis zum nächsten Suchvorgang im Code zur
Verfügung stehen. Dollar plus Gravis (Backtick) ist der linke Kontext. Auf einer deut-
schen Tastatur erreichen Sie den Gravis, indem Sie die Umschalttaste zusammen mit der
Taste rechts neben dem scharfen s (ß) und danach eventuell noch einmal die Leertaste
drücken. Dollar plus einfaches Anführungszeichen ist der rechte Kontext. Das einfache
Anführungszeichen erreicht man auf einer deutschen Tastatur üblicherweise über die
Umschalttaste zusammen mit der Taste rechts neben dem Ä. Dollar plus Unterstrich ent-
hält den gesamten Ausgangstext. Wie .NET und Perl nutzt JavaScript «$`» und «$'» für
den linken und rechten Kontext. Allerdings gibt es in JavaScript kein Token für das Ein-
fügen des gesamten Ausgangstexts. Sie können ihn zusammensetzen, indem Sie das
Suchergebnis «$&» mit dem linken und rechten Kontext verknüpfen.

100 | Kapitel 2: Grundlagen regulärer Ausdrücke


In Ruby kann man auf den linken und rechten Kontext über «\`» und «\'» zugreifen.
«\&» fügt das vollständige Suchergebnis ein. Wie bei JavaScript gibt es kein Token für den
gesamten Ausgangstext.

Siehe auch
„Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und Rezept 3.15.

2.22 Suchergebniskontext in den Ersetzungstext einfügen | 101


KAPITEL 3
Mit regulären Ausdrücken programmieren

Programmiersprachen und Regex-Varianten


Dieses Kapitel beschreibt, wie man reguläre Ausdrücke mit der Programmiersprache
Ihrer Wahl implementiert. Die Rezepte in diesem Kapitel gehen davon aus, dass Sie
schon einen regulären Ausdruck haben, der funktioniert. Das vorhergehende Kapitel
kann Ihnen da eventuell weiterhelfen. Jetzt haben Sie die Aufgabe, einen regulären Aus-
druck in Quellcode umzuwandeln und auch dafür zu sorgen, dass er tatsächlich etwas
tut.
Wir geben in diesem Kapitel unser Bestes, um genau zu erklären, wie und warum jeder
Codeabschnitt so arbeitet, wie er arbeitet. Aufgrund der vielen Details in diesem Kapitel
kann es etwas ermüdend sein, es vom Anfang bis zum Ende durchzulesen. Wenn Sie das
Reguläre Ausdrücke Kochbuch das erste Mal lesen, empfehlen wir Ihnen, dieses Kapitel
zunächst nur zu überfliegen, um zu verstehen, was möglich ist und was nicht. Wollen Sie
dann später einen der regulären Ausdrücke aus den folgenden Kapiteln implementieren,
kommen Sie hierhin zurück, um zu erfahren, wie man genau die Regexes in Ihre Pro-
grammiersprache integriert.
Die Kapitel 4 bis 8 nutzen reguläre Ausdrücke, um reale Probleme zu lösen. Diese Kapitel
konzentrieren sich auf die regulären Ausdrücke selbst, und viele Rezepte enthalten über-
haupt keinen Quellcode. Damit die dort gefundenen regulären Ausdrücke auch wirklich
funktionieren, fügen Sie sie einfach in einen der Codeabschnitte aus diesem Kapitel ein.
Da sich die anderen Kapitel auf die regulären Ausdrücke konzentrieren, werden die
Lösungen dort für bestimmte Regex-Varianten vorgestellt und nicht für bestimmte Pro-
grammiersprachen. Regex-Varianten entsprechen nicht eins zu eins Programmierspra-
chen. Skriptsprachen haben meist schon ihre eigene Regex-Variante eingebaut, während
andere Programmiersprachen auf Bibliotheken zurückgreifen. Einige der Bibliotheken
gibt es für mehrere Sprachen, während manche Sprache auch mehrere Bibliotheken im
Angebot hat.
„Viele Varianten regulärer Ausdrücke“ auf Seite 2, beschreibt alle in diesem Buch behan-
delten Regex-Varianten, „Viele Varianten des Ersetzungstexts“ auf Seite 6, zählt die Vari-

| 103
anten für die Ersetzungstexte auf, die beim Suchen und Ersetzen mithilfe regulärer
Ausdrücke genutzt werden. Alle in diesem Kapitel besprochenen Programmiersprachen
nutzen eine dieser Varianten.

In diesem Kapitel behandelte Sprachen


Dieses Kapitel behandelt acht Programmiersprachen. Jedes Rezept enthält eigene Lösun-
gen für alle acht Sprachen, und viele Rezepte enthalten auch getrennte Diskussionsab-
schnitte für alle acht Sprachen. Wenn sich eine Technik mit mehr als einer Sprache
einsetzen lässt, wiederholen wir sie für jede der Sprachen. Das haben wir gemacht, damit
Sie ohne Gefahr die Diskussionen zu Programmiersprachen überspringen können, an
denen Sie nicht interessiert sind.
C#
C# nutzt das .NET Framework von Microsoft. Die Klassen in System.Text.Regular-
Expressions nutzen die .NET-Variante für reguläre Ausdrücke und Ersetzungstexte.
Dieses Buch behandelt C# in den Versionen 1.0 bis 3.5 oder Visual Studio 2002 bis
2008.
VB.NET
Dieses Buch nutzt VB.NET und Visual Basic.NET, um auf Visual Basic 2002 und
neuer zu verweisen und diese Versionen von Visual Basic 6 und älter zu unterschei-
den. Visual Basic nutzt mittlerweile das .NET Framework von Microsoft. Die Klas-
sen in System.Text.RegularExpressions nutzen die .NET-Variante für reguläre
Ausdrücke und Ersetzungstexte. Dieses Buch behandelt Visual Basic 2002 bis 2008.
Java
Java 4 ist das erste Java-Release, das reguläre Ausdrücke auch ohne externe Anbie-
ter unterstützt. Dazu dient das Paket java.util.regex. Es verwendet die Java-Vari-
ante für reguläre Ausdrücke und Ersetzungstexte. Dieses Buch behandelt Java 4, 5
und 6.
JavaScript
Dies ist die Regex-Variante, die in der Programmiersprache genutzt wird, die allge-
mein als JavaScript bekannt ist. Alle modernen Webbrowser haben sie implemen-
tiert: Internet Explorer (ab Version 5.5), Firefox, Opera, Safari und Chrome. Viele
andere Anwendungen nutzen ebenfalls JavaScript als Skriptsprache.
Streng genommen verwenden wir den Begriff JavaScript in diesem Buch, um uns auf
die Programmiersprache zu beziehen, die in Version 3 des ECMA-262-Standards
definiert ist. Dieser Standard definiert die Programmiersprache ECMAScript, die
eher durch ihre Implementierungen JavaScript und JScript in den verschiedenen
Webbrowsern bekannt ist.
ECMA-262v3 definiert auch die Variante für reguläre Ausdrücke und Ersetzungs-
texte, die von JavaScript genutzt wird. In diesem Buch bezeichnen wir diese Varian-
ten als „JavaScript“.

104 | Kapitel 3: Mit regulären Ausdrücken programmieren


PHP
PHP bietet drei verschiedene Arten von Regex-Funktionen an. Wir empfehlen drin-
gend, die preg-Funktionen zu nutzen. Daher geht es in diesem Buch nur um die
preg-Funktionen, die es in PHP seit Version 4.2.0 gibt. Dieses Buch behandelt PHP
4 und 5. Die preg-Funktionen sind PHP-Wrapper um die PCRE-Bibliothek. Die
PCRE-Regex-Variante wird in diesem Buch als „PCRE“ bezeichnet. Da PCRE keine
Funktionalität zum Suchen und Ersetzen enthält, haben die PHP-Entwickler ihre
eigene Ersetzungstextsyntax für preg_replace entwickelt. Diese Ersetzungstextvari-
ante wird im Weiteren als „PHP“ bezeichnet.
Die mb_ereg-Funktionen sind Teil von PHPs „Multibyte“-Funktionen, die dafür
gedacht sind, gut mit Sprachen zusammenzuarbeiten, die traditionell mit Mehrbyte-
Zeichensätzen kodiert sind, wie zum Beispiel Japanisch und Chinesisch. In PHP 5
nutzen die mb_ereg-Funktionen die Regex-Bibliothek Oniguruma, die ursprünglich
für Ruby entwickelt wurde. Die Oniguruma-Regex-Variante wird hier als „Ruby
1.9“ bezeichnet. Sie sollten die mb_ereg-Funktionen nur dann nutzen, wenn Sie auf
jeden Fall mit Mehrbyte-Codepages arbeiten müssen und schon mit den mb_-Funk-
tionen in PHP vertraut sind.
Die Gruppe der ereg-Funktionen enthält die ältesten Regex-Funktionen in PHP und
gilt offiziell seit PHP 5.3.0 als veraltet. Diese Funktionen benötigen keine externen
Bibliotheken, und sie implementieren die POSIX ERE-Variante. Diese Variante stellt
nur einen eingeschränkten Satz an Features zur Verfügung und wird auch nicht in
diesem Buch behandelt. POSIX ERE ist eine echte Untermenge der Varianten Ruby
1.9 und PCRE. Sie können jede Regex aus einem ereg-Funktionsaufruf nehmen und
mit mb_ereg oder preg aufrufen. Bei preg müssen Sie noch Begrenzer im Perl-Stil hin-
zufügen (Rezept 3.1).
Perl
Die in Perl eingebaute Unterstützung für reguläre Ausdrücke ist der Hauptgrund für
deren aktuelle Beliebtheit. Die von den Perl-Operatoren m// und s/// genutzten
Varianten für Regexes und Ersetzungstexte werden in diesem Buch als „Perl“
bezeichnet. Behandelt werden Perl 5.6, 5.8 und 5.10.
Python
Python unterstützt reguläre Ausdrücke über sein Modul re. Die Varianten für regu-
läre Ausdrücke und Ersetzungstexte, die dieses Modul nutzt, werden in diesem
Buch als „Python“ bezeichnet. Behandelt werden Python 2.4 and 2.5.
Ruby
Ruby enthält bereits in der Sprache eine Unterstützung für reguläre Ausdrücke. In
diesem Buch werden Ruby 1.8 und Ruby 1.9 behandelt. Diese zwei Versionen von
Ruby nutzen unterschiedliche Regex-Engines. Ruby 1.9 verwendet die Oniguruma-
Engine, die mehr Features enthält als die klassische Engine aus Ruby 1.8. „Regex-
Varianten in diesem Buch“ auf Seite 3 geht darauf detaillierter ein.
In diesem Kapitel geht es uns nicht so sehr um die Unterschiede zwischen Ruby 1.8
und 1.9. Die regulären Ausdrücke in diesem Kapitel sind sehr einfach und verwen-

Programmiersprachen und Regex-Varianten | 105


den keine der neuen Features aus Ruby 1.9. Da die Unterstützung für reguläre Aus-
drücke in die Sprache selbst hineinkompiliert ist, ist der Ruby-Code, den Sie zum
Implementieren Ihrer regulären Ausdrücke nutzen, unabhängig von der verwende-
ten Engine – egal ob Sie Ruby mit der klassischen Regex-Engine oder mit der Onigu-
ruma-Engine kompiliert haben. Sie können Ruby 1.8 neu kompilieren und dabei die
Oniguruma-Engine nutzen, wenn Sie deren Features benötigen.

Mehr Programmiersprachen
Die Programmiersprachen in der folgenden Liste werden in diesem Buch nicht explizit
behandelt, aber sie nutzen eine der hier vorgestellten Regex-Varianten. Sollten Sie mit
einer der folgenden Sprachen arbeiten, können Sie dieses Kapitel überspringen, aber alle
weiteren werden trotzdem nützlich für Sie sein:
ActionScript
ActionScript ist Adobes Implementierung des ECMA-262-Standards. Seit Version
3.0 enthält ActionScript eine vollständige Unterstützung der in ECMA-262v3 defi-
nierten regulären Ausdrücke. Diese Regex-Variante wird in diesem Buch als „Java-
Script“ bezeichnet. Die Sprache ActionScript ist JavaScript sehr ähnlich. Sie sollten
die in diesem Kapitel vorgestellten JavaScript-Beispiele an ActionScript anpassen
können.
C
C kann eine große Zahl an Bibliotheken für reguläre Ausdrücke nutzen. Die Open
Source-Bibliothek PCRE ist vermutlich die beste Wahl aus den hier vorgestellten
Varianten. Sie können den vollständigen C-Quellcode unter http://www.pcre.org
herunterladen. Der Code ist so geschrieben, dass er sich mit einer Vielzahl von
Compilern auf vielen Plattformen kompilieren lässt.
C++
C++ kann ebenfalls diverse Bibliotheken für reguläre Ausdrücke nutzen. Die Open
Source-Bibliothek PCRE ist vermutlich die beste Wahl aus den hier vorgestellten
Varianten. Sie können entweder direkt die C-API oder die C++-Wrapper-Klassen
nutzen, die in der PCRE bereits enthalten sind (siehe http://www.pcre.org).
Unter Windows können Sie das COM-Objekt VBScript 5.5 RegExp COM importie-
ren, wie es später für Visual Basic 6 erläutert wird. Das kann nützlich sein, um in
einem C++-Backend und einem JavaScript-Frontend die gleichen Regexes nutzen zu
können.
Delphi for Win32
Während der Entstehung dieses Texts enthält die Win32-Version von Delphi keine
eingebaute Unterstützung für reguläre Ausdrücke. Es gibt viele VCL-Komponenten,
mit denen reguläre Ausdrücke genutzt werden können. Ich empfehle, eine zu ver-
wenden, die auf PCRE basiert. Delphi kann C-Objektdateien in Ihre Anwendungen
einbinden, und viele VCL-Wrapper für PCRE nutzen solche Objektdateien. Damit
können Sie Ihre Anwendung als EXE-Datei ausliefern.

106 | Kapitel 3: Mit regulären Ausdrücken programmieren


Sie können die von mir (Jan Goyvaerts) entwickelte TPerlRegEx-Komponente unter
http://www.regexp.info/delphi.html herunterladen. Dabei handelt es sich um eine
VCL-Komponente, die sich selbst in der Komponentenpalette installiert, sodass sie
sich leicht auf eine Form ziehen lässt. Ein weiterer beliebter PCRE-Wrapper für Del-
phi ist die Klasse TJclRegEx, die Teil der JCL-Bibliothek ist (siehe http://www.delphi-
jedi.org). TJclRegEx leitet sich von TObject ab, daher lässt sie sich nicht auf eine Form
ziehen.
Beide Bibliotheken sind Open Source und befinden sich unter der Mozilla Public
License.
Delphi Prism
In Delphi Prism können Sie die vom .NET Framework angebotene Unterstützung
für reguläre Ausdrücke nutzen. Fügen Sie einfach der uses-Klausel einer Delphi
Prism Unit, in der Sie reguläre Ausdrücke nutzen wollen, System.Text.Regular-
Expressions hinzu.
Wenn Sie das erledigt haben, können Sie die gleichen Techniken nutzen, die in den
Codeschnipseln für C# und VB.NET in diesem Kapitel demonstriert werden.
Groovy
Sie können in Groovy reguläre Ausdrücke genau wie in Java mit dem Paket
java.util.regex nutzen. Tatsächlich müssten alle in diesem Kapitel vorgestellten
Lösungen für Java auch mit Groovy funktionieren. Die Groovy-eigene Syntax für
reguläre Ausdrücke stellt eigentlich nur Notationsabkürzungen dar. Eine literale
Regex, die durch Schrägstriche begrenzt ist, ist eine Instanz von java.lang.String,
und der Operator =~ instantiiert java.util.regex.Matcher. Sie können die Groovy-
Syntax mit der normalen Java-Syntax beliebig mischen – die Klassen und Objekte
sind überall gleich.
PowerShell
PowerShell ist Microsofts eigene Shell-Skriptsprache. Sie basiert auf dem .NET
Framework. Die in die PowerShell eingebauten Operatoren -match und -replace
nutzen die in diesem Buch vorgestellten .NET-Varianten für reguläre Ausdrücke
und Ersetzungstexte.
R
Das Projekt R unterstützt reguläre Ausdrücke über die Funktionen grep, sub und
regexpr aus dem Paket base. Alle diese Funktionen erwarten ein Argument namens
perl, das auf FALSE gesetzt wird, wenn Sie es weglassen. Setzen Sie es auf TRUE, um
die in diesem Buch beschriebene PCRE-Variante zu verwenden. Die für PCRE 7
gezeigten Ausdrücke arbeiten mit R 2.5.0 und neuer. Frühere Versionen von R nut-
zen reguläre Ausdrücke, die in diesem Buch mit „PCRE 4 und neuer“ gekennzeich-
net sind. Die von R unterstützten Varianten „basic“ und „extended“ sind älter und
eingeschränkter. In diesem Buch werden sie nicht behandelt.

Programmiersprachen und Regex-Varianten | 107


REALbasic
REALbasic hat eine eingebaute Klasse RegEx. Intern nutzt diese Klasse die UTF-8-
Version der PCRE-Bibliothek. Das bedeutet, dass Sie die Unicode-Unterstützung der
PCRE nutzen können, aber die REALbasic-Klasse TextConverter verwenden müs-
sen, um Nicht-ASCII-Texte nach UTF-8 zu konvertieren, bevor Sie sie an die Klasse
RegEx übergeben.
Alle in diesem Buch für PCRE 6 gezeigten regulären Ausdrücke funktionieren in
REALbasic. Allerdings sind in REALbasic die Optionen Groß-/Kleinschreibung igno-
rieren und Zirkumflex und Dollar passen zu Zeilenumbrüchen (“Multiline”) stan-
dardmäßig eingeschaltet. Wenn Sie einen regulären Ausdruck aus diesem Buch
nutzen wollen, der nicht explizit darauf hinweist, diese Optionen einzuschalten,
müssen Sie sie in REALbasic deaktivieren.
Scala
Scala stellt eine eingebaute Unterstützung durch das Paket scala.util.matching
bereit. Dabei wird die Regex-Engine aus dem Java-Paket java.util.regex genutzt.
Die von Java und Scala genutzten Varianten für reguläre Ausdrücke und Ersetzungs-
texte werden in diesem Buch als „Java“ bezeichnet.
Visual Basic 6
Visual Basic 6 ist die letzte Version von Visual Basic, die nicht das .NET Framework
benötigt. Das bedeutet aber auch, dass Visual Basic 6 die ausgezeichnete Unterstüt-
zung von regulären Ausdrücken durch das .NET Framework nicht nutzen kann. Die
Codebeispiele in diesem Kapitel für VB.NET funktionieren nicht mit VB 6.
Mit Visual Basic 6 kann man aber sehr leicht Funktionalität aus ActiveX- und COM-
Bibliotheken integrieren. Eine solche Bibliothek ist die VBScript-Skripting-Biblio-
thek von Microsoft, die ab Version 5.5 eine halbwegs anständige Unterstützung für
reguläre Ausdrücke anbietet. Die Skripting-Bibliothek implementiert die gleiche
Regex-Variante, die auch in JavaScript genutzt wird und die in ECMA-262v3 stan-
dardisiert ist. Diese Bibliothek ist Teil des Internet Explorer 5.5 und neuer. Sie steht
auf allen Rechnern zur Verfügung, die unter Windows XP oder Vista laufen, aber
auch unter älteren Windows-Versionen, wenn der Benutzer den IE auf Version 5.5
oder neuer aktualisiert hat. Das bedeutet, nahezu jeder Windows-PC, der für das
Surfen im Internet verwendet wird, bietet diese Bibliothek an.
Um diese Bibliothek in Ihrer Visual Basic-Anwendung zu nutzen, wählen Sie im VB-
IDE-Menü Project/References. Blättern Sie durch die Liste, bis Sie den Eintrag
Microsoft VBScript Regular Expressions 5.5 finden, der direkt unter Microsoft
VBScript Regular Expressions 1.0 liegt. Stellen Sie sicher, dass Sie Version 5.5 mar-
kiert haben und nicht Version 1.0. Letztere ist nur aus Gründen der Abwärtskompa-
tibilität verfügbar, und ihre Fähigkeiten sind nicht sehr umfangreich.
Nach dem Hinzufügen der Referenz können Sie sehen, welche Klassen und Klassen-
Member durch die Bibliothek bereitgestellt werden. Wählen Sie im Menü View/
Object Browser aus. Im Object Browser wählen Sie aus der Auswahlliste in der
linken oberen Ecke die Bibliothek VBScript_RegExp_55 aus.

108 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.1 Literale reguläre Ausdrücke im Quellcode
Problem
Sie haben den regulären Ausdruck ‹[$"'\n\d/\\]› als Lösung für ein Problem erhalten.
Dieser reguläre Ausdruck besteht nur aus einer Zeichenklasse, mit der ein Dollarzeichen,
ein doppeltes Anführungszeichen, ein einfaches Anführungszeichen, ein Line-Feed, eine
Ziffer von 0 bis 9, ein Schrägstrich oder ein Backslash gefunden werden kann. Sie wollen
diesen regulären Ausdruck hartkodiert in Ihrem Quellcode als String-Konstante oder als
Regex-Operator einbauen.

Lösung
C#
Als normaler String:
"[$\"'\n\\d/\\\\]"

Als Verbatim-String:
@"[$""'\n\d/\\]"

VB.NET
"[$""'\n\d/\\]"

Java
"[$\"'\n\\d/\\\\]"

JavaScript
/[$"'\n\d\/\\]/

PHP
'%[$"\'\n\d/\\\\]%'

Perl
Mustererkennungsoperator:
/[\$"'\n\d\/\\]/
m![\$"'\n\d/\\]!

Substitutionsoperator:
s![\$"'\n\d/\\]!!

3.1 Literale reguläre Ausdrücke im Quellcode | 109


Python
String mit dreifachem Anführungszeichen:
r"""[$"'\n\d/\\]"""

Normaler String:
"[$\"'\n\\d/\\\\]"

Ruby
Literale Regex, begrenzt durch Schrägstriche:
/[$"'\n\d\/\\]/

Literale Regex, begrenzt durch ein Zeichen Ihrer Wahl:


%r![$"'\n\d/\\]!

Diskussion
Wenn in diesem Buch ein regulärer Ausdruck allein vorgestellt wird (und nicht als Teil
eines längeren Quellcodeabschnitts), wird er immer ganz schmucklos präsentiert. Dieses
Rezept ist die einzige Ausnahme dazu. Wollen Sie ein Programm zum Testen regulärer
Ausdrücke nutzen, wie zum Beispiel RegexBuddy oder RegexPal, würden Sie die Regex
genau so eingeben. Erwartet Ihre Anwendung einen regulären Ausdruck als Benutzerein-
gabe, würde der Anwender sie ebenfalls so eingeben.
Möchten Sie jedoch den regulären Ausdruck hartkodiert in Ihrem Quellcode unterbrin-
gen, müssen Sie ein bisschen mehr tun. Wenn Sie einfach einen regulären Ausdruck aus
einem Testprogramm in Ihren Quellcode kopieren – oder umgekehrt –, wird das häufig
dazu führen, dass Sie sich am Kopf kratzen und sich fragen, warum der Ausdruck in
Ihrem Tool funktioniert, aber nicht in Ihrem Quellcode, oder warum das Testprogramm
nichts mit einer Regex anfangen kann, die Sie aus dem Quellcode von jemand anderem
kopiert haben. Bei allen in diesem Buch besprochenen Programmiersprachen müssen
literale reguläre Ausdrücke auf eine bestimmte Art und Weise begrenzt sein, wobei man-
che Sprachen Strings nutzen und andere dagegen eine spezielle Regex-Konstante verwen-
den. Wenn Ihre Regex eines der Begrenzungszeichen Ihrer Sprache enthält oder andere
Zeichen mit besonderen Bedeutungen, müssen Sie sie maskieren.
Der Backslash ist das am häufigsten genutzte Maskierungszeichen. Das ist der Grund
dafür, dass die meisten Lösungen für dieses Problem deutlich mehr Backslashs enthalten
als die im ursprünglichen regulären Ausdruck vorhandenen vier.

C#
In C# können Sie literale reguläre Ausdrücke an den Konstruktor Regex() und eine ganze
Reihe von Member-Funktionen der Klasse Regex übergeben. Der Parameter, der den
regulären Ausdruck aufnimmt, ist immer als String deklariert.

110 | Kapitel 3: Mit regulären Ausdrücken programmieren


C# unterstützt zwei Arten von String-Literalen. Die gebräuchlichste ist der String in dop-
pelten Anführungszeichen, der auch aus Sprachen wie C++ und Java bekannt ist. Bei sol-
chen Strings müssen doppelte Anführungszeichen und Backslashs durch einen Backslash
maskiert werden. Maskierte Zeichen für nicht druckbare Zeichen, wie zum Beispiel ‹\n›,
sind in Strings ebenfalls möglich. Es gibt aber einen Unterschied zwischen "\n" und
"\\n", wenn RegexOptions.IgnorePatternWhitespace genutzt wird (siehe Rezept 3.4), um
den Freiform-Modus einzuschalten (erklärt in Rezept 2.18). "\n" ist ein String mit einem
literalen Zeilenumbruch, was als Whitespace ignoriert wird, "\\n" ist dagegen ein String
mit dem Regex-Token ‹\n›, das zu einem Newline-Zeichen passt.
Verbatim-Strings beginnen mit einem At-Zeichen und einem doppelten Anführungszei-
chen, und sie enden mit einem doppelten Anführungszeichen. Um ein doppeltes Anfüh-
rungszeichen in einem Verbatim-String zu nutzen, müssen Sie es verdoppeln. Backslashs
werden zum Maskieren nicht benötigt, wodurch sich die Lesbarkeit eines regulären Aus-
drucks deutlich verbessert. @"\n" ist immer das Regex-Token ‹\n›, mit dem ein Zeilen-
umbruch gefunden wird, selbst im Freiform-Modus. Verbatim-Strings unterstützen kein
‹\n› auf String-Ebene, sie können jedoch mehrere Zeilen umfassen. Damit sind sie ideale
Kandidaten für reguläre Ausdrücke im Freiform-Modus.
Die Wahl ist klar: Für reguläre Ausdrücke sollte man im C#-Quellcode Verbatim-Strings
nutzen.

VB.NET
In VB.NET können Sie literale reguläre Ausdrücke an den Konstruktor Regex() und ver-
schiedene Member-Funktionen der Klasse Regex übergeben. Die dafür genutzten Parame-
ter sind immer als String deklariert.
Visual Basic nutzt Strings zwischen doppelten Anführungszeichen. Innerhalb solcher
Strings müssen doppelte Anführungszeichen verdoppelt werden. Andere Zeichen sind
nicht zu maskieren.

Java
In Java können Sie literale reguläre Ausdrücke an die Klassenfabrik Pattern.compile()
und an eine Reihe von Funktionen der Klasse String übergeben. Die entsprechenden
Parameter für die regulären Ausdrücke sind immer als Strings deklariert.
Java nutzt Strings zwischen doppelten Anführungszeichen. In solchen Strings müssen
doppelte Anführungszeichen und Backslashs durch einen Backslash maskiert werden.
Maskierte Zeichen für nicht druckbare Zeichen, wie zum Beispiel ‹\n›, und Unicode-
Maskierungen wie ‹\uFFFF› werden in Strings ebenfalls unterstützt.
Es gibt einen Unterschied zwischen "\n" und "\\n", wenn man Pattern.COMMENTS nutzt
(siehe Rezept 3.4), um den Freiform-Modus einzuschalten (erklärt in Rezept 2.18). "\n"
ist ein String mit einem literalen Zeilenumbruch, was als Whitespace ignoriert wird,
"\\n" ist dagegen ein String mit dem Regex-Token ‹\n›, das zu einem Newline-Zeichen
passt.

3.1 Literale reguläre Ausdrücke im Quellcode | 111


JavaScript
In JavaScript werden reguläre Ausdrücke am besten erstellt, indem man die spezielle Syn-
tax für das Deklarieren literaler regulärer Ausdrücke nutzt. Fassen Sie Ihren regulären
Ausdruck einfach in Schrägstriche ein. Wenn innerhalb des regulären Ausdrucks selbst
ein Schrägstrich vorkommt, maskieren Sie ihn mit einem Backslash.
Obwohl es möglich ist, ein RegExp-Objekt aus einem String zu erstellen, ist es nicht so
sinnvoll, die String-Notation für literale reguläre Ausdrücke in Ihrem Code zu nutzen. Sie
müssten Anführungszeichen und Backslashs maskieren, was im Allgemeinen zu einem
ganzen Wald von Backslashs führt.

PHP
Literale reguläre Ausdrücke für die preg-Funktionen von PHP nutzen eine interessante
Syntax. Anders als in JavaScript oder Perl gibt es in PHP keinen eingebauten Typ für
reguläre Ausdrücke. Sie müssen immer als Strings eingegeben werden. Das gilt auch für
die ereg- und mb_ereg-Funktionen. Aber bei dem Versuch, sich so weit wie möglich an
Perl zu orientieren, haben die Entwickler der PHP-Wrapper-Funktionen für die PCRE
noch eine weitere Anforderung hinzugefügt.
Innerhalb des Strings muss der reguläre Ausdruck wie eine literale Regex in Perl notiert
werden. Würden Sie also in Perl /Regex/ schreiben, bräuchten die preg-Funktionen in
PHP für den String den Wert '/Regex/'. Wie in Perl können Sie beliebige Begrenzungszei-
chen nutzen. Wenn das Regex-Begrenzungszeichen innerhalb der Regex auftaucht, muss
es durch einen Backslash maskiert werden. Um das zu verhindern, sollten Sie ein Begren-
zungszeichen wählen, das nicht in der Regex vorkommt. Für dieses Rezept nutze ich das
Prozentzeichen, weil der Schrägstrich Teil der Regex ist. Ist er jedoch nicht Teil der
Regex, sollten Sie ihn als Begrenzungszeichen nutzen, da er in Perl das am häufigsten
dafür genutzte Zeichen ist. In JavaScript und Ruby ist er sogar verpflichtend.
PHP unterstützt Strings sowohl mit einfachen als auch mit doppelten Anführungszei-
chen. Bei beiden müssen die Anführungszeichen (einfache und doppelte) sowie der Back-
slash innerhalb einer Regex mit einem Backslash maskiert werden. In Strings mit
doppelten Anführungszeichen muss auch das Dollarzeichen maskiert werden. Für regu-
läre Ausdrücke sollten Sie Strings in einfachen Anführungszeichen nutzen, sofern Sie
nicht tatsächlich Variablen innerhalb Ihrer Regex auflösen wollen.

Perl
In Perl werden literale reguläre Ausdrücke zusammen mit dem Operator zur Musterüber-
einstimmung und zum Ersetzen genutzt. Der Operator zur Musterübereinstimmung
besteht aus zwei Schrägstrichen, zwischen denen sich die Regex befindet. Schrägstriche
innerhalb des regulären Ausdrucks müssen durch einen Backslash maskiert werden.
Andere Zeichen benötigen keine Maskierung, außer vielleicht $ und @, wie am Ende die-
ses Abschnitts noch erläutert wird.

112 | Kapitel 3: Mit regulären Ausdrücken programmieren


Eine alternative Notation für den Musterübereinstimmungsoperator steckt den regulären
Ausdruck zwischen zwei beliebige Satzzeichen, vor denen der Buchstabe m steht. Wenn
Sie beliebige Zeichen für den öffnenden und schließenden Begrenzer nutzen (runde,
geschweifte oder eckige Klammern), müssen diese zueinander passen, zum Beispiel bei
m{Regex}. Verwenden Sie andere Satzzeichen, können Sie das Zeichen doppelt nutzen.
Die Lösung für dieses Rezept verwendet das Ausrufezeichen. Damit müssen wir den lite-
ralen Schrägstrich im regulären Ausdruck nicht maskieren. Wenn das öffnende und das
schließende Zeichen unterschiedlich sind, muss nur das schließende Zeichen mit einem
Backslash maskiert werden, wenn es als Literal im regulären Ausdruck vorkommt.
Der Ersetzungsoperator sieht dem Musterübereinstimmungsoperator sehr ähnlich. Er
beginnt mit einem s statt mit einem m, und am Ende findet sich noch der Ersetzungstext.
Wenn Sie Klammern als Begrenzer nutzen, brauchen Sie zwei Paare: s[Regex][Erset-
zung]. Alle anderen Satzzeichen nutzen Sie drei Mal: s/Regex/Ersetzung/.
Perl parst die beiden Operatoren als Strings in doppelten Anführungszeichen. Wenn Sie
m/Ich heiße $name/ schreiben und $name den Wert "Jan" enthält, führt das im Endeffekt
zum regulären Ausdruck ‹IchzheißezJan›. $" ist in Perl ebenfalls eine Variable, daher
müssen wir das literale Dollarzeichen in unserer Zeichenklasse hier im Rezept maskieren.
Maskieren Sie niemals ein Dollarzeichen, das Sie als Anker nutzen wollen (siehe
Rezept 2.5). Ein maskiertes Dollarzeichen ist immer ein Literal. Perl ist schlau genug,
zwischen Dollarzeichen als Anker und Dollarzeichen für das Auswerten von Variablen zu
unterscheiden. Denn Anker können sich sinnvollerweise nur am Ende einer Gruppe,
einer ganzen Regex oder vor einem Newline befinden. Sie dürfen das Dollarzeichen in
‹m/^Regex$/› nicht maskieren, wenn Sie herausfinden wollen, ob „Regex“ dem ganzen
Ausgangstext entspricht.
Das At-Zeichen (der Klammeraffe) hat in regulären Ausdrücken keine besondere Bedeu-
tung, es wird aber beim Auswerten von Variablen in Perl verwendet. Daher müssen Sie es
in literalen regulären Ausdrücken im Perl-Code maskieren, so wie Sie es auch in Strings
mit doppelten Anführungszeichen tun.

Python
Die Funktionen im re-Modul von Python erwarten die ihnen übergebenen regulären Aus-
drücke als Strings. Sie können selbst entscheiden, welchen der von Python angebotenen
Wege Sie nutzen wollen, um die Strings im Quelltext zu nutzen. Je nachdem, welche Zei-
chen Sie in Ihrem regulären Ausdruck verwenden, werden durch die unterschiedlichen
Begrenzungszeichen weniger Backslashs zum Maskieren notwendig sein.
Im Allgemeinen sind Raw-Strings die beste Wahl. In solchen Strings müssen in Python
keine Zeichen maskiert werden. Wenn Sie einen Raw-String verwenden, brauchen Sie die
Backslashs in Ihrem regulären Ausdruck nicht zu verdoppeln. r"\d+" lässt sich einfacher
lesen als "\\d+", insbesondere wenn Ihre Regex länger wird.

3.1 Literale reguläre Ausdrücke im Quellcode | 113


Raw-Strings sind allerdings nicht so ideal, wenn Ihr regulärer Ausdruck sowohl einzelne
als auch doppelte Anführungszeichen enthält. Dann können Sie keinen Raw-String nut-
zen, der von einzelnen oder doppelten Anführungszeichen umschlossen ist, da sich diese
Zeichen innerhalb des Ausdrucks nicht maskieren lassen. In diesem Fall können Sie drei
Anführungszeichen als Begrenzer nutzen, wie wir es auch in der Python-Lösung für die-
ses Rezept getan haben. Der normale String ist zum Vergleich mit angegeben.
Wenn Sie das in Rezept 2.7 beschriebene Unicode-Feature in Ihrem regulären Ausdruck
nutzen wollen, müssen Sie Unicode-Strings verwenden. Ein String lässt sich in einen Uni-
code-String verwandeln, indem Sie ihm ein u voranstellen.
Raw-Strings unterstützen keine nicht druckbaren Zeichen wie zum Beispiel \n. Dort wer-
den solche Maskierungssequenzen als literaler Text betrachtet. Das ist für das Modul re
allerdings kein Problem. Diese Maskierungen werden dort als Teil der Regex-Syntax und
der Ersetzungstextsyntax unterstützt. Ein literales \n in einem Raw-String wird in Ihren
regulären Ausdrücken und Ersetzungstexten trotzdem als Newline interpretiert werden.
Es gibt einen Unterschied zwischen dem String "\n" einerseits und dem String "\\n" und
dem Raw-String r"\n" andererseits, wenn man mit re.VERBOSE (siehe Rezept 3.4) den
Freiform-Modus aktiviert (erläutert in Rezept 2.18). "\n" ist ein String mit einem literalen
Zeilenumbruch, der als Whitespace ignoriert wird. "\\n" und r"\n" sind Strings mit dem
Regex-Token ‹\n›, das zu einem Newline passt.
Im Freiform-Modus sind Raw-Strings mit drei Anführungszeichen als Begrenzer (zum
Beispiel r"""\n""") die beste Wahl, da sie über mehrere Zeilen reichen können. Zudem
wird ‹\n› nicht auf String-Ebene interpretiert, sodass es von der Regex-Engine genutzt
werden kann, um einen Zeilenumbruch zu finden.

Ruby
In Ruby werden reguläre Ausdrücke am besten mit der speziellen Syntax für das Dekla-
rieren literaler regulärer Ausdrücke erstellt. Setzen Sie Ihren regulären Ausdruck einfach
zwischen zwei Schrägstriche. Wenn die Regex selbst einen Schrägstrich enthält, maskie-
ren Sie ihn mit einem Backslash.
Wenn Sie keine Schrägstriche in Ihrer Regex maskieren wollen, können Sie Ihrem regulä-
ren Ausdruck ein %r voranstellen und dann ein beliebiges Satzzeichen als Begrenzer nut-
zen.
Es ist zwar möglich, ein Regexp aus einem String zu erstellen, aber das ist bei literalen
Regexes nicht sehr sinnvoll. Sie müssten dann Anführungszeichen und Backslashs mas-
kieren, was im Allgemeinen zu einem ganzen Wald von Backslashs führt.

Ruby ähnelt hier sehr stark JavaScript, nur dass die Klasse in Ruby Regexp
heißt, während sie in JavaScript den Namen RegExp (in CamelCase) trägt.

114 | Kapitel 3: Mit regulären Ausdrücken programmieren


Siehe auch
Rezept 2.3 erklärt, wie Zeichenklassen funktionieren und warum im regulären Ausdruck
zwei Backslashs erforderlich sind, um in der Zeichenklasse nur einen zu nutzen.
Rezept 3.4 erklärt, wie man die Optionen für reguläre Ausdrücke setzt. In manchen Pro-
grammiersprachen ist das Teil des literalen regulären Ausdrucks.

3.2 Importieren der Regex-Bibliothek


Problem
Um reguläre Ausdrücke in Ihrer Anwendung nutzen zu können, wollen Sie die Regex-
Bibliothek oder den entsprechenden Namensraum in Ihren Quellcode importieren.

Bei den weiteren Quellcodeschnipseln, die in diesem Buch verwendet wer-


den, wird davon ausgegangen, dass das schon geschehen ist (sofern nötig).

Lösung
C#
using System.Text.RegularExpressions;

VB.NET
Imports System.Text.RegularExpressions

Java
import java.util.regex.*;

Python
import re

Diskussion
In manche Programmiersprachen wurde die Unterstützung regulärer Ausdrücke bereits
eingebaut. Dort müssen Sie nichts tun, um Regexes nutzen zu können. Bei anderen Spra-
chen wird die Funktionalität über eine Bibliothek bereitgestellt, die mit einer entspre-
chenden Anweisung in Ihrem Quellcode importiert werden muss. Einige Sprachen bieten
gar keine Unterstützung regulärer Ausdrücke an. Dort müssen Sie selbst eine entspre-
chende Funktionalität kompilieren und linken.

3.2 Importieren der Regex-Bibliothek | 115


C#
Wenn Sie die using-Anweisung am Anfang Ihrer C#-Quellcodedatei eintragen, können
Sie direkt auf die Klassen mit der Regex-Funktionalität verweisen, ohne jedes Mal ihren
vollständig qualifizierten Namen angeben zu müssen. So können Sie zum Beispiel
Regex() statt System.Text.RegularExpressions.Regex() schreiben.

VB.NET
Tragen Sie die Imports-Anweisung am Anfang Ihrer VB.NET-Quellcodedatei ein, können
Sie direkt auf die Klassen mit der Regex-Funktionalität verweisen, ohne jedes Mal ihren
vollständig qualifizierten Namen angeben zu müssen. So können Sie zum Beispiel
Regex() statt System.Text.RegularExpressions.Regex() schreiben.

Java
Sie müssen das Paket java.util.regex in Ihre Anwendung importieren, um die bei Java
mitgelieferte Bibliothek für reguläre Ausdrücke nutzen zu können.

JavaScript
In JavaScript ist die Unterstützung regulärer Ausdrücke schon eingebaut und direkt ver-
fügbar.

PHP
Die preg-Funktionen sind bereits eingebaut und ab PHP 4.2.0 immer verfügbar.

Perl
Die Unterstützung regulärer Ausdrücke ist in Perl eingebaut und immer verfügbar.

Python
Sie müssen das Modul re in Ihr Skript importieren, um die Regex-Funktionen von
Python nutzen zu können.

Ruby
Die Unterstützung regulärer Ausdrücke ist in Ruby eingebaut und immer verfügbar.

116 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.3 Erstellen eines Regex-Objekts
Problem
Sie wollen ein Objekt für einen regulären Ausdruck instantiieren oder stattdessen einen
regulären Ausdruck so kompilieren, dass er in Ihrer ganzen Anwendung effizient genutzt
werden kann.

Lösung
C#
Wenn Sie wissen, dass die Regex korrekt ist:
Regex regexObj = new Regex("Regex-Muster");

Wenn die Regex vom Endanwender angegeben wird (UserInput sei eine String-Variable):
try {
Regex regexObj = new Regex(UserInput);
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

VB.NET
Wenn Sie wissen, dass die Regex korrekt ist:
Dim RegexObj As New Regex("Regex-Muster")

Wenn die Regex vom Endanwender angegeben wird (UserInput sei eine String-Variable):
Try
Dim RegexObj As New Regex(UserInput)
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Java
Wenn Sie wissen, dass die Regex korrekt ist:
Pattern regex = Pattern.compile("Regex-Muster");

Wenn die Regex vom Endanwender angegeben wird (userInput sei eine String-Variable):
try {
Pattern regex = Pattern.compile(userInput);
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
}

3.3 Erstellen eines Regex-Objekts | 117


Um die Regex auf einen String anzuwenden, erzeugen Sie einen Matcher:
Matcher regexMatcher = regex.matcher(subjectString);

Um die Regex auf einen anderen String anzuwenden, können Sie, wie eben gezeigt, einen
neuen Matcher erstellen oder einen bestehenden wiederverwenden:
regexMatcher.reset(anotherSubjectString);

JavaScript
Literaler regulärer Ausdruck in Ihrem Code:
var myregexp = /Regex-Muster/;

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der
Variablen userinput abgelegt ist:
var myregexp = new RegExp(userinput);

Perl
$myregex = qr/Regex-Muster/

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der
Variablen $userinput abgelegt ist:
$myregex = qr/$userinput/

Python
reobj = re.compile("Regex-Muster")

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der
Variablen userinput abgelegt ist:
reobj = re.compile(userinput)

Ruby
Literaler regulärer Ausdruck in Ihrem Code:
myregexp = /Regex-Muster/;

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der
Variablen userinput abgelegt ist:
myregexp = Regexp.new(userinput);

Diskussion
Bevor die Regex-Engine einen regulären Ausdruck auf einen String anwenden kann, muss
der Ausdruck kompiliert werden. Das geschieht, während Ihre Anwendung ausgeführt
wird. Der Regex-Konstruktor oder die Kompilierungsfunktion parst den String, der Ihren
regulären Ausdruck enthält, und wandelt ihn in eine Baumstruktur oder einen endlichen

118 | Kapitel 3: Mit regulären Ausdrücken programmieren


Automaten um. Bei der eigentlichen Musterüberprüfung wird dieser Baum beziehungs-
weise dieser Automat durchlaufen, während der String überprüft wird. Programmier-
sprachen, die literale reguläre Ausdrücke unterstützen, kompilieren den regulären
Ausdruck, wenn die Ausführungsabfolge den entsprechenden Regex-Operator erreicht.

.NET
In C# und VB.NET hält die .NET-Klasse System.Text.RegularExpressions.Regex einen
kompilierten regulären Ausdruck. Der einfachste Konstruktor erwartet nur einen Para-
meter: einen String mit Ihrem regulären Ausdruck.
Wenn es einen Syntaxfehler im regulären Ausdruck gibt, wirft der Regex()-Konstruktor
eine ArgumentException. Der Exception-Text gibt dann genauer Auskunft darüber, was
für ein Fehler aufgetreten ist. Es ist wichtig, diese Exception abzufangen, wenn der regu-
läre Ausdruck vom Anwender angegeben wird. Zeigen Sie dann den Exception-Text an
und bitten Sie den Anwender, den Ausdruck zu korrigieren. Ist Ihr regulärer Ausdruck als
String-Literal hartkodiert, können Sie sich das Fangen der Exception sparen, falls Sie ein
Tool nutzen, durch das sichergestellt ist, dass diese Codezeile keinen Fehler werfen wird.
Der Zustand oder Modus kann sich nicht so ändern, dass die gleiche literale Regex ein-
mal kompiliert werden kann und einmal nicht. Beachten Sie, dass die Exception bei einer
fehlerhaften Regex beim Ausführen Ihres Programms auftreten wird, nicht beim Kompi-
lieren.
Sie sollten ein Regex-Objekt erstellen, wenn Sie den regulären Ausdruck in einer Schleife
oder zumindest wiederholt in Ihrer Anwendung nutzen wollen. Durch das Erstellen des
Regex-Objekts gibt es keinen zusätzlichen Overhead. Der statische Member der Regex-
Klasse, der die Regex als String-Parameter übernimmt, erzeugt so oder so intern ein
Regex-Objekt, daher können Sie das auch in Ihrem Code machen und sich eine Referenz
auf das Objekt merken.
Wenn Sie vorhaben, die Regex nur ein paar wenige Male zu nutzen, können Sie stattdes-
sen auch die statischen Member der Regex-Klasse nutzen, um sich eine Codezeile zu spa-
ren. Die statischen Regex-Member werfen das intern erzeugte Regex-Objekt nicht sofort
wieder weg, stattdessen verwalten sie einen Cache mit den 15 am häufigsten genutzten
regulären Ausdrücken. Sie können die Größe des Caches durch das Setzen der Eigen-
schaft Regex.CacheSize anpassen. Im Cache wird nach der Regex gesucht, indem der
Regex-String verglichen wird. Aber verlassen Sie sich nicht komplett auf den Cache.
Wenn Sie viele Regex-Objekte immer wieder benötigen, bauen Sie lieber einen eigenen
Cache auf, den Sie dann auch effizienter durchsuchen können.

Java
In Java verwaltet die Pattern-Klasse einen kompilierten regulären Ausdruck. Sie können
Objekte dieser Klasse mit der Klassenfabrik Pattern.compile() erstellen. Dafür ist nur ein
Parameter notwendig: ein String mit Ihrem regulären Ausdruck.

3.3 Erstellen eines Regex-Objekts | 119


Wenn es im regulären Ausdruck einen Syntaxfehler gibt, wirft die Pattern.compile()-
Fabrik eine PatternSyntaxException. Der Exception-Text beschreibt genauer, was für ein
Fehler aufgetreten ist. Es ist wichtig, diese Exception abzufangen, wenn der reguläre Aus-
druck aus einer Benutzereingabe stammt. Zeigen Sie dann die Fehlermeldung an und bit-
ten Sie den Anwender, den regulären Ausdruck zu korrigieren. Ist Ihr regulärer Ausdruck
als String-Literal hartkodiert, können Sie sich das Fangen der Exception sparen, falls Sie
ein Tool nutzen, durch das sichergestellt ist, dass diese Codezeile keinen Fehler werfen
wird. Der Zustand oder Modus kann sich nicht so ändern, dass die gleiche literale Regex
einmal kompiliert werden kann und einmal nicht. Beachten Sie, dass die Exception bei
einer fehlerhaften Regex beim Ausführen Ihres Programms auftreten wird, nicht beim
Kompilieren.
Sofern Sie eine Regex nicht nur einmal nutzen wollen, sollten Sie ein Pattern-Objekt
erstellen, statt die statischen Member der Klasse String zu verwenden. Das erfordert zwar
ein paar zusätzliche Codezeilen, aber der Code wird effizienter ablaufen. Die statischen
Aufrufe kompilieren Ihre Regex jedes Mal neu. Tatsächlich bietet Java statische Aufrufe
nur für ein paar wenige, sehr einfache Regex-Aufgaben an.
Ein Pattern-Objekt speichert nur einen kompilierten regulären Ausdruck und führt kei-
nerlei echte Aufgaben aus. Die eigentliche Musterfindung wird durch die Klasse Matcher
ausgeführt. Um einen Matcher zu erstellen, rufen Sie die Methode matcher() für Ihren
kompilierten regulären Ausdruck auf. Übergeben Sie dabei den Ausgangstext als Argu-
ment an matcher().
Rufen matcher() so oft auf, wie Sie den gleichen regulären Ausdruck auf unterschiedliche
Strings anwenden wollen. Sie können mit mehreren Matchern für die gleiche Regex
arbeiten, solange Sie alles in einem einzelnen Thread halten. Die Klassen Pattern und
Matcher sind nicht Thread-sicher. Wenn Sie die gleiche Regex in mehreren Threads nut-
zen wollen, rufen Sie in jedem Pattern.compile() auf.
Haben Sie eine Regex auf einen String angewendet und möchten die gleiche Regex auf
einen anderen String anwenden, können Sie das Matcher-Objekt wiederverwenden,
indem Sie reset() aufrufen. Übergeben Sie den neuen Ausgangstext als Argument. Das
ist effizienter als das Erstellen eines neuen Matcher-Objekts. reset() liefert den gleichen
Matcher zurück, für den Sie es aufgerufen haben, sodass Sie in einer Zeile einen Matcher
zurücksetzen und wieder anwenden können, zum Beispiel regexMatcher.reset(next-
String).find().

JavaScript
Die in Rezept 3.2 gezeigte Notation für literale reguläre Ausdrücke erstellt immer ein
neues Regex-Objekt. Um dasselbe Objekt mehrfach zu verwenden, weisen Sie es einfach
einer Variablen zu.
Wenn Sie einen regulären Ausdruck in einer String-Variablen abgelegt haben (weil Sie
zum Beispiel den Anwender gebeten haben, einen regulären Ausdruck einzugeben), ver-
wenden Sie den Konstruktor RegExp(), um den regulären Ausdruck zu kompilieren.

120 | Kapitel 3: Mit regulären Ausdrücken programmieren


Beachten Sie, dass der reguläre Ausdruck innerhalb des Strings nicht durch Schrägstriche
begrenzt wird. Diese Schrägstriche sind Teil der Notation für literale RegExp-Objekte in
JavaScript und gehören nicht zum regulären Ausdruck selbst.

Da das Zuweisen einer literalen Regex zu einer Variablen so einfach ist,


lassen die meisten JavaScript-Lösungen in diesem Kapitel diese Codezeile
weg und verwenden den literalen regulären Ausdruck direkt. In Ihrem
eigenen Code sollten Sie die Regex einer Variablen zuweisen und diese
dann verwenden, falls Sie sie mehrfach benötigen. Dadurch verbessert sich
die Performance, und Ihr Code lässt sich leichter warten.

PHP
PHP bietet keine Möglichkeit, einen kompilierten regulären Ausdruck in einer Variablen
abzulegen. Immer wenn Sie etwas mit einer Regex tun wollen, müssen Sie sie als String
an eine der preg-Funktionen übergeben.
Die preg-Funktionen verwalten intern einen Cache von bis zu 4.096 kompilierten regulä-
ren Ausdrücken. Auch wenn die Hash-basierte Cache-Suche nicht so schnell wie eine
Referenz auf eine Variable ist, ist der Performanceverlust dennoch weit geringer als bei
einem regelmäßigen Neukompilieren des gleichen regulären Ausdrucks. Wenn der
Cache voll ist, wird die Regex zuerst entfernt, deren Kompilierungszeitpunkt am weites-
ten zurückliegt.

Perl
Sie können den „Quote-Regex“-Operator nutzen, um einen regulären Ausdruck zu kom-
pilieren und einer Variablen zuzuweisen. Er nutzt die gleiche Syntax wie der in
Rezept 3.1 beschriebene Mustererkennungsoperator, nur dass er mit den Buchstaben qr
und nicht mit m beginnt.
Perl ist im Allgemeinen beim erneuten Verwenden vorher kompilierter regulärer Ausdrü-
cke ziemlich effizient. Daher verwenden wir in diesem Kapitel in den Codebeispielen den
Operator qr// nicht. Nur in Rezept 3.5 wird gezeigt, wie er funktioniert.
qr// ist dann nützlich, wenn Sie Variablen im regulären Ausdruck auswerten oder wenn
Sie den kompletten regulären Ausdruck als String erhalten (zum Beispiel aus einer Benut-
zereingabe). Mit qr/$regexstring/ können Sie Einfluss darauf nehmen, wann die Regex
neu kompiliert wird, um den neuen Inhalt von $regexstring widerzuspiegeln. m/$regex-
string/ würde die Regex jedes Mal neu kompilieren, während sie mit m/$regexstring/o
niemals neu kompiliert würde. Rezept 3.4 beschreibt die Option /o.

Python
Die Funktion compile() aus Pythons Modul re erwartet einen String mit Ihrem regulären
Ausdruck und liefert ein Objekt mit der kompilierten Version zurück.

3.3 Erstellen eines Regex-Objekts | 121


Sie sollten compile() explizit aufrufen, wenn Sie die gleiche Regex mehrfach nutzen wol-
len. Alle Funktionen im Modul re rufen zunächst compile() und dann die von Ihnen
gewünschte Funktion mit dem kompilierten Regex-Objekt auf.
Die Funktion compile() merkt sich die letzten 100 regulären Ausdrücke, die sie kompi-
liert hat. Das erübrigt eine erneute Dictionary-Suche nach Ausdrücken, die in den 100
zuletzt genutzten regulären Ausdrücken enthalten waren. Wenn der Cache voll ist, wird
er komplett geleert.
Wenn die Performance kein Thema ist, ist der Cache völlig ausreichend, und Sie können
die Funktionen im Modul re direkt nutzen. Aber wenn etwas zeitkritisch ist, ist ein Auf-
ruf von compile() anzuraten.

Ruby
Die in Rezept 3.2 gezeigte Notation für literale reguläre Ausdrücke erzeugt immer ein
neues Regex-Objekt. Um dasselbe Objekt mehrfach verwenden zu können, weisen Sie es
einfach einer Variablen zu.
Wenn Sie einen regulären Ausdruck haben, der in einer String-Variablen gespeichert ist
(weil Sie zum Beispiel den Anwender gebeten haben, einen regulären Ausdruck einzuge-
ben), nutzen Sie die Regexp.new()-Fabrik oder ihr Synonym Regexp.compile(), um den
regulären Ausdruck zu kompilieren. Beachten Sie, dass der reguläre Ausdruck innerhalb
des Strings nicht durch Schrägstriche eingefasst ist. Diese Schrägstriche sind Teil der
Notation für literale Regexp-Objekte in Ruby und gehören nicht zum regulären Ausdruck
selbst.

Da das Zuweisen einer literalen Regex zu einer Variablen so einfach ist,


lassen die meisten Ruby-Lösungen in diesem Kapitel diese Codezeile weg
und verwenden den literalen regulären Ausdruck direkt. In Ihrem eigenen
Code sollten Sie die Regex einer Variablen zuweisen und diese dann ver-
wenden, falls Sie sie mehrfach benötigen. Dadurch verbessert sich die Per-
formance, und Ihr Code lässt sich leichter warten.

Einen regulären Ausdruck in CIL kompilieren


C#
Regex regexObj = new Regex("Regex-Muster", RegexOptions.Compiled);

VB.NET
Dim RegexObj As New Regex("Regex-Muster", RegexOptions.Compiled)

122 | Kapitel 3: Mit regulären Ausdrücken programmieren


Diskussion
Wenn Sie in .NET ein Regex-Objekt erstellen, ohne Optionen zu übergeben, wird der
reguläre Ausdruck so kompiliert, wie wir es in „Diskussion“ auf Seite 118 beschrieben
haben. Geben Sie als zweiten Parameter für den Regex()-Konstruktor RegexOptions.Com-
piled mit, verhält sich die Regex-Klasse etwas anders. Sie kompiliert dann Ihren regulären
Ausdruck bis hinunter in die CIL, auch bekannt als MSIL. CIL steht für Common Inter-
mediate Language, eine Programmiersprache, die deutlich enger an Assembler ausgerich-
tet ist als an C# oder Visual Basic. Alle .NET-Compiler erzeugen CIL. Wenn Ihre
Anwendung das erste Mal ausgeführt wird, kompiliert das .NET Framework die CIL
dann zu Maschinencode, der vom Computer direkt nutzbar ist.
Der Vorteil, einen regulären Ausdruck mit RegexOptions.Compiled zu kompilieren, ist
seine bis zu zehnfach höhere Ausführungsgeschwindigkeit, verglichen mit einem normal
kompilierten regulären Ausdruck. Nachteil ist, dass das Kompilieren bis zu zwei Größen-
ordnungen langsamer sein kann als das reine Parsen des Regex-Strings in einen Baum.
Der CIL-Code wird zudem Teil Ihrer Anwendung, bis sie beendet ist. CIL-Code nutzt
keine Garbage Collection.
Verwenden Sie RegexOptions.Compiled nur dann, wenn ein regulärer Ausdruck entweder
so komplex ist oder so umfangreiche Textmengen bearbeiten muss, dass der Anwender
eine deutliche Wartezeit bemerkt. Der Overhead durch das Kompilieren und Assemblie-
ren lohnt sich nicht für Regexes, die in Sekundenbruchteilen ausgeführt werden.

Siehe auch
Rezepte 3.1, 3.2 und 3.4.

3.4 Optionen für reguläre Ausdrücke setzen


Problem
Sie wollen einen regulären Ausdruck mit allen verfügbaren Modi kompilieren – Freiform-
Modus sowie die Modi Groß-/Kleinschreibung ignorieren, Punkt passt zu Zeilenumbruch
und Zirkumflex und Dollar passen zu Zeilenumbruch.

Lösung
C#
Regex regexObj = new Regex("Regex-Muster",
RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase |
RegexOptions.Singleline | RegexOptions.Multiline);

3.4 Optionen für reguläre Ausdrücke setzen | 123


VB.NET
Dim RegexObj As New Regex("Regex-Muster",
RegexOptions.IgnorePatternWhitespace Or RegexOptions.IgnoreCase Or
RegexOptions.Singleline Or RegexOptions.Multiline)

Java
Pattern regex = Pattern.compile("Regex-Muster",
Pattern.COMMENTS | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE |
Pattern.DOTALL | Pattern.MULTILINE);

JavaScript
Literaler regulärer Ausdruck in Ihrem Code:
var myregexp = /Regex-Muster/im;

Regulärer Ausdruck aus einer Benutzereingabe als String:


var myregexp = new RegExp(userinput, "im");

PHP
regexstring = '/Regex-Muster/simx';

Perl
m/Regex-Muster/simx;

Python
reobj = re.compile("Regex-Muster",
re.VERBOSE | re.IGNORECASE |
re.DOTALL | re.MULTILINE)

Ruby
Literaler regulärer Ausdruck in Ihrem Code:
myregexp = /Regex-Muster/mix;

Regulärer Ausdruck aus einer Benutzereingabe als String:


myregexp = Regexp.new(userinput,
Regexp::EXTENDED or Regexp::IGNORECASE or
Regexp::MULTILINE);

Diskussion
Viele der regulären Ausdrücke in diesem Buch und auch solche, die Sie woanders finden,
sind dafür geschrieben, mit bestimmten Regex-Modi zu funktionieren. Es gibt vier
grundlegende Basismodi, die nahezu alle modernen Regex-Varianten unterstützen. Lei-

124 | Kapitel 3: Mit regulären Ausdrücken programmieren


der nutzen einige Varianten inkonsistente und verwirrende Namen für die Optionen,
durch die die Modi implementiert werden. Nutzt man einen falschen Modus, funktio-
niert der reguläre Ausdruck im Allgemeinen nicht mehr so, wie man sich das vorgestellt
hat.
Alle Lösungen in diesem Rezept nutzen Schalter oder Optionen, die von der Program-
miersprache oder der Regex-Klasse zum Setzen der Modi verwendet werden. Eine andere
Möglichkeit, Modi zu setzen, ist die Verwendung von Modus-Modifikatoren innerhalb
der regulären Ausdrücke. Modus-Modifikatoren in der Regex überschreiben immer die
Optionen oder Schalter, die außerhalb des regulären Ausdrucks gesetzt wurden.

.NET
Der Konstruktor Regex() besitzt einen zweiten, optionalen Parameter für die Regex-
Optionen. Sie finden die verfügbaren Optionen in der Enumeration RegexOptions.
Freiform: RegexOptions.IgnorePatternWhitespace
Groß-/Kleinschreibung ignorieren: RegexOptions.IgnoreCase
Punkt passt zu Zeilenumbruch: RegexOptions.Singleline
Zirkumflex und Dollar passen zu Zeilenumbruch: RegexOptions.Multiline

Java
Der Klassenfabrik Pattern.compile() kann man einen optionalen zweiten Parameter für
die Regex-Optionen mitgeben. Die Klasse Pattern definiert eine Reihe von Konstanten
für die verschiedenen Optionen. Sie können mehrere Optionen gleichzeitig setzen, indem
Sie sie durch den bitweisen Oder-Operator | verbinden.
Freiform: Pattern.COMMENTS
Groß-/Kleinschreibung ignorieren: Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
Punkt passt zu Zeilenumbruch: Pattern.DOTALL
Zirkumflex und Dollar passen zu Zeilenumbruch: Pattern.MULTILINE
Für das Ignorieren von Groß- und Kleinschreibung gibt es tatsächlich zwei Optionen, die
Sie beide setzen müssen, wenn Sie die Schreibweise komplett ignorieren wollen. Setzen
Sie nur Pattern.CASE_INSENSITIVE, werden lediglich die Buchstaben A bis Z unabhängig
von der Schreibweise gefunden. Setzen Sie beide Optionen, werden alle Zeichen aus allen
Schriftsystemen unabhängig von der Groß-/Kleinschreibung gefunden. Der einzige
Grund, Pattern.UNICODE_CASE nicht zu nutzen, ist der Performanceaspekt, wenn Sie schon
im Voraus wissen, dass Sie nur mit ASCII-Text arbeiten. Wenn Sie innerhalb Ihres regu-
lären Ausdrucks Modus-Modifikatoren verwenden, nutzen Sie ‹(?i)› für das Ignorieren
von Groß- und Kleinschreibung nur für ASCII-Zeichen und ‹(?iu)› für ein vollständiges
Ignorieren.

3.4 Optionen für reguläre Ausdrücke setzen | 125


JavaScript
In JavaScript können Sie Optionen festlegen, indem Sie ein oder mehrere Zeichen nach
dem zweiten Schrägstrich an das RegExp-Literal anhängen. Wenn in Büchern oder auf
Webseiten über diese Schalter geschrieben wird, geschieht das meist in der Form /i und
/m, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen. Es sind keine
zusätzlichen Schrägstriche anzugeben, wenn man die Modusschalter nutzt.
Verwenden Sie den Konstruktor RegExp(), um einen String zu einem regulären Ausdruck
zu kompilieren, können Sie einen optionalen zweiten Parameter mit den Schaltern über-
geben. Dabei sollte es sich um einen String mit den Buchstaben der Optionen handeln,
die Sie setzen wollen. Fügen Sie hier keine Schrägstriche ein.
Freiform: nicht durch JavaScript unterstützt
Groß-/Kleinschreibung ignorieren: /i
Punkt passt zu Zeilenumbruch: nicht durch JavaScript unterstützt
Zirkumflex und Dollar passen zu Zeilenumbruch: /m

PHP
In Rezept 3.1 ist beschrieben, dass literale reguläre Ausdrücke bei den preg-Funktionen
von PHP durch zwei Satzzeichen begrenzt sein müssen – im Allgemeinen Schrägstriche –
und dass alles als String zu formatieren ist. Sie können dabei Optionen angeben, indem
Sie ein oder mehrere Zeichen als Modifikatoren an das Ende des Strings stellen. Das
heißt, der Modifikator-Buchstabe kommt nach dem schließenden Regex-Begrenzer, aber
er muss sich immer noch innerhalb der Anführungszeichen des Strings befinden. Wenn
in Büchern oder auf Webseiten über diese Schalter geschrieben wird, geschieht das meist
in der Form /x, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen und
der Begrenzer zwischen Regex und Modifikator gar kein Schrägstrich sein muss.
Freiform: /x
Groß-/Kleinschreibung ignorieren: /i
Punkt passt zu Zeilenumbruch: /s
Zirkumflex und Dollar passen zu Zeilenumbruch: /m

Perl
Sie können Optionen für reguläre Ausdrücke festlegen, indem Sie einen oder mehrere aus
einem Zeichen bestehende Modifikatoren an das Ende des Operators zur Mustererken-
nung oder zum Ersetzen anhängen. Wenn in Büchern oder auf Webseiten über diese
Schalter geschrieben wird, geschieht das meist in der Form /x, obwohl die Schalter
eigentlich nur aus dem Zeichen selbst bestehen und der Begrenzer zwischen Regex und
Modifikator gar kein Schrägstrich sein muss.
Freiform: /x
Groß-/Kleinschreibung ignorieren: /i

126 | Kapitel 3: Mit regulären Ausdrücken programmieren


Punkt passt zu Zeilenumbruch: /s
Zirkumflex und Dollar passen zu Zeilenumbruch: /m

Python
Der (im obigen Rezept erläuterten) Funktion compile() kann ein optionaler zweiter Para-
meter für die Optionen für den regulären Ausdruck mitgegeben werden. Sie können die-
sen Parameter aufbauen, indem Sie die im Modul re definierten Konstanten mit dem
Operator | kombinieren. Viele der anderen Funktionen im Modul re, die einen literalen
regulären Ausdruck als Parameter nutzen, bieten auch einen (letzten) optionalen Parame-
ter an, mit dem die Optionen übergeben werden können.
Die Konstanten für die Regex-Optionen gibt es immer paarweise. Jede Option kann ent-
weder als Konstante mit einem vollständigen Namen oder mit einem einfachen Buchsta-
ben genutzt werden. Die Funktionalität ist die gleiche. Einziger Unterschied ist, dass der
vollständige Name Ihren Code für Entwickler leichter lesbar macht, die mit der Buchsta-
bensuppe der Regex-Optionen nicht so vertraut sind. Die aufgeführten Ein-Buchstaben-
Optionen entsprechen denen von Perl.
Freiform: re.VERBOSE oder re.X
Groß-/Kleinschreibung ignorieren: re.IGNORECASE oder re.I
Punkt passt zu Zeilenumbruch: re.DOTALL oder re.S
Zirkumflex und Dollar passen zu Zeilenumbruch: re.MULTILINE oder re.M

Ruby
In Ruby können Sie die Optionen angeben, indem Sie dem Regexp-Literal einen oder
mehrere Buchstaben als Schalter anhängen. Wenn in Büchern oder auf Webseiten über
diese Schalter geschrieben wird, geschieht das meist in der Form /i und /m, obwohl die
Schalter eigentlich nur aus dem Zeichen selbst bestehen. Es sind keine zusätzlichen
Schrägstriche anzugeben, wenn man die Modusschalter nutzt.
Verwenden Sie die Regexp.new()-Fabrik, um einen String in einen regulären Ausdruck zu
kompilieren, können Sie einen optionalen zweiten Parameter mit Schaltern an den Kons-
truktor übergeben. Der zweite Parameter sollte entweder den Wert nil haben, um alle
Optionen abzuschalten, oder aus einer Kombination der Konstanten aus der Klasse
Regexp bestehen, die mit dem Operator or kombiniert wurden.
Freiform: /r oder Regexp::EXTENDED
Groß-/Kleinschreibung ignorieren: /i oder Regexp::IGNORECASE
Punkt passt zu Zeilenumbruch : /m oder Regexp::MULTILINE. Ruby nutzt hier tatsächlich „m“
und „Multiline“, obwohl alle anderen Varianten „s“ oder „Single Line“ für Punkt
passt zu Zeilenumbruch verwenden.
Zirkumflex und Dollar passen zu Zeilenumbruch: Zirkumflex und Dollar passen in Ruby immer
auf Zeilenumbrüche. Sie können dieses Verhalten nicht abschalten. Mit ‹\A› und
‹\Z› finden Sie den Anfang oder das Ende des Ausgangstexts.

3.4 Optionen für reguläre Ausdrücke setzen | 127


Weitere sprachspezifische Optionen
.NET
RegexOptions.ExplicitCapture sorgt dafür, dass alle Gruppen, mit Ausnahme der benann-
ten Gruppen, nicht-einfangend sind. Mit dieser Option ist ‹(Gruppe)› das Gleiche wie
‹(?:Gruppe)›. Wenn Sie Ihre einfangenden Gruppen immer benennen, schalten Sie diese
Option ein, damit Ihr regulärer Ausdruck effizienter wird, ohne die ‹(?:Gruppe)›-Syntax
nutzen zu müssen. Anstatt RegexOptions.ExplicitCapture zu verwenden, können Sie
diese Option auch aktivieren, indem Sie ‹(?n)› an den Anfang Ihres regulären Ausdrucks
stellen. In Rezept 2.9 erfahren Sie mehr über Gruppen. Rezept 2.11 erklärt benannte
Gruppen.
Wenn Sie den gleichen regulären Ausdruck in Ihrem .NET-Code und in JavaScript-Code
nutzen und Sie sicherstellen wollen, dass er sich in beiden Umgebungen gleich verhält,
können Sie RegexOptions.ECMAScript nutzen. Das ist insbesondere dann nützlich, wenn
Sie die Clientseite einer Webanwendung in JavaScript und die Serverseite in ASP.NET
entwickeln. Der wichtigste Effekt, der durch diese Option eintritt, ist die Einschränkung
von \w und \d auf ASCII-Zeichen, so wie es in JavaScript auch der Fall ist.

Java
Eine in Java einmalige Option ist Pattern.CANON_EQ, mit der eine „kanonische Äquiva-
lenz“ ermöglicht wird. Wie in „Unicode-Graphem“ auf Seite 56 beschrieben, gibt es in
Unicode unterschiedliche Wege, diakritische Zeichen zu repräsentieren. Wenn Sie diese
Option aktivieren, wird Ihre Regex ein Zeichen finden, auch wenn es im Ausgangstext
anders kodiert wurde. So wird zum Beispiel die Regex ‹\u00E0› sowohl auf "\u00E0" als
auch auf "\u0061\u0300" passen, weil beide kanonisch äquivalent sind. Beide zeigen auf
dem Bildschirm ein „à“ an, sodass der Endanwender keinen Unterschied bemerkt. Ohne
die kanonische Äquivalenz passt die Regex ‹\u00E0› nicht zum String "\u0061\u0300". In
der Weise verhalten sich jedoch alle anderen Regex-Varianten aus diesem Buch.
Schließlich teilt Pattern.UNIX_LINES Java noch mit, nur ‹\n› von Punkt, Zirkumflex und
Dollarzeichen als echten Zeilenumbruch behandeln zu lassen. Standardmäßig werden
alle Unicode-Zeilenumbrüche als solche Zeichen angesehen.

JavaScript
Wenn Sie einen regulären Ausdruck wiederholt auf den gleichen String anwenden wollen
– zum Beispiel um alle Fundstellen zu durchlaufen oder um nicht nur die erste, sondern
alle Übereinstimmungen zu ersetzen –, geben Sie den „Global“-Schalter /g mit.

PHP
/u weist die PCRE an, sowohl den regulären Ausdruck als auch den Ausgangstext als
UTF-8-Strings zu interpretieren. Dieser Modifikator ermöglicht es zudem, Unicode-

128 | Kapitel 3: Mit regulären Ausdrücken programmieren


Regex-Tokens zu nutzen, wie zum Beispiel ‹\p{FFFF}› und ‹\p{L}›. Diese werden in
Rezept 2.7 erklärt. Ohne den Modifikator behandelt die PCRE jedes Byte als eigenes Zei-
chen, und die Unicode-Regex-Tokens führen zu einem Fehler.
/U vertauscht das „genügsame“ und das „gierige“ Verhalten von Quantoren mit und
ohne Fragezeichen. Normalerweise ist ‹.*› gierig und ‹.*?› genügsam. Mit der Option
/U ist ‹.*› genügsam und ‹.*?› gierig. Ich empfehle dringend, diese Option niemals zu
verwenden, da Programmierer, die später Ihren Code lesen und den Modifikator /U über-
sehen, komplett verwirrt werden können. Verwechseln Sie auch nicht /U mit /u, wenn Sie
diese Optionen im Code von anderen Leuten vorfinden. Regex-Modifikatoren reagieren
durchaus empfindlich auf eine unterschiedliche Schreibweise.

Perl
Wenn Sie einen regulären Ausdruck wiederholt auf den gleichen String anwenden wollen
– zum Beispiel um alle Fundstellen zu durchlaufen oder um nicht nur die erste, sondern
alle Übereinstimmungen zu ersetzen –, geben Sie den „Global“-Schalter /g mit.
Lassen Sie eine Variable in einer Regex auswerten – zum Beispiel m/Ich heiße $name/ –,
wird Perl den regulären Ausdruck jedes Mal neu kompilieren, wenn er angewendet wer-
den soll, da sich der Inhalt von $name geändert haben könnte. Sie können dieses Verhal-
ten mit dem Modifikator /o unterbinden. m/Ich heiße $name/o wird von Perl nur
kompiliert, wenn der Ausdruck das erste Mal benötigt wird, und dann immer wieder
angewandt. Wenn sich der Inhalt von $name ändert, wird die Regex diese Änderung nicht
widerspiegeln. In Rezept 3.3 finden Sie Informationen zum neuen Kompilieren der
Regex.

Python
Python bietet zwei zusätzliche Optionen an, die die Bedeutung der Wortgrenzen (siehe
Rezept 2.6), der Kurzzeichenklassen ‹\w›, ‹\d› und ‹\s› und ihrer negierten Gegenspieler
(siehe Rezept 2.3) ändern. Standardmäßig nutzen diese Tokens nur ASCII-Buchstaben,
Ziffern und Whitespace.
Die Option re.LOCALE oder re.L lässt diese Tokens abhängig vom aktuellen Locale agie-
ren. Das Locale bestimmt dann, welche Zeichen von den Regex-Tokens als Buchstaben,
Ziffern und Whitespace behandelt werden. Sie sollten diese Option angeben, wenn der
Ausgangstext kein Unicode-String ist und Sie zum Beispiel diakritische Zeichen als Buch-
staben behandelt wissen wollen.
re.UNICODE oder re.U lässt diese Tokens auf den Unicode-Standard Rücksicht nehmen.
Alle Zeichen, die von Unicode als Buchstaben, Ziffern und Whitespace gekennzeichnet
sind, werden dann von den Regex-Tokens auch als solche behandelt. Sie sollten diese
Option nutzen, wenn es sich beim Ausgangstext um einen Unicode-String handelt.

3.4 Optionen für reguläre Ausdrücke setzen | 129


Ruby
Der Regexp.new()-Fabrik kann ein optionaler dritter Parameter übergeben werden, um
die String-Kodierung auszuwählen, die Ihr regulärer Ausdruck unterstützt. Wenn Sie
keine Kodierung für Ihren regulären Ausdruck angeben, wird die genommen, die auch
Ihre Quellcodedatei nutzt. Meist ist die von der Quellcodedatei genutzte Kodierung
schon die richtige.
Um eine Kodierung explizit auszuwählen, übergeben Sie für diesen Parameter ein einzel-
nes Zeichen. Groß- und Kleinschreibung ist nicht wichtig. Mögliche Werte sind:
n
Das steht für „None“. Jedes Byte in Ihrem String wird als einzelnes Zeichen behan-
delt. Nutzen Sie diese Kodierung für ASCII-Texte.
e
Verwendet die „EUC“-Kodierung für Sprachen aus dem ostasiatischen Raum.
s
Verwendet die japanische „Shift-JIS“-Kodierung.
u
Verwendet UTF-8, das ein bis vier Byte pro Zeichen nutzt und alle Sprachen im Uni-
code-Standard unterstützt (wozu alle wichtigen lebenden Sprachen gehören).
Wenn Sie einen literalen regulären Ausdruck verwenden, können Sie die Kodierung
durch die Modifikatoren /n, /e, /s und /u setzen. Dabei kann nur einer dieser Modifika-
toren für einen einzelnen regulären Ausdruck genutzt werden. Man kann sie aber mit den
Modifikatoren /x, /i und /m kombinieren.

Verwechseln Sie Rubys /s nicht mit dem von Perl, Java oder .NET. In
Ruby sorgt /s für die Anwendung der Shift-JIS-Kodierung. In Perl und den
meisten anderen Regex-Varianten wird damit der Modus „Punkt passt zu
Zeilenumbruch“ aktiviert. In Ruby erreichen Sie das wiederum mit /m.

Siehe auch
Die Auswirkungen der Modi werden detailliert in Kapitel 2 erläutert. Dieser Abschnitt
zeigt auch, wie man die Modus-Modifikatoren innerhalb von regulären Ausdrücken
nutzt:
Freiform: Rezept 2.18
Groß-/Kleinschreibung ignorieren: „Übereinstimmungen unabhängig von Groß- und Klein-
schreibung“ auf Seite 29 in Rezept 2.1
Punkt passt zu Zeilenumbruch: Rezept 2.4
Zirkumflex und Dollar passen zu Zeilenumbruch: Rezept 2.5

130 | Kapitel 3: Mit regulären Ausdrücken programmieren


Die Rezepte 3.1 und 3.3 beschreiben, wie Sie literale reguläre Ausdrücke in Ihrem Quell-
code verwenden und wie Sie Regex-Objekte erstellen. Die Regex-Optionen werden dabei
während des Erzeugens eines regulären Ausdrucks gesetzt.

3.5 Auf eine Übereinstimmung in einem Text prüfen


Problem
Sie wollen prüfen, ob für einen bestimmten regulären Ausdruck in einem bestimmten
String eine Übereinstimmung gefunden werden kann. Eine teilweise Übereinstimmung
ist ausreichend, zum Beispiel stimmt die Regex ‹RegexzMuster› teilweise mit Das Regex-
Muster kann gefunden werden überein. Sie kümmern sich nicht um die Details der Über-
einstimmung, Sie wollen bloß wissen, ob die Regex im String gefunden wird.

Lösung
C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diesen stati-
schen Aufruf verwenden:
bool foundMatch = Regex.IsMatch(subjectString, "Regex-Muster");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
bool foundMatch = false;
try {
foundMatch = Regex.IsMatch(subjectString, UserInput);
} catch (ArgumentNullException ex) {
// Regex und Text müssen vorhanden sein
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Regex regexObj = new Regex("Regex-Muster");
bool foundMatch = regexObj.IsMatch(subjectString);

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per
Exception Handling absichern:
bool foundMatch = false;
try {
Regex regexObj = new Regex(UserInput);
try {
foundMatch = regexObj.IsMatch(subjectString);
} catch (ArgumentNullException ex) {
// Regex und Text müssen vorhanden sein

3.5 Auf eine Übereinstimmung in einem Text prüfen | 131


}
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diesen stati-
schen Aufruf verwenden:
Dim FoundMatch = Regex.IsMatch(SubjectString, "Regex-Muster")

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
Dim FoundMatch As Boolean
Try
FoundMatch = Regex.IsMatch(SubjectString, UserInput)
Catch ex As ArgumentNullException
'Regex und Text müssen vorhanden sein
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim RegexObj As New Regex("Regex-Muster")
Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Der Aufruf von IsMatch() sollte als Parameter nur SubjectString nutzen. Achten Sie
darauf, den Aufruf für die Instanz RegexObj durchzuführen, nicht für die Klasse Regex:
Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per
Exception Handling absichern:
Dim FoundMatch As Boolean
Try
Dim RegexObj As New Regex(UserInput)
Try
FoundMatch = Regex.IsMatch(SubjectString)
Catch ex As ArgumentNullException
'Regex und Text müssen vorhanden sein
End Try
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Java
Um auf eine teilweise Übereinstimmung zu testen, muss man einen Matcher erzeugen:
Pattern regex = Pattern.compile("Regex-Muster");
Matcher regexMatcher = regex.matcher(subjectString);
boolean foundMatch = regexMatcher.find();

132 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wenn die Regex vom Benutzer eingegeben wird, sollten Sie Exception Handling berück-
sichtigen:
boolean foundMatch = false;
try {
Pattern regex = Pattern.compile(UserInput);
Matcher regexMatcher = regex.matcher(subjectString);
foundMatch = regexMatcher.find();
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
}

JavaScript
if (/Regex-Muster/.test(subject)) {
// Übereinstimmung gefunden
} else {
// Keine Übereinstimmung
}

PHP
if (preg_match('/Regex-Muster/', $subject)) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

Perl
Wenn sich der Text in der Spezial-Variablen $_ befindet:
if (m/Regex-Muster/) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

Wenn sich der Text in der Variablen $subject befindet:


if ($subject =~ m/Regex-Muster/) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

Beim Verwenden eines vorkompilierten regulären Ausdrucks:


$regex = qr/Regex-Muster/;
if ($subject =~ $regex) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

3.5 Auf eine Übereinstimmung in einem Text prüfen | 133


Python
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diese globale
Funktion verwenden:
if re.search("Regex-Muster", subject):
# Übereinstimmung gefunden
else:
# Keine Übereinstimmung

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile("Regex-Muster")
if reobj.search(subject):
# Übereinstimmung gefunden
else:
# Keine Übereinstimmung

Ruby
if subject =~ /Regex-Muster/
# Übereinstimmung gefunden
else
# Keine Übereinstimmung
end

Dieser Code macht genau das Gleiche:


if /Regex-Muster/ =~ subject
# Übereinstimmung gefunden
else
# Keine Übereinstimmung
end

Diskussion
Die grundlegendste Aufgabe eines regulären Ausdrucks ist das Prüfen, ob in einem String
eine Übereinstimmung gefunden werden kann. In den meisten Programmiersprachen
reicht eine teilweise Übereinstimmung aus, damit die entsprechende Funktion einen
Erfolg meldet. Die Übereinstimmungsfunktion durchläuft den gesamten Ausgangstext,
um herauszufinden, ob der reguläre Ausdruck einen Teil davon findet. Sobald es eine
Übereinstimmung gibt, liefert die Funktion true zurück. Den Wert false gibt sie nur
dann zurück, wenn sie das Ende des Strings erreicht, ohne eine Übereinstimmung gefun-
den zu haben.
Die Codebeispiele in diesem Rezept sind nützlich, wenn man prüfen will, ob ein String
bestimmte Daten enthält. Wollen Sie jedoch testen, ob ein String in seiner Gesamtheit
einem bestimmten Muster entspricht (zum Beispiel bei der Eingabekontrolle), ist das
nächste Rezept das richtige für Sie.

134 | Kapitel 3: Mit regulären Ausdrücken programmieren


C# und VB.NET
Die Klasse Regex stellt vier überladene Versionen der Methode IsMatch() bereit, von
denen zwei statisch sind. Damit ist es möglich, IsMatch() mit unterschiedlichen Parame-
tern aufzurufen. Der Ausgangstext ist immer der erste Parameter. In diesem String ver-
sucht der reguläre Ausdruck, eine Übereinstimmung zu finden. Dieser erste Parameter
darf nicht null sein, da IsMatch() ansonsten eine ArgumentNullException wirft.
Sie können den Test in einer einzelnen Codezeile durchführen, indem Sie Regex.IsMatch()
aufrufen, ohne ein Regex-Objekt zu erzeugen. Übergeben Sie einfach den regulären Aus-
druck als zweiten Parameter und die Regex-Optionen optional als dritten Parameter.
Wenn Ihr regulärer Ausdruck einen Syntaxfehler enthält, wird eine ArgumentException von
IsMatch() geworfen. Ist Ihre Regex gültig, wird der Aufruf bei einer Übereinstimmung mit
einem Teil des Strings true zurückgeben. Wenn keine Übereinstimmung gefunden werden
konnte, liefert die Funktion stattdessen false zurück.
Möchten Sie den gleichen regulären Ausdruck auf mehrere Strings anwenden, können Sie
Ihren Code effizienter machen, indem Sie zunächst ein Regex-Objekt erzeugen und dann
für dieses Objekt IsMatch() aufrufen. Der erste Parameter mit dem Ausgangstext ist der
einzig notwendige Parameter. Sie können optional einen zweiten Parameter angeben, in
dem die Position im String angegeben wird, ab der die Suche beginnen soll. Im Prinzip
handelt es sich bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck
am Anfang des Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den
String schon bis zu einer gewissen Position verarbeitet haben und nun prüfen wollen, ob
der Rest auch noch zu bearbeiten ist. Wenn Sie diese Zahl angeben, muss sie größer oder
gleich null und kleiner oder gleich der Länge des Strings sein. Ansonsten wirft IsMatch()
eine ArgumentOutOfRangeException.
Die statisch überladenen Varianten ermöglichen keine Angabe der Position, ab der im
String gesucht werden soll. Es gibt auch keine Version von IsMatch(), der mitgeteilt wer-
den kann, dass die Funktion nicht bis zum Ende des Strings zu suchen braucht. Wenn Sie
das erreichen wollen, können Sie Regex.Match("subject", start, stop) aufrufen und
dann die Eigenschaft Success des zurückgelieferten Match-Objekts auswerten. In
Rezept 3.8 finden Sie weitere Details dazu.

Java
Um zu prüfen, ob eine Regex einen String teilweise oder vollständig abbildet, instantiie-
ren Sie einen Matcher, wie in Rezept 3.3 beschrieben. Dann rufen Sie die Methode find()
für Ihren neu erstellten oder frisch zurückgesetzten Matcher auf.
Verwenden Sie nicht die Methoden String.matches(), Pattern.matches() oder
Matcher.matches(). Bei all diesen Methoden muss die Regex auf den gesamten String pas-
sen.

3.5 Auf eine Übereinstimmung in einem Text prüfen | 135


JavaScript
Um zu prüfen, ob ein regulärer Ausdruck auf einen Teil des Strings passt, rufen Sie die
Methode test() für Ihren regulären Ausdruck auf. Übergeben Sie den Ausgangs-String
als einzigen Parameter.
regexp.test() gibt true zurück, wenn der reguläre Ausdruck zu einem Teil oder der
Gesamtheit des Strings passt. Ansonsten gibt die Funktion false zurück.

PHP
Die Funktion preg_match() kann für eine ganze Reihe von Aufgaben genutzt werden. Die
einfachste Variante ist der Aufruf mit den beiden notwendigen Parametern: dem String
mit Ihrem regulären Ausdruck und dem String mit dem Text, auf den die Regex ange-
wendet werden soll. preg_match() liefert 1 zurück, wenn es eine Übereinstimmung gab,
und 0, wenn nichts gefunden wurde.
Weitere Rezepte weiter unten in diesem Kapitel erklären die optionalen Parameter, die
Sie an preg_match() übergeben können.

Perl
In Perl ist m// ein echter Regex-Operator, nicht nur ein Regex-Container. Wenn Sie m//
allein verwenden, greift er auf die Variable $_ als Ausgangstext zurück.
Wollen Sie den Mustererkennungsoperator auf den Inhalt einer anderen Variablen
anwenden, nutzen Sie den Bindungsoperator =~, um den Regex-Operator mit Ihrer Vari-
ablen zu verknüpfen. Durch das Binden der Regex an einen String wird die Regex direkt
ausgeführt. Der Mustererkennungsoperator liefert true zurück, wenn die Regex zu einem
Teil des Ausgangstexts passt, und false, wenn keine Übereinstimmung gefunden wurde.
Wenn Sie prüfen wollen, ob ein regulärer Ausdruck nicht auf einen String passt, können
Sie !~ nutzen, die negierte Version von =~.

Python
Die Funktion search() aus dem Modul re durchsucht einen String, um herauszufinden,
ob der reguläre Ausdruck auf einen Teil davon passt. Übergeben Sie Ihren regulären Aus-
druck als ersten Parameter und den Ausgangstext als zweiten Parameter. Optional kön-
nen Sie noch die Regex-Optionen als dritten Parameter mitgeben.
Die Funktion re.search() ruft re.compile() und dann die Methode search() für das kom-
pilierte Regex-Objekt auf. Diese Methode erhält nur einen einzigen Parameter: den Aus-
gangstext.
Wenn der reguläre Ausdruck eine Übereinstimmung findet, liefert search() eine Instanz
eines MatchObject zurück. Findet die Regex keine Übereinstimmung, liefert search() den
Wert None zurück. Wenn Sie den zurückgegebenen Wert in einer if-Anweisung auswerten,

136 | Kapitel 3: Mit regulären Ausdrücken programmieren


führt MatchObject zu True, während None zu False wird. Weitere Rezepte in diesem Kapitel
werden zeigen, wie Sie die Informationen in einem MatchObject verwenden können.

Bringen Sie search() nicht mit match() durcheinander. Sie können match()
nicht nutzen, um eine Übereinstimmung mitten in einem String zu finden.
Das nächste Rezept verwendet match().

Ruby
Der Operator =~ ist der Mustererkennungsoperator. Wenn Sie ihn zwischen einem regu-
lären Ausdruck und einem String einsetzen, wird damit die erste Übereinstimmung
durch die Regex gefunden. Der Operator gibt einen Integer-Wert mit der Position der
Übereinstimmung zurück. Wenn keine Übereinstimmung gefunden wurde, liefert er
stattdessen nil.
Dieser Operator ist in den beiden Klassen Regexp und String implementiert. In Ruby 1.8
ist es egal, welche Klasse Sie links und welche Sie rechts des Operators verwenden. In
Ruby 1.9 gibt es spezielle Nebeneffekte, die benannte einfangende Gruppen betreffen.
Rezept 3.9 geht darauf näher ein.

In allen anderen Ruby-Codeschnipseln in diesem Buch haben wir den Aus-


gangstext links vom =~-Operator und den regulären Ausdruck rechts
davon platziert. Das entspricht der Vorgehensweise in Perl, aus der Ruby
sich die =~-Syntax ausgeliehen hat. Zudem werden damit die Besonderhei-
ten in Ruby 1.9 bezüglich der benannten einfangenden Gruppen vermie-
den, die eventuell nicht berücksichtigt weredn.

Siehe auch
Rezepte 3.6 und 3.7.

3.6 Auf eine vollständige Übereinstimmung einer Regex


mit einem Text prüfen
Problem
Sie wollen prüfen, ob ein String komplett zu einem bestimmten Muster passt. Das heißt,
Sie wollen sicherstellen, dass der reguläre Ausdruck, der das Muster enthält, den String
vom Anfang bis zum Ende abdecken kann. Wenn Ihre Regex zum Beispiel
‹RegexzMuster› ist, wird der Text Regex-Muster gefunden, aber nicht der längere String
Das Regex-Muster kann gefunden werden.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 137
Lösung
C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
bool foundMatch = Regex.IsMatch(subjectString, @"\ARegex-Muster\Z");

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Regex regexObj = new Regex(@"\ARegex-Muster\Z");
bool foundMatch = regexObj.IsMatch(subjectString);

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
Dim FoundMatch = Regex.IsMatch(SubjectString, "\ARegex-Muster\Z")

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim RegexObj As New Regex("\ARegex-Muster\Z")
Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Der Aufruf von IsMatch() sollte als Parameter nur SubjectString nutzen. Achten Sie
darauf, den Aufruf für die Instanz RegexObj durchzuführen, nicht für die Klasse Regex:
Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Java
Wenn Sie nur einen String prüfen wollen, können Sie den statischen Aufruf nutzen:
boolean foundMatch = subjectString.matches("Regex-Muster");

Wollen Sie die gleiche Regex auf mehrere Strings anwenden, kompilieren Sie Ihre Regex
und erstellen einen Matcher:
Pattern regex = Pattern.compile("Regex-Muster");
Matcher regexMatcher = regex.matcher(subjectString);
boolean foundMatch = regexMatcher.matches(subjectString);

JavaScript
if (/^Regex-Muster$/.test(subject)) {
// Übereinstimmung gefunden
} else {
// Keine Übereinstimmung
}

138 | Kapitel 3: Mit regulären Ausdrücken programmieren


PHP
if (preg_match('/\ARegex-Muster\Z/', $subject)) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

Perl
if ($subject =~ m/\ARegex-Muster\Z/) {
# Übereinstimmung gefunden
} else {
# Keine Übereinstimmung
}

Python
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale
Funktion verwenden:
if re.match(r"Regex-Muster\Z", subject):
# Übereinstimmung gefunden
else:
# Keine Übereinstimmung
Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile(r"Regex-Muster\Z")
if reobj.match(subject):
# Übereinstimmung gefunden
else:
# Keine Übereinstimmung

Ruby
if subject =~ /\ARegex-Muster\Z/
# Übereinstimmung gefunden
else
# Keine Übereinstimmung
end

Diskussion
Normalerweise erfahren Sie durch eine erfolgreiche Übereinstimmung eines regulären
Ausdrucks nur, dass das Muster irgendwo innerhalb des Texts vorhanden ist. In vielen
Situationen wollen Sie aber auch sicherstellen, dass der Text vollständig abgedeckt ist
und nichts enthält, was nicht mit dem Muster übereinstimmt. Das kommt vor allem in
Situationen vor, in denen man Eingaben von Benutzern auf Gültigkeit prüfen will. Wenn
ein Anwender eine Telefonnummer oder eine IP-Adresse eingibt, aber darüber hinaus
zusätzliche Zeichen hinzufügt, wollen Sie die Angaben zurückweisen.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 139
Die Lösungen, die die Anker ‹$› und ‹\Z› nutzen, funktionieren auch, wenn Sie eine
Datei Zeile für Zeile verarbeiten (Rezept 3.21) und beim Einlesen der Zeilen den Zeilen-
umbruch am Ende belassen. Wie in Rezept 2.5 erläutert, passen diese Anker auch vor
einem abschließenden Zeilenumbruch, womit sich dieser Zeilenumbruch ignorieren
lässt.
In den folgenden Abschnitten erläutern wir die Lösungen für die verschiedenen Spra-
chen.

C# und VB.NET
Die Klasse Regex() im .NET Framework bietet keine Funktion an, mit der sich testen
lässt, ob eine Regex einen String komplett abdeckt. Daher muss man hier den Text-
anfangs-Anker ‹\A› an den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende
setzen. So passt der reguläre Ausdruck nur auf den vollständigen String – oder gar nicht.
Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›,
müssen Sie sicherstellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die
Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›.
Ist Ihr regulärer Ausdruck so angepasst, können Sie die gleiche Methode IsMatch()
benutzen, die schon im vorhergehenden Rezept beschrieben wurde.

Java
In Java gibt es drei Methoden mit dem Namen matches(). Alle drei prüfen, ob eine Regex
einen String vollständig abdecken kann. Diese Methoden stellen eine einfache Möglich-
keit zur Eingabeüberprüfung dar, da Sie Ihre Regex nicht extra mit Ankern am Anfang
und am Ende versehen müssen.
Die Klasse String enthält eine Methode matches(), die als einzigen Parameter einen regu-
lären Ausdruck erwartet. Sie liefert true oder false zurück, um anzugeben, ob die Regex
den ganzen String abdecken konnte. Die Klasse Pattern besitzt eine statische Methode
matches(), die zwei Strings erwartet. Der erste ist der reguläre Ausdruck, der zweite der
fragliche Text. Sie können als Text sogar eine beliebige CharSequence an Pattern.
matches() übergeben. Das ist der einzige Grund dafür, Pattern.matches() statt String.
matches() zu verwenden.
Sowohl String.matches() als auch Pattern.matches() kompilieren den regulären Aus-
druck jedes Mal, indem sie Pattern.compile("Regex").matcher(subjectString).matches()
aufrufen. Da die Regex immer wieder neu kompiliert wird, sollten Sie diese Methoden
nur verwenden, wenn Sie die Regex lediglich ein Mal nutzen wollen (um zum Beispiel ein
Feld in einem Eingabefenster auszuwerten) oder wenn Effizienz kein Thema ist. Bei
diesen Methoden kann man außerhalb des regulären Ausdrucks keine Regex-Optionen
mitgeben. Wenn Ihr regulärer Ausdruck einen Syntaxfehler enthält, wird eine Pattern-
SyntaxException geworfen.

140 | Kapitel 3: Mit regulären Ausdrücken programmieren


Möchten Sie die gleiche Regex effizient auf mehrere Strings anwenden, sollten Sie Ihre
Regex kompilieren, dann einen Matcher erstellen und diesen wiederholt nutzen, wie in
Rezept 3.3 beschrieben. Anschließend rufen Sie für Ihre Matcher-Instanz die Methode
matches() auf. Diese Funktion erwartet keine Parameter, da Sie den Ausgangstext schon
beim Erstellen oder Zurücksetzen des Matchers angegeben haben.

JavaScript
JavaScript bietet keine Funktion an, mit der man testen kann, ob eine Regex einen String
vollständig abdeckt. Stattdessen fügen Sie am Anfang Ihres regulären Ausdrucks ein ‹^›
und am Ende ein ‹$› an. Stellen Sie sicher, dass Sie nicht die Option /m nutzen. Denn nur
ohne diese Option passen Zirkumflex und Dollarzeichen lediglich am Anfang und am
Ende des Ausgangstexts. Wenn Sie /m setzen, passen sie auch auf Zeilenumbrüche mitten
im Text.
Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie die gleiche
Methode regexp.test() nutzen, die im vorhergehenden Rezept beschrieben wurde.

PHP
PHP bietet keine Funktion an, mit der sich testen lässt, ob eine Regex einen String
komplett abdeckt. Daher muss man hier den Textanfangs-Anker ‹\A› an den Anfang
der Regex und den Textende-Anker ‹\Z› an das Ende setzen. So passt der reguläre
Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer Aus-
druck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen,
dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufügen:
‹\A(?:eins|zwei|drei)\Z›.
Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie die gleiche
Funktion preg_match() nutzen, die im vorhergehenden Rezept beschrieben wurde.

Perl
Perl hat nur einen Mustererkennungsoperator, der schon bei Übereinstimmung mit
einem Teil des Texts zufrieden ist. Um also zu prüfen, ob Ihre Regex den gesamten Text
abdeckt, fügen Sie den Textanfangs-Anker ‹\A› an den Anfang der Regex und den Text-
ende-Anker ‹\Z› an das Ende ein. So passt der reguläre Ausdruck nur auf den vollständi-
gen String – oder gar nicht. Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum
Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen, dass die Alternation in eine Gruppe
gesteckt wird, bevor Sie die Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›.
Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie ihn genau
so wie im vorhergehenden Rezept beschrieben anwenden.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 141
Python
Die Funktion match() ähnelt sehr der im vorigen Rezept beschriebenen Funktion
search(). Hauptunterschied ist, dass match() den regulären Ausdruck nur am Anfang des
Ausgangstexts auswertet. Wenn die Regex nicht am Anfang des Strings passt, liefert
match() direkt den Wert None zurück. Die Funktion search() versucht hingegen, die
Regex an jeder möglichen Position im String anzuwenden, bis sie entweder eine Überein-
stimmung findet oder das Ende des Strings erreicht.
Bei der Funktion match() ist es nicht erforderlich, dass der reguläre Ausdruck den gesam-
ten String abdeckt. Es reicht eine Übereinstimmung mit einem Teil des Texts, solange
diese am Anfang des Strings beginnt. Wenn Sie prüfen wollen, ob Ihre Regex den gesam-
ten String abdeckt, müssen Sie einen Textende-Anker ‹\Z› an Ihren regulären Ausdruck
anhängen.

Ruby
Die Ruby-Klasse Regexp enthält keine Funktion, mit der sich testen lässt, ob eine Regex
einen String komplett abdeckt. Daher müssen Sie hier den Textanfangs-Anker ‹\A› an
den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende setzen. So passt der
reguläre Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer
Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicher-
stellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufü-
gen: ‹\A(?:eins|zwei|drei)\Z›.
Haben Sie Ihren regulären Ausdruck um die Anker ergänzt, können Sie den gleichen
Operator =~ nutzen, der schon im vorhergehenden Rezept beschrieben wurde.

Siehe auch
Rezept 2.5 erklärt genauer, wie Anker funktionieren.
Die Rezepte 2.8 und 2.9 erklären die Alternation und das Gruppieren. Wenn Ihre Regex
eine Alternation außerhalb von Gruppen verwendet, müssen Sie sie gruppieren, bevor Sie
die Anker hinzufügen. Nutzen Sie keine Alternation oder verwenden Sie sie nur innerhalb
von Gruppen, brauchen Sie keine zusätzliche Gruppe, damit die Anker wie gewünscht
funktionieren.
Schauen Sie sich Rezept 3.5 an, wenn eine teilweise Übereinstimmung ausreichend ist.

3.7 Auslesen des übereinstimmenden Texts


Problem
Sie haben einen regulären Ausdruck, der zu einem Teil des Ausgangstexts passt. Sie wol-
len den übereinstimmenden Text auslesen. Findet der reguläre Ausdruck im String mehr

142 | Kapitel 3: Mit regulären Ausdrücken programmieren


als eine Übereinstimmung, wollen Sie lediglich die erste auslesen. Wenn Sie zum Beispiel
die Regex ‹\d+› auf den String Mögen Sie 13 oder 42? anwenden, sollte 13 zurückgegeben
werden.

Lösung
C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
string resultString = Regex.Match(subjectString, @"\d+").Value;

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
string resultString = null;
try {
resultString = Regex.Match(subjectString, @"\d+").Value;
} catch (ArgumentNullException ex) {
// Regex und Text müssen vorhanden sein
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Regex regexObj = new Regex(@"\d+");
string resultString = regexObj.Match(subjectString).Value;

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per
Exception Handling absichern:
string resultString = null;
try {
Regex regexObj = new Regex(@"\d+");
try {
resultString = regexObj.Match(subjectString).Value;
} catch (ArgumentNullException ex) {
// Regex und Text müssen vorhanden sein
}
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
Dim ResultString = Regex.Match(SubjectString, "\d+").Value

3.7 Auslesen des übereinstimmenden Texts | 143


Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
Dim ResultString As String = Nothing
Try
ResultString = Regex.Match(SubjectString, "\d+").Value
Catch ex As ArgumentNullException
'Regex und Text dürfen nicht Nothing sein
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim RegexObj As New Regex("\d+")
Dim ResultString = RegexObj.Match(SubjectString).Value

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per
Exception Handling absichern:
Dim ResultString As String = Nothing
Try
Dim RegexObj As New Regex("\d+")
Try
ResultString = RegexObj.Match(SubjectString).Value
Catch ex As ArgumentNullException
'Text darf nicht Nothing sein
End Try
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Java
Erstellen Sie einen Matcher, um die Suche auszuführen und das Ergebnis zu sichern:
String resultString = null;
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
resultString = regexMatcher.group();
}

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Ganze mit einem
kompletten Exception Handling versehen:
String resultString = null;
try {
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
resultString = regexMatcher.group();
}
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
}

144 | Kapitel 3: Mit regulären Ausdrücken programmieren


JavaScript
var result = subject.match(/\d+/);
if (result) {
result = result[0];
} else {
result = '';
}

PHP
if (preg_match('/\d+/', $subject, $groups)) {
$result = $groups[0];
} else {
$result = '';
}

Perl
if ($subject =~ m/\d+/) {
$result = $&;
} else {
$result = '';
}

Python
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale
Funktion verwenden:
matchobj = re.search("Regex-Muster", subject)
if matchobj:
result = matchobj.group()
else:
result = ""

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile("Regex-Muster")
matchobj = reobj.search(subject)
if match:
result = matchobj.group()
else:
result = ""

Ruby
Sie können den Operator =~ und seine magische Variable $& nutzen:
if subject =~ /Regex-Muster/
result = $&
else
result = ""
end

3.7 Auslesen des übereinstimmenden Texts | 145


Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen:
matchobj = /Regex-Muster/.match(subject)
if matchobj
result = matchobj[0]
else
result = ""
end

Diskussion
Das Extrahieren eines Teils eines längeren Strings, der zu einem bestimmten Muster
passt, ist ein weiteres Haupteinsatzgebiet regulärer Ausdrücke. Alle in diesem Buch
behandelten Programmiersprachen bieten eine einfache Möglichkeit, die erste Überein-
stimmung eines regulären Ausdrucks in einem String auszulesen. Die Funktion versucht
dabei, die Regex am Anfang des Strings anzuwenden, und durchläuft ihn so lange, bis der
Ausdruck passt.

.NET
Die .NET-Klasse Regex hat keinen Member, der den von einem regulären Ausdruck
gefundenen String zurückgibt. Aber es gibt eine Methode Match(), die eine Instanz der
Klasse Match zurückgibt. Dieses Match-Objekt hat eine Eigenschaft namens Value, in der
der vom regulären Ausdruck gefundene Text zu finden ist. Wenn der reguläre Ausdruck
nichts findet, gibt er trotzdem ein Match-Objekt zurück, aber die Eigenschaft Value ent-
hält dann einen leeren String.
Fünf verschieden überladene Versionen ermöglichen es Ihnen, die Methode Match() auf
unterschiedlichste Art und Weise aufzurufen. Der erste Parameter ist immer der String,
in dem sich der Ausgangstext befindet, auf den der reguläre Ausdruck angewendet wer-
den soll. Dieser Parameter sollte nicht null sein, da Match() ansonsten eine Argument-
NullException werfen wird.
Wenn Sie den regulären Ausdruck nur ein paar Mal nutzen wollen, können Sie einen sta-
tischen Aufruf durchführen. Der zweite Parameter ist dann der reguläre Ausdruck, den
Sie nutzen wollen. Sie können als optionalen dritten Parameter noch Regex-Optionen
angeben. Enthält Ihr regulärer Ausdruck einen Syntaxfehler, wird eine ArgumentException
geworfen.
Möchten Sie den gleichen regulären Ausdruck auf viele Strings anwenden, können Sie
Ihren Code effizienter gestalten, indem Sie zunächst ein Regex-Objekt erstellen und dann
für dieses Objekt Match() aufrufen. Der erste Parameter mit dem Text ist der einzig not-
wendige. Sie können einen optionalen zweiten Parameter mit der Zeichenposition ange-
ben, an der der reguläre Ausdruck mit der Suche beginnen soll. Im Prinzip handelt es sich
bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck am Anfang des
Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis zu
einer gewissen Position verarbeitet haben und nun noch den Rest durchsuchen wollen.

146 | Kapitel 3: Mit regulären Ausdrücken programmieren


Diese Zahl muss größer oder gleich null und kleiner oder gleich der Länge des Strings
sein. Ansonsten wirft IsMatch() eine ArgumentOutOfRangeException.
Wenn Sie den zweiten Parameter mit der Startposition übergeben, können Sie auch einen
dritten Parameter angeben, der die Länge des Substrings festlegt, in der die Regex suchen
darf. Diese Zahl muss größer oder gleich null sein und darf die Länge des restlichen
Strings nicht überschreiten. So versucht zum Beispiel regexObj.Match("123456", 3, 2),
eine Übereinstimmung in "45" zu finden. Wenn der dritte Parameter größer als die Länge
des Texts ist, wirft Match() eine ArgumentOutOfRangeException. Ist er nicht größer als die
Länge des Texts, aber größer als der verbleibende Text, wird stattdessen eine IndexOutOf-
RangeException geworfen. Erlauben Sie dem Anwender, Anfangs- und Endpositionen
anzugeben, müssen Sie sie entweder vor dem Aufruf von Match() prüfen oder zumindest
sicherstellen, dass Sie beide Exceptions abfangen.
Bei den statischen Methoden können keine Parameter für die Definition des zu durchsu-
chenden Abschnitts mitgegeben werden.

Java
Um den Teil eines Strings zu erhalten, der von einem regulären Ausdruck gefunden
wurde, müssen Sie einen Matcher erstellen, wie in Rezept 3.3 beschrieben. Dann rufen Sie
dessen Methode find() ohne weitere Parameter auf. Wenn find() den Wert true zurück-
gibt, rufen Sie group() ohne weitere Parameter auf, um den von Ihrem regulären Aus-
druck gefundenen Text zu erhalten. Liefert find() den Wert false, sollten Sie group()
nicht aufrufen, da Sie dann nur eine IllegalStateException erhalten würden.
Matcher.find() hat einen optionalen Parameter, mit dem die Startposition im Text ange-
geben werden kann. Sie können ihn dazu verwenden, die Suche an einer bestimmten
Stelle im String beginnen zu lassen. Wenn Sie 0 angeben, wird am Anfang des Texts
begonnen. Es wird eine IndexOutOfBoundsException geworfen, wenn Sie die Startposition
auf einen negativen Wert setzen oder auf einen Wert, der größer ist als die Länge des
Texts.
Lassen Sie den Parameter weg, beginnt find() mit dem Zeichen nach der Position, an der
die letzte Übereinstimmung durch find() gefunden wurde. Wenn Sie find() das erste
Mal nach Pattern.matcher() oder Matcher.reset() aufrufen, beginnt find() mit der Suche
am Anfang des Strings.

JavaScript
Die Methode string.match() erwartet einen regulären Ausdruck als Parameter. Sie kön-
nen einen regulären Ausdruck als literale Regex, als Regex-Objekt oder als String überge-
ben. Wenn Sie einen String mitgeben, erzeugt string.match() ein temporäres regexp-
Objekt.
Ist die Suche erfolglos, liefert string.match() den Wert null zurück. Damit können Sie
unterscheiden zwischen einer Regex, die keine Übereinstimmungen gefunden hat, und

3.7 Auslesen des übereinstimmenden Texts | 147


einer, die eine Übereinstimmung der Länge null enthält. Das bedeutet, dass Sie das
Ergebnis nicht direkt anzeigen können, da „null“ oder ein Fehler über ein Null-Objekt
erscheinen kann.
Wenn die Suche erfolgreich war, liefert string.match() ein Array mit den Details der
Übereinstimmung zurück. Das nullte Element im Array ist ein String mit dem vom regu-
lären Ausdruck gefundenen Text.
Achten Sie darauf, nicht die Option /g zu nutzen. Denn dann verhält sich string.match()
anders, wie in Rezept 3.10 geschildert wird.

PHP
Der in den vorletzten beiden Rezepten besprochenen Funktion preg_match() kann ein
optionaler dritter Parameter mitgegeben werden, in dem der gefundene Text und die ein-
gefangenen Gruppen abgelegt werden. Wenn preg_match() den Wert 1 zurückgibt, ent-
hält die Variable ein Array mit Strings. Das nullte Element des Arrays enthält den vom
regulären Ausdruck gefundenen Teil des Texts. Die anderen Elemente werden in
Rezept 3.9 beschrieben.

Perl
Wenn der Mustererkennungsoperator m// eine Übereinstimmung findet, setzt er eine
Reihe von speziellen Variablen. Eine davon ist die Variable $&, die den Teil des Strings
enthält, der durch den regulären Ausdruck gefunden wurde. Die anderen speziellen Vari-
ablen werden in späteren Rezepten erläutert.

Python
In Rezept 3.5 wird die Funktion search() beschrieben. Hier speichern wir die von
search() zurückgegebene Instanz von MatchObject in einer Variablen. Um an den vom
regulären Ausdruck gefundenen Teil des Strings zu gelangen, rufen wir die Methode
group() für das Match-Objekt ohne Parameter auf.

Ruby
In Rezept 3.8 sind die Variable $~ und das Objekt MatchData beschrieben. In einem
String-Kontext wird aus diesem Objekt der Text, der durch den regulären Ausdruck
gefunden wurde. In einem Array-Kontext wird es zu einem Array, dessen nulltes Element
den von der Regex gefundenen Text enthält.
$& ist eine spezielle, nur lesbare Variable. Sie ist ein Alias für $~[0], in dem ein String mit
dem vom regulären Ausdruck gefundenen Text zu finden ist.

Siehe auch
Rezepte 3.5, 3.8, 3.9, 3.10 und 3.11.

148 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.8 Position und Länge der Übereinstimmung ermitteln
Problem
Statt den Substring auszulesen, der von einem regulären Ausdruck gefunden wurde (wie
im vorherigen Rezept), wollen Sie Startposition und Länge der Übereinstimmung wissen.
Mit dieser Information können Sie die Übereinstimmung in Ihrem eigenen Code ermit-
teln oder irgendwelche spannenden Verarbeitungsschritte mit dem ursprünglichen String
anstellen.

Lösung
C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
int matchstart, matchlength = -1;
Match matchResult = Regex.Match(subjectString, @"\d+");
if (matchResult.Success) {
matchstart = matchResult.Index;
matchlength = matchResult.Length;
}

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


int matchstart, matchlength = -1;
Regex regexObj = new Regex(@"\d+");
Match matchResult = regexObj.Match(subjectString).Value;
if (matchResult.Success) {
matchstart = matchResult.Index;
matchlength = matchResult.Length;
}

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
Dim MatchStart = -1
Dim MatchLength = -1
Dim MatchResult = Regex.Match(SubjectString, "\d+")
If MatchResult.Success Then
MatchStart = MatchResult.Index
MatchLength = MatchResult.Length
End If

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim MatchStart = -1
Dim MatchLength = -1

3.8 Position und Länge der Übereinstimmung ermitteln | 149


Dim RegexObj As New Regex("\d+")
Dim MatchResult = Regex.Match(SubjectString, "\d+")
If MatchResult.Success Then
MatchStart = MatchResult.Index
MatchLength = MatchResult.Length
End If

Java
int matchStart, matchLength = -1;
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
matchStart = regexMatcher.start();
matchLength = regexMatcher.end() - matchStart;
}

JavaScript
var matchstart = -1;
var matchlength = -1;
var match = /\d+/.exec(subject);
if (match) {
matchstart = match.index;
matchlength = match[0].length;
}

PHP
if (preg_match('/\d+/', $subject, $groups, PREG_OFFSET_CAPTURE)) {
$matchstart = $groups[0][1];
$matchlength = strlen($groups[0][0]);
}

Perl
if ($subject =~ m/\d+/g) {
$matchlength = length($&);
$matchstart = length($`);
}

Python
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale
Funktion verwenden:
matchobj = re.search(r"\d+", subject)
if matchobj:
matchstart = matchobj.start()
matchlength = matchobj.end() - matchstart

150 | Kapitel 3: Mit regulären Ausdrücken programmieren


Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile(r"\d+")
matchobj = reobj.search(subject)
if matchobj:
matchstart = matchobj.start()
matchlength = matchobj.end() - matchstart

Ruby
Sie können den Operator =~ und seine magische Variable $& nutzen:
if subject =~ /Regex-Muster/
matchstart = $~.begin()
matchlength = $~.end() - matchstart
end

Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen:
matchobj = /Regex-Muster/.match(subject)
if matchobj
matchstart = matchobj.begin()
matchlength = matchobj.end() - matchstart
end

Diskussion
.NET
Um die Startposition und die Länge des Übereinstimmungsbereichs zu erhalten, nutzen
wir die gleiche Methode Regex.Match(), die schon im vorhergehenden Rezept beschrieben
wurde. Dieses Mal verwenden wir die Eigenschaften Index und Length des von
Regex.Match() zurückgegebenen Match-Objekts.
Index ist die Position im Text, an der die Regex-Übereinstimmung beginnt. Wenn dies
gleich am Anfang des Strings der Fall ist, hat Index den Wert 0. Beginnt die Übereinstim-
mung mit dem zweiten Zeichen im String, hat Index den Wert 1. Der maximale Wert für
Index ist die Länge des Strings. Das kann dann passieren, wenn die Regex eine Überein-
stimmung der Länge null am Ende des Strings findet. Besteht die Regex zum Beispiel nur
aus dem Textende-Anker ‹\Z›, findet sich die Übereinstimmung immer am Ende des
Strings.
Length gibt die Zahl der Zeichen an, für die eine Übereinstimmung besteht. Es ist mög-
lich, dass eine Übereinstimmung null Zeichen lang ist. Besteht zum Beispiel die Regex
nur aus der Wortgrenze ‹\b›, wird eine Übereinstimmung der Länge null am Anfang des
ersten Worts im String gefunden.
Wenn es keine Übereinstimmung gibt, liefert Regex.Match() trotzdem ein Match-Objekt
zurück. Dessen Eigenschaften Index und Length haben dann beide den Wert null. Diese
Werte können aber auch bei einer erfolgreichen Suche vorkommen. Besteht die Regex
zum Beispiel aus dem Textanfangs-Anker ‹\A›, wird eine Übereinstimmung der Länge

3.8 Position und Länge der Übereinstimmung ermitteln | 151


null am Anfang des Strings gefunden. Daher können Sie sich nicht auf Match.Index oder
Match.Length verlassen, um herauszufinden, ob die Suche erfolgreich war. Verwenden Sie
stattdessen besser Match.Success.

Java
Um die Position und die Länge der Übereinstimmung zu finden, rufen Sie zunächst, wie
schon im vorhergehenden Rezept beschrieben, Matcher.find() auf. Wenn find() true
zurückgibt, rufen Sie als Nächstes Matcher.start() ohne Parameter auf, um den Index
des ersten Zeichens zu erhalten, das Teil der Regex-Übereinstimmung ist. Ein Aufruf von
end() ohne Parameter liefert den Index des ersten Zeichens nach der Übereinstimmung
zurück. Subtrahieren Sie den Anfang vom Ende, um die Länge der Übereinstimmung zu
erhalten. Diese kann durchaus null sein. Wenn Sie start() oder end() ohne einen vorhe-
rigen Aufruf von find() aufrufen, erhalten Sie eine IllegalStateException.

JavaScript
Rufen Sie die Methode exec() für ein regexp-Objekt auf, um ein Array mit den Details des
Suchergebnisses zu erhalten. Dieses Array besitzt ein paar zusätzliche Eigenschaften. In
der Eigenschaft index ist die Position im Text abgelegt, an der die Regex-Übereinstim-
mung beginnt. Wenn dies der Anfang des Strings ist, hat index den Wert null. Das nullte
Element des Arrays enthält einen String mit dem gesamten Suchergebnis. Die Eigenschaft
length dieses Strings ist die Länge der Übereinstimmung.
Konnte der reguläre Ausdruck keine Übereinstimmung finden, liefert regexp.exec() den
Wert null zurück.
Verwenden Sie nicht die Eigenschaft lastIndex des von exec() zurückgegebenen Arrays,
um das Ende der Übereinstimmung zu erhalten. In einer strikten JavaScript-Implemen-
tierung existiert lastIndex gar nicht im zurückgegebenen Array, sondern nur im regexp-
Objekt selbst. Sie sollten aber ebenfalls nicht regexp.lastIndex verwenden. Es ist auf-
grund von Unterschieden zwischen den Browsern nicht verlässlich nutzbar (siehe
Rezept 3.11). Stattdessen addieren Sie match.index und match[0].length einfach, um he-
rauszufinden, wo die Regex-Übereinstimmung endet.

PHP
Das vorhergehende Rezept hat erläutert, wie Sie den von einem regulären Ausdruck
gefundenen Text erhalten können, indem Sie preg_match() einen dritten Parameter über-
geben. Sie können die Position der Übereinstimmung ermitteln, indem Sie die Konstante
PREG_OFFSET_CAPTURE als vierten Parameter übergeben. Dieser Parameter beeinflusst das,
was preg_match() im dritten Parameter ablegt, wenn es 1 zurückliefert.
Wenn Sie den vierten Parameter weglassen oder auf null setzen, enthält die als dritter
Parameter übergebene Variable ein Array mit Strings. Übergeben Sie PREG_OFFSET_CAPTURE
als vierten Parameter, enthält die Variable ein Array aus Arrays. Das nullte Element im

152 | Kapitel 3: Mit regulären Ausdrücken programmieren


Hauptarray enthält weiterhin das Suchergebnis (siehe dazu das obige Rezept), während
die darauffolgenden Elemente immer noch die Ergebnisse der einfangenden Gruppen
enthalten. Aber statt eines Strings mit dem von der Regex oder einer einfangenden
Gruppe gefundenen Text enthält das Element nun ein Array mit zwei Werten: dem Text,
der gefunden wurde, und der Position im String, an der er gefunden wurde.
Um die Details des gesamten Suchergebnisses zu bekommen, liefert uns das nullte Unter-
element des nullten Elements den Text, der von der Regex gefunden wurde. Diesen
übergeben wir an die Funktion strlen(), um seine Länge zu ermitteln. Das erste Unter-
element des nullten Elements enthält einen Integer-Wert mit der Position im Text, an der
die Übereinstimmung beginnt.

Perl
Um die Länge der Übereinstimmung zu ermitteln, berechnen wir einfach die Länge der
Variablen $&, die das vollständige Suchergebnis enthält. Um den Anfang des Überein-
stimmungsbereichs herauszufinden, berechnen wir die Länge der Variablen $`, in der der
Text des Strings vor dem Regex-Übereinstimmungsbereich zu finden ist.

Python
Die Methode start() von MatchObject gibt die Position im String zurück, an der die
Regex-Übereinstimmung beginnt. Die Methode end() gibt die Position des ersten Zei-
chens nach dem Übereinstimmungsbereich zurück. Beide Methoden liefern den gleichen
Wert, wenn eine Übereinstimmung der Länge null gefunden wurde.
Sie können an die Methoden start() und end() einen Parameter übergeben, um den
Textbereich einer der einfangenden Gruppen des regulären Ausdrucks zu erhalten. Mit
start(1) sprechen Sie die erste einfangende Gruppe an, mit end(2) die zweite und so wei-
ter. Python unterstützt bis zu 99 einfangende Gruppen. Die Gruppe mit der Nummer 0
ist das gesamte Suchergebnis. Jede Zahl außerhalb des Bereichs von 0 bis zur Anzahl der
einfangenden Gruppen (mit 99 als absoluter Obergrenze) führt zu einem IndexError.
Wenn die Gruppennummer gültig ist, die Gruppe aber an der Regex-Übereinstimmung
nicht beteiligt war, liefern sowohl start() als auch end() für diese Gruppe den Wert -1
zurück.
Wollen Sie Start- und Endposition in einem Tupel speichern, rufen Sie die Methode
span() für das Match-Objekt auf.

Ruby
In Rezept 3.5 wird der Operator =~ genutzt, um die erste Regex-Übereinstimmung in
einem String zu finden. Ein Nebeneffekt dieses Operators ist, dass er die spezielle Vari-
able $~ mit einer Instanz der Klasse MatchData bestückt. Diese Variable ist Thread- und
Methoden-lokal. Das bedeutet, Sie können den Inhalt dieser Variablen nutzen, bis Ihre
Methode beendet ist oder bis Sie das nächste Mal den Operator =~ in Ihrer Methode nut-

3.8 Position und Länge der Übereinstimmung ermitteln | 153


zen. Sie müssen sich nicht darum sorgen, dass ein anderer Thread oder eine andere
Methode in Ihrem Thread den Inhalt überschreibt.
Wenn Sie die Details mehrerer Regex-Suchen sichern wollen, rufen Sie die Methode
match() für ein Regexp-Objekt auf. Diese Methode erwartet den Text als einzigen Parame-
ter. Sie liefert eine Instanz von MatchData zurück, wenn es eine Übereinstimmung gab,
ansonsten den Wert nil. Auch sie setzt die Variable $~ auf die gleiche Instanz von
MatchObject, überschreibt aber keine anderen MatchObject-Instanzen, die in anderen Vari-
ablen gespeichert waren.
Das Objekt MatchData speichert alle Details zu einer Regex-Übereinstimmung. Die
Rezepte 3.7 und 3.9 beschreiben, wie man an den Text gelangt, der vom regulären Aus-
druck und von den einfangenden Gruppen gefunden wurde.
Die Methode begin() liefert die Position im Text zurück, an der die Regex-Übereinstim-
mung beginnt. end() liefert die Position des ersten Zeichens nach dem Regex-Überein-
stimmungsbereich zurück. offset() gibt ein Array zurück, in dem sich die Anfangs- und
Endpositionen befinden. Diese drei Methoden erwarten jeweils einen Parameter. Mit 0
erhalten Sie die Positionen des gesamten Suchergebnisses. Übergeben Sie eine positive
Zahl, erhalten Sie die Daten für die entsprechende einfangende Gruppe. So liefert
begin(1) zum Beispiel den Anfang der ersten einfangenden Gruppe.
Verwenden Sie nicht length() oder size(), um die Länge der Übereinstimmung zu ermit-
teln. Beide Methoden geben die Anzahl der Elemente im Array zurück, das MatchData im
Array-Kontext liefert (erklärt in Rezept 3.9).

Siehe auch
Rezepte 3.5 und 3.9.

3.9 Teile des übereinstimmenden Texts auslesen


Problem
Wie in Rezept 3.7 haben Sie einen regulären Ausdruck, der auf einen Substring des Texts
passt. Dieses Mal wollen Sie aber nur einen Teil dieses Substrings nutzen. Um den
gewünschten Teil abzugrenzen, haben Sie Ihrem regulären Ausdruck eine einfangende
Gruppe hinzugefügt (wie bereits in Rezept 2.9 beschrieben).
So passt der reguläre Ausdruck ‹http://([a-z0-9.-]+)› zum Beispiel auf http://www.
regexcookbook.com im String Auf http://www.regexcookbook.com finden Sie mehr Informa-
tionen. Der Teil der Regex innerhalb der ersten einfangenden Gruppe passt auf
www.regexcookbook.com, und Sie wollen den Domainnamen aus der ersten einfangenden
Gruppe in eine String-Variable auslesen.

154 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wir verwenden diese einfache Regex, um das Konzept von einfangenden Gruppen deut-
lich zu machen. In Kapitel 7 finden Sie exaktere Regexes, die auf URLs passen.

Lösung
C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
string resultString = Regex.Match(subjectString,
"http://([a-z0-9.-]+)").Groups[1].Value;

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Regex regexObj = new Regex("http://([a-z0-9.-]+)");
string resultString = regexObj.Match(subjectString).Groups[1].Value;

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
Dim ResultString = Regex.Match(SubjectString,
"http://([a-z0-9.-]+)").Groups(1).Value

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim RegexObj As New Regex("http://([a-z0-9.-]+)")
Dim ResultString = RegexObj.Match(SubjectString).Groups(1).Value

Java
String resultString = null;
Pattern regex = Pattern.compile("http://([a-z0-9.-]+)");
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
resultString = regexMatcher.group(1);
}

JavaScript
var result = "";
var match = /http:\/\/([a-z0-9.-]+)/.exec(subject);
if (match) {
result = match[1];
} else {
result = '';
}

3.9 Teile des übereinstimmenden Texts auslesen | 155


PHP
if (preg_match('%http://([a-z0-9.-]+)%', $subject, $groups)) {
$result = $groups[1];
} else {
$result = '';
}

Perl
if ($subject =~ m!http://([a-z0-9.-]+)!) {
$result = $1;
} else {
$result = '';
}

Python
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale
Funktion verwenden:
matchobj = re.search("http://([a-z0-9.-]+)", subject)
if matchobj:
result = matchobj.group(1)
else:
result = ""

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile("http://([a-z0-9.-]+)")
matchobj = reobj.search(subject)
if match:
result = matchobj.group(1)
else:
result = ""

Ruby
Sie können den Operator =~ und seine magischen nummerierten Variablen (wie zum Bei-
spiel $1) nutzen:
if subject =~ %r!http://([a-z0-9.-]+)!
result = $1
else
result = ""
end

Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen:
matchobj = %r!http://([a-z0-9.-]+)!.match(subject)
if matchobj
result = matchobj[1]
else
result = ""
end

156 | Kapitel 3: Mit regulären Ausdrücken programmieren


Diskussion
In Rezept 2.10 und 2.21 haben wir erklärt, wie Sie nummerierte Rückwärtsreferenzen im
regulären Ausdruck und im Ersetzungstext nutzen können, um denselben Text erneut zu
suchen oder um Teile der Regex-Übereinstimmung in den Ersetzungstext einzufügen. Sie
können die gleichen Referenzzahlen nutzen, um den Text in Ihrem Code auszulesen, der
von einer oder mehreren einfangenden Gruppen gefunden wurde.
Bei regulären Ausdrücken werden einfangende Gruppen beginnend mit eins nummeriert.
Programmiersprachen nummerieren Arrays und Listen im Allgemeinen bei null begin-
nend. Alle in diesem Buch behandelten Programmiersprachen speichern die einfangen-
den Gruppen in einem Array oder in einer Liste, wobei sie aber die gleiche Nummerie-
rung wie bei den einfangenden Gruppen im regulären Ausdruck verwenden, also mit eins
beginnen. Das nullte Element im Array oder in der Liste wird dafür genutzt, das gesamte
Suchergebnis zu speichern. Wenn also Ihr regulärer Ausdruck drei einfangende Gruppen
hat, finden sich im Array mit den Übereinstimmungen vier Elemente. Das Element null
enthält das gesamte Suchergebnis, während die Elemente eins, zwei und drei den Text
enthalten, der durch die drei einfangenden Gruppen gefunden wurde.

.NET
Um die Details zu einfangenden Gruppen zu erhalten, greifen wir erneut auf die Mem-
ber-Funktion Regex.Match() zurück, die erstmals in Rezept 3.7 beschrieben wurde. Das
zurückgegebene Objekt vom Typ Match besitzt eine Eigenschaft namens Groups. Dabei
handelt es sich um eine Collection des Typs GroupCollection. Die Collection enthält die
Details aller einfangenden Gruppen in Ihrem regulären Ausdruck. In Groups[1] stehen die
Details der ersten einfangenden Gruppe, in Groups[2] die der zweiten und so weiter.
Die Collection Groups enthält für jede einfangende Gruppe ein Objekt des Typs Group.
Die Klasse Group besitzt die gleichen Eigenschaften wie die Klasse Match, abgesehen von
der Eigenschaft Groups. Match.Groups[1].Value liefert den von der ersten einfangenden
Gruppe gefundenen Text zurück – genauso wie Match.Value das gesamte Suchergebnis
liefert. Match.Groups[1].Index und Match.Groups[1].Length geben die Startposition und
die Länge des von der Gruppe gefundenen Texts zurück. In Rezept 3.8, finden Sie mehr
Informationen zu Index und Length.
Groups[0] enthält die Details des gesamten Suchergebnisses, die auch direkt im Match-
Objekt gefunden werden können. Match.Value und Match.Groups[0].Value sind äquiva-
lent.
Die Collection Groups wirft keine Exception, wenn Sie eine ungültige Gruppennummer
angeben. So liefert Groups[-1] zum Beispiel trotzdem ein Group-Objekt zurück, aber die
Eigenschaften dieses Objekts zeigen dann, dass die fiktive einfangende Gruppe -1 nichts
gefunden hat. Die beste Möglichkeit, das zu prüfen, ist die Eigenschaft Success. Groups
[-1].Success wird den Wert false zurückgeben.

3.9 Teile des übereinstimmenden Texts auslesen | 157


Um herauszubekommen, wie viele einfangende Gruppen es gibt, schauen Sie sich
Match.Groups.Count an. Die Eigenschaft Count folgt den gleichen Konventionen wie die
Eigenschaft Count aller anderen Collection-Objekte in .NET: Sie gibt die Anzahl an
Elementen in der Collection zurück – also den größten zulässigen Index plus eins. In
unserem Beispiel gibt es in der Collection Groups die Elemente Groups[0] und
Groups[1].Groups.Count ergibt damit 2.

Java
Um den von einer einfangenden Gruppe gefundenen Text oder die Details der Überein-
stimmung zu ermitteln, braucht man praktisch den gleichen Code wie für das gesamte
Suchergebnis, der in den vorhergehenden beiden Rezepten zu finden ist. Die Methoden
group(), start() und end() der Klasse Matcher besitzen alle einen optionalen Parameter.
Ohne diesen Parameter oder mit dem Wert null erhalten Sie die Übereinstimmung bezie-
hungsweise die Positionen des gesamten Suchergebnisses.
Wenn Sie eine positive Zahl übergeben, erhalten Sie die Details der einfangenden
Gruppe. Gruppen werden mit eins beginnend nummeriert, so wie die Rückwärtsreferen-
zen im regulären Ausdruck selbst. Geben Sie eine Zahl an, die größer ist als die Anzahl
der einfangenden Gruppen in Ihrem regulären Ausdruck, werfen diese drei Funktionen
eine IndexOutOfBoundsException. Wenn die einfangende Gruppe vorhanden ist, es aber
keine Übereinstimmung für sie gibt, liefert group(n) den Wert null, während start(n)
und end(n) als Ergebnis -1 ausgeben.

JavaScript
Wie im vorhergehenden Rezept beschrieben, liefert die Methode exec() eines regulären
Ausdrucks ein Array mit den Details über das Suchergebnis zurück. In Element null steht
der gesamte Suchausdruck. Element eins enthält den Text, der durch die erste einfan-
gende Gruppe gefunden wurde, Element zwei den der zweiten Gruppe und so weiter.
Wenn ein regulärer Ausdruck gar nichts findet, liefert regexp.exec() den Wert null
zurück.

PHP
In Rezept 3.7 wird beschrieben, wie Sie den vom regulären Ausdruck gefundenen Text
erhalten können, indem Sie preg_match() einen dritten Parameter übergeben. Wenn
preg_match() den Wert 1 zurückgibt, wird der Parameter mit einem Array gefüllt. Das
nullte Element enthält einen String mit dem gesamten Suchergebnis.
Das erste Element enthält den Text der ersten einfangenden Gruppe, das zweite den Text
der zweiten Gruppe und so weiter. Die Länge des Arrays entspricht der Anzahl der ein-
fangenden Gruppen plus eins. Array-Indexe entsprechen den Rückwärtsverweisnum-
mern im regulären Ausdruck.

158 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wenn Sie die Konstante PREG_OFFSET_CAPTURE als vierten Parameter angeben, wie es in obi-
gem Rezept beschrieben wurde, entspricht die Länge des Arrays immer noch der Anzahl
der einfangenden Gruppen plus eins. Aber statt an jedem Index einen String zu enthalten,
findet sich dort ein Unterarray mit zwei Elementen. Das nullte Unterelement ist der String
mit dem von der Regex oder einfangenden Gruppe gefundenen Text. Das erste Unterele-
ment ist eine Integer-Zahl, die die Position des gefundenen Texts im Ausgangstext angibt.

Perl
Wenn der Operator m// eine Übereinstimmung findet, wird eine Reihe von speziellen
Variablen gesetzt. Dazu gehören auch die nummerierten Variablen $1, $2, $3 und so wei-
ter, die den Teil des Strings enthalten, der von der entsprechenden einfangenden Gruppe
im regulären Ausdruck gefunden wurden.

Python
Die Lösung für dieses Problem ist nahezu identisch mit der aus Rezept 3.7. Statt group()
ohne Parameter aufzurufen, geben wir die Nummer der einfangenden Gruppe an, an der
wir interessiert sind. Mit group(1) erhalten Sie den Text, der durch die erste einfangende
Gruppe gefunden wurde, mit group(2) den der zweiten Gruppe und so weiter. Python
unterstützt bis zu 99 einfangende Gruppen. Die Gruppe mit der Nummer 0 enthält das
gesamte Suchergebnis. Übergeben Sie eine Zahl, die größer als die Anzahl der einfangen-
den Gruppen ist, wirft group() eine IndexError-Exception. Wenn die Gruppennummer
gültig ist, die Gruppe aber an der Regex-Übereinstimmung nicht beteiligt war, liefert
group() den Wert None.
Sie können group() mehrere Gruppennummern übergeben, um den von mehreren ein-
fangenden Gruppen gefundenen Text mit einem Aufruf zu erhalten. Das Ergebnis ist
dann eine Liste mit Strings.
Wenn Sie ein Tupel mit den von allen einfangenden Gruppen gefundenen Texten erhal-
ten wollen, können Sie die Methode groups() von MatchObject aufrufen. Das Tupel ent-
hält für Gruppen, die an der Übereinstimmung nicht beteiligt sind, den Wert None. Wenn
Sie groups() einen Parameter mitgeben, wird für Gruppen ohne Suchergebnis dieser Wert
anstelle von None genommen.
Wenn Sie ein Dictionary statt eines Tupels haben wollen, rufen Sie groupdict() statt
groups() auf. Sie können auch hier einen Parameter übergeben, dessen Inhalt anstelle von
None für Gruppen genommen wird, die kein Suchergebnis enthalten.

Ruby
In Rezept 3.8 werden die Variable $~ und das Objekt MatchData beschrieben. In einem Array-
Kontext liefert dieses Objekt ein Array mit den Texten, die durch die einfangenden Gruppen
gefunden wurden. Die Gruppen werden dabei wie die Rückwärtsreferenzen mit 1 beginnend
nummeriert, während sich in Element 0 im Array das gesamte Suchergebnis befindet.

3.9 Teile des übereinstimmenden Texts auslesen | 159


$1, $2 und folgende sind spezielle, nur lesbare Variablen. $1 ist eine Kurzform von $~[1],
in dem sich der von der ersten einfangenden Gruppe gefundene Text befindet. $2 liefert
den Text aus der zweiten Gruppe und so weiter.

Benannte Captures
Wenn Ihr regulärer Ausdruck benannte einfangende Gruppen nutzt, können Sie den
Namen der Gruppe verwenden, um den von ihr gefundenen Text in Ihrem Code zu ver-
wenden.

C#
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
string resultString = Regex.Match(subjectString,
"http://(?<domain>[a-z0-9.-]+)").Groups["domain"].Value;

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Regex regexObj = new Regex("http://(?<domain>[a-z0-9.-]+)");
string resultString = regexObj.Match(subjectString).Groups["domain"].Value;

In C# sieht der Code für das Ermitteln des Group-Objekts einer benannten Gruppe und
einer nummerierten Gruppe gar nicht so unterschiedlich aus. Statt die Groups-Collection
mit einem Integer-Wert als Index anzusprechen, nutzen Sie einen String. Auch dann
wird .NET keine Exception werfen, wenn die Gruppe nicht existiert. Match.Groups
["keinegruppe"].Success liefert in diesem Fall schlicht false zurück.

VB.NET
Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen
Aufruf verwenden:
Dim ResultString = Regex.Match(SubjectString,
"http://(?<domain>[a-z0-9.-]+)").Groups("domain").Value

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt:


Dim RegexObj As New Regex("http://(?<domain>[a-z0-9.-]+)")
Dim ResultString = RegexObj.Match(SubjectString).Groups("domain").Value

In VB.NET fällt der Code für das Ermitteln des Group-Objekts einer benannten Gruppe
und einer nummerierten Gruppe gar nicht so unterschiedlich aus. Statt die Groups-
Collection mit einem Integer-Wert als Index anzusprechen, nutzen Sie einen String.
Auch dann wird .NET keine Exception werfen, wenn die Gruppe nicht existiert.
Match.Groups("keinegruppe").Success liefert in diesem Fall schlicht False zurück.

160 | Kapitel 3: Mit regulären Ausdrücken programmieren


PHP
if (preg_match('%http://(?P<domain>[a-z0-9.-]+)%', $subject, $groups)) {
$result = $groups['domain'];
} else {
$result = '';
}

Wenn Ihr regulärer Ausdruck benannte einfangende Gruppen besitzt, ist das $groups
zugewiesene Array ein assoziatives Array. Der Text, der von jeder benannten einfangen-
den Gruppe gefunden wird, steht im Array zwei Mal zur Verfügung. Sie können den Text
auslesen, indem Sie das Array entweder über die Gruppennummer oder über den Grup-
pennamen ansprechen. Im Codebeispiel speichert $groups[0] das gesamte Suchergebnis
der Regex, während sowohl $groups[1] als auch $groups['domain'] den Text enthalten,
der von der einen einfangenden Gruppe gefunden wurde, die im regulären Ausdruck ent-
halten ist.

Perl
if ($subject =~ '!http://(?<domain>[a-z0-9.-]+)%!) {
$result = $+{'domain'};
} else {
$result = '';
}

Perl unterstützt benannte einfangende Gruppen seit Version 5.10. Der Hash $+ enthält
den Text, der von allen einfangenden Gruppen gefunden wurde. Perl nummeriert
benannte Gruppen zusammen mit den nummerierten Gruppen durch. In diesem Beispiel
findet sich sowohl in $1 als auch in $+{'domain'} der Text, der von der einen einfangenden
Gruppe gefunden wurde, die im regulären Ausdruck enthalten ist.

Python
matchobj = re.search("http://(?P<domain>[a-z0-9.-]+)", subject)
if matchobj:
result = matchobj.group("domain")
else:
result = ""

Wenn Ihr regulärer Ausdruck benannte Gruppen besitzt, können Sie der Methode
group() statt der Nummer auch den Gruppennamen übergeben.

Siehe auch
Rezept 2.9 beschreibt nummerierte einfangende Gruppen.
Rezept 2.11 beschreibt benannte einfangende Gruppen.

3.9 Teile des übereinstimmenden Texts auslesen | 161


3.10 Eine Liste aller Übereinstimmungen erhalten
Problem
Alle bisherigen Rezepte in diesem Kapitel drehen sich nur darum, die erste Übereinstim-
mung zu finden, die ein regulärer Ausdruck im Text ermittelt. Aber in vielen Fällen kann
ein regulärer Ausdruck, der einen String nur teilweise abdeckt, auch noch eine weitere
Übereinstimmung im restlichen Text ermitteln ... und vielleicht noch eine dritte und so
weiter. So kann zum Beispiel die Regex ‹\d+› sechs Übereinstimmungen im Text Die
Gewinnzahlen sind 7, 13, 16, 42, 65 und 99 finden: 7, 13, 16, 42, 65 und 99.
Sie wollen die Liste aller Substrings ermitteln, die der reguläre Ausdruck findet, wenn er
nach jeder Übereinstimmung erneut auf den Rest des Strings angewendet wird.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
MatchCollection matchlist = Regex.Matches(subjectString, @"\d+");

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
Regex regexObj = new Regex(@"\d+");
MatchCollection matchlist = regexObj.Matches(subjectString);

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
Dim matchlist = Regex.Matches(SubjectString, "\d+")

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
Dim RegexObj As New Regex("\d+")
Dim MatchList = RegexObj.Matches(SubjectString)

Java
List<String> resultList = new ArrayList<String>();
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
resultList.add(regexMatcher.group());
}

162 | Kapitel 3: Mit regulären Ausdrücken programmieren


JavaScript
var list = subject.match(/\d+/g);

PHP
preg_match_all('/\d+/', $subject, $result, PREG_PATTERN_ORDER);
$result = $result[0];

Perl
@result = $subject =~ m/\d+/g;

Das funktioniert nur bei regulären Ausdrücken, die keine einfangenden Gruppen enthal-
ten. Greifen Sie daher auf nicht-einfangende Gruppen zurück. Details dazu finden Sie in
Rezept 2.9.

Python
Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen,
können Sie die globale Funktion nutzen:
result = re.findall(r"\d+", subject)

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt:
reobj = re.compile(r"\d+")
result = reobj.findall(subject)

Ruby
result = subject.scan(/\d+/)

Diskussion
.NET
Die Methode Matches() der Klasse Regex wendet den regulären Ausdruck wiederholt auf
den String an, bis alle Übereinstimmungen gefunden wurden. Sie liefert ein Objekt vom
Typ MatchCollection zurück, in der sich alle Übereinstimmungen finden. Der Ausgangs-
text ist immer der erste Parameter. In diesem String versucht der reguläre Ausdruck, eine
Übereinstimmung zu finden. Dieser Parameter darf nicht null sein, da Matches() sonst
eine ArgumentNullException wirft.
Wenn Sie die Regex-Treffer nur in ein paar wenigen Strings erhalten wollen, können Sie
die statische Version von Matches() nutzen. Übergeben Sie Ihren Text als ersten und den
regulären Ausdruck als zweiten Parameter. Optionen für den regulären Ausdruck können
Sie als optionalen dritten Parameter mitgeben.
Wenn Sie viele Strings verarbeiten, erstellen Sie zunächst ein Regex-Objekt und rufen
dann dafür Matches() auf. Der Ausgangstext ist dabei der einzige notwendige Parameter.

3.10 Eine Liste aller Übereinstimmungen erhalten | 163


Mit einem optionalen zweiten Parameter können Sie festlegen, ab welcher Zeichenposi-
tion der reguläre Ausdruck seine Überprüfung beginnen soll. Im Prinzip handelt es sich
bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck am Anfang des
Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis zu
einer gewissen Position verarbeitet haben und nun noch den Rest durchsuchen wollen.
Wenn Sie diese Zahl angeben, muss sie größer oder gleich null und kleiner oder gleich
der Länge des Strings sein. Ansonsten wirft IsMatch() eine ArgumentOutOfRangeException.
Die statische Version der Methode ermöglicht keine Angabe, ab welcher Position die
Regex ihre Suche beginnen soll. Es gibt auch keine Version von Matches(), der Sie sagen
können, wo die Suche vor Ende des Strings abbrechen soll. Wenn Sie das wollen, können
Sie Regex.Match("Ausgangstext", start, stop) in einer Schleife aufrufen, wie dies im
nächsten Rezept gezeigt wird, und alle Übereinstimmungen selbst in einer Liste sam-
meln.

Java
Java bietet keine Funktion an, mit der Sie eine Liste von Übereinstimmungen erhalten
können. Aber das lässt sich leicht mit eigenem Code erreichen, indem man Rezept 3.7
anpasst. Anstatt find() in einer if-Anweisung aufzurufen, nutzen Sie eine while-Schleife.
Für die im Beispiel verwendeten Klassen List und ArrayList müssen Sie an den Anfang
Ihres Codes import java.util.*; setzen.

JavaScript
Dieser Code ruft genauso wie in Rezept 3.7 die Funktion string.match() auf. Es gibt
jedoch einen kleinen, aber entscheidenden Unterschied – die Option /g. Regex-Optionen
werden in Rezept 3.4 beschrieben.
Die Option /g teilt der Funktion match() mit, über alle Übereinstimmungen im String zu
iterieren und sie in einem Array abzulegen. Im Codebeispiel enthält list[0] dann die
erste Regex-Übereinstimmung, list[1] die zweite und so weiter. Über list.length
bekommen Sie heraus, wie viele Übereinstimmungen es gibt. Wenn es gar keine Überein-
stimmungen gab, liefert string.match wie üblich null zurück.
Die Elemente im Array sind Strings. Wenn Sie eine Regex mit der Option /g verwenden,
liefert string.match() keine weiteren Details zu den Übereinstimmungen. Benötigen Sie
weitere Informationen, müssen Sie, wie in Rezept 3.11 beschrieben, über die einzelnen
Ergebnisse iterieren.

PHP
In allen bisherigen Rezepten wurde bei PHP die Funktion preg_match() genutzt, die die
erste Regex-Übereinstimmung in einem String findet. preg_match_all() ist eine sehr ähn-
liche Funktion. Der Hauptunterschied ist, dass sie alle Übereinstimmungen findet. Sie
liefert eine Integer-Zahl zurück, die angibt, wie oft die Regex gefunden werden konnte.

164 | Kapitel 3: Mit regulären Ausdrücken programmieren


Die ersten drei Parameter von preg_match_all() sind die gleichen wie die ersten drei von
preg_match(): ein String mit Ihrem regulären Ausdruck, der zu durchsuchende String und
eine Variable, in der ein Array mit den Ergebnissen abgelegt wird. Nur ist dieses Mal der
dritte Parameter nicht optional, und das Array ist immer mehrdimensional.
Für den vierten Parameter geben Sie entweder die Konstante PREG_PATTERN_ORDER oder
PREG_SET_ORDER an. Wenn Sie den Parameter weglassen, wird PREG_PATTERN_ORDER als Stan-
dardwert genutzt.
Mit PREG_PATTERN_ORDER erhalten Sie ein Array, in dem sich die Details des gesamten
Suchergebnisses im nullten Element befinden, während die Details der einfangenden
Gruppen in den darauffolgenden Elementen zu finden sind. Die Länge des Arrays ent-
spricht der Anzahl der einfangenden Gruppen plus eins. Das ist die gleiche Reihenfolge,
die bei preg_match() genutzt wird. Der Unterschied liegt darin, dass jedes Element nun
nicht nur einen String mit dem Suchergebnis enthält – wie es bei preg_match() der Fall ist
–, sondern ein Unterarray mit allen Übereinstimmungen, die von preg_match_all()
gefunden wurden. Die Länge jedes Unterarrays entspricht dem Wert, der von
preg_match_all() zurückgegeben wurde.
Um eine Liste aller Regex-Übereinstimmungen im String zu erhalten, ohne sich für die Texte
zu interessieren, die von einfangenden Gruppen gefunden wurden, geben Sie
PREG_PATTERN_ORDER an und nutzen das nullte Element im Array. Wenn Sie nur an den Texten
interessiert sind, die von einer bestimmten einfangenden Gruppe gefunden wurden, nutzen
Sie PREG_PATTERN_ORDER und die Gruppennummer der einfangenden Gruppe. So erhalten Sie
zum Beispiel nach dem Aufruf von preg_match('%http://([a-z0-9.-]+)%', $subject,
$result) in $result[1] die Liste der Domainnamen aller URLs in Ihrem Ausgangstext.
PREG_SET_ORDER füllt das Array mit den gleichen Strings, aber in einer anderen Kombina-
tion. Die Länge des Arrays entspricht dem Wert, der von preg_match_all() zurückgege-
ben wird. Jedes Element in diesem Array ist ein Unterarray, in dem sich an Position null
das Suchergebnis befindet und die Ergebnisse der einfangenden Gruppen an darauffol-
genden Positionen. Wenn Sie PREG_SET_ORDER angeben, enthält $result[0] das gleiche
Array wie bei einem Aufruf von preg_match().
Sie können PREG_OFFSET_CAPTURE mit PREG_PATTERN_ORDER oder PREG_SET_ORDER kombinie-
ren. Das hat den gleichen Effekt wie die Übergabe von PREG_OFFSET_CAPTURE als vierten
Parameter an preg_match(). Statt in den Elementen des Arrays Strings abzulegen, finden
sich dort Unterarrays mit zwei Elementen – dem String und der Position, an der der
String im Ausgangstext vorkommt.

Perl
In Rezept 3.4 wird erklärt, dass Sie Ihre Regex um den Modifikator /g ergänzen müssen,
um mehr als eine Übereinstimmung im Ausgangstext zu finden. Wenn Sie eine globale
Regex in einem Listenkontext verwenden, wird sie alle Übereinstimmungen finden und
zurückliefern. In diesem Rezept sorgt die List-Variable auf der linken Seite des Zuwei-
sungsoperators für den entsprechenden Kontext.

3.10 Eine Liste aller Übereinstimmungen erhalten | 165


Wenn der reguläre Ausdruck keine einfangenden Gruppen enthält, wird die Liste alle
Suchergebnisse enthalten. Sind aber einfangende Gruppen vorhanden, wird die Liste die
von allen einfangenden Gruppen gefundenen Texte für alle Regex-Übereinstimmungen
enthalten. Das gesamte Suchergebnis ist dann nicht enthalten, sofern Sie nicht noch eine
einfangende Gruppe um die gesamte Regex legen. Möchten Sie nur eine Liste aller Such-
ergebnisse bekommen, ersetzen Sie alle einfangenden durch nicht-einfangende Gruppen.
In Rezept 2.9 werden beide Gruppenarten beschrieben.

Python
Die Funktion findall() im Modul re durchsucht wiederholt einen String, um alle Über-
einstimmungen zum regulären Ausdruck zu finden. Übergeben Sie Ihren regulären Aus-
druck als ersten und den Ausgangstext als zweiten Parameter. Optionen für den
regulären Ausdruck können Sie optional im dritten Parameter mitliefern.
Die Funktion re.findall() ruft re.compile() und dann die Methode findall() für das
kompilierte Regex-Objekt auf. Diese Methode hat nur einen notwendigen Parameter –
den Ausgangstext.
Die Methode findall() besitzt zwei optionale Parameter, die von der globalen Funktion
re.findall() nicht unterstützt werden. Nach dem Ausgangstext können Sie die Zeichen-
position im String angeben, ab der findall() mit ihrer Suche beginnen soll. Lassen Sie
diesen Parameter weg, verarbeitet findall() den gesamten Text. Wenn Sie eine Startposi-
tion angeben, können Sie auch eine Endposition festlegen. Geben Sie keine Endposition
mit, wird die Suche bis zum Ende des Strings durchgeführt.
Egal wie Sie findall() aufrufen – das Ergebnis ist immer eine Liste mit allen Übereinstim-
mungen, die gefunden wurden. Wenn die Regex keine einfangenden Gruppen besitzt,
erhalten Sie eine Liste mit Strings. Sind welche vorhanden, bekommen Sie eine Liste mit
Tupeln, in denen sich der Text aller einfangenden Gruppen für jede Regex-Übereinstim-
mung findet.

Ruby
Die Methode scan() der Klasse String erwartet einen regulären Ausdruck als einzigen
Parameter. Sie iteriert über alle Regex-Übereinstimmungen im String. Ruft man sie ohne
einen Block auf, liefert scan() ein Array mit allen Regex-Übereinstimmungen zurück.
Wenn Ihr regulärer Ausdruck keine einfangenden Gruppen besitzt, gibt scan() ein String-
Array zurück. In diesem Array gibt es für jede Regex-Übereinstimmung ein Element mit
dem gefundenen Text.
Gibt es einfangende Gruppen, liefert scan() ein Array aus Arrays zurück. Das Array ent-
hält ein Element für jede Regex-Übereinstimmung. Jedes dieser Elemente ist ein Array
mit den bei jeder Regex-Übereinstimmung gefundenen Texten. Unterelement null ent-
hält den Text, der von der ersten einfangenden Gruppe gefunden wurde, Unterelement

166 | Kapitel 3: Mit regulären Ausdrücken programmieren


eins enthält den Text der zweiten einfangenden Gruppe und so weiter. Das gesamte
Suchergebnis ist nicht im Array enthalten. Wenn Sie auch dieses brauchen, müssen Sie
Ihren gesamten regulären Ausdruck mit einer zusätzlichen einfangenden Gruppe
umschließen.
Ruby bietet keine Möglichkeit an, sich durch scan() ein Array mit Strings zurückgeben
zu lassen, wenn die Regex einfangende Gruppen besitzt. Sie können dann nur alle
benannten und nummerierten Gruppen durch nicht-einfangende Gruppen ersetzen.

Siehe auch
Rezepte 3.7, 3.11 und 3.12.

3.11 Durch alle Übereinstimmungen iterieren


Problem
Das vorhergehende Rezept hat gezeigt, wie eine Regex wiederholt auf einen String ange-
wendet werden kann, um eine Liste mit Übereinstimmungen zu erhalten. Jetzt wollen Sie
über alle diese Übereinstimmungen in Ihrem eigenen Code iterieren.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
Match matchResult = Regex.Match(subjectString, @"\d+");
while (matchResult.Success) {
// Hier können Sie die in matchResult abgelegten Übereinstimmungen bearbeiten
matchResult = matchResult.NextMatch();
}

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
Regex regexObj = new Regex(@"\d+");
matchResult = regexObj.Match(subjectString);
while (matchResult.Success) {
// Hier können Sie die in matchResult abgelegten Übereinstimmungen bearbeiten
matchResult = matchResult.NextMatch();
}

3.11 Durch alle Übereinstimmungen iterieren | 167


VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
Dim MatchResult = Regex.Match(SubjectString, "\d+")
While MatchResult.Success
'Hier können Sie die in MatchResult abgelegten Übereinstimmungen bearbeiten
MatchResult = MatchResult.NextMatch
End While

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
Dim RegexObj As New Regex("\d+")
Dim MatchResult = RegexObj.Match(SubjectString)
While MatchResult.Success
'Hier können Sie die in MatchResult abgelegten Übereinstimmungen bearbeiten
MatchResult = MatchResult.NextMatch
End While

Java
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
// Hier können Sie die in regexMatcher abgelegten Übereinstimmungen bearbeiten
}

JavaScript
Wenn Ihr regulärer Ausdruck eine Übereinstimmung der Länge null enthalten kann oder
Sie sich da einfach nicht sicher sind, sollten Sie nach Möglichkeit versuchen, Probleme
zwischen den verschiedenen Browsern bezüglich solcher Übereinstimmungen und exec()
zu umgehen:
var regex = /\d+/g;
var match = null;
while (match = regex.exec(subject)) {
// Browser wie Firefox sollen nicht in einer Endlosschleife festsitzen
if (match.index == regex.lastIndex) regex.lastIndex++;
// Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten
}

Wenn Sie sicher sind, dass Ihre Regex niemals eine Übereinstimmung der Länge null fin-
den wird, können Sie direkt über die Regex iterieren:
var regex = /\d+/g;
var match = null;
while (match = regex.exec(subject)) {
// Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten
}

168 | Kapitel 3: Mit regulären Ausdrücken programmieren


PHP
preg_match_all('/\d+/', $subject, $result, PREG_PATTERN_ORDER);
for ($i = 0; $i < count($result[0]); $i++) {
# Gefundener Text = $result[0][$i];
}

Perl
while ($subject =~ m/\d+/g) {
# Gefundener Text = $&
}

Python
Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen,
können Sie die globale Funktion nutzen:
for matchobj in re.finditer(r"\d+", subject):
# Hier können Sie die in matchobj abgelegten Übereinstimmungen bearbeiten

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt:
reobj = re.compile(r"\d+")
for matchobj in reobj.finditer(subject):
# Hier können Sie die in matchobj abgelegten Übereinstimmungen bearbeiten

Ruby
subject.scan(/\d+/) {|match|
# Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten
}

Diskussion
.NET
In Rezept 3.7 wird beschrieben, wie man die Member-Funktion Match() der Klasse Regex
nutzt, um die erste Regex-Übereinstimmung im String zu finden. Um nach und nach alle
Übereinstimmungen im String zu erhalten, rufen wir wieder die Funktion Match() auf,
um die Details der ersten Übereinstimmung zu ermitteln. Diese Funktion liefert eine
Instanz der Klasse Match zurück, die wir in der Variablen matchResult ablegen. Wenn die
Eigenschaft Success des Objekts matchResult den Wert true besitzt, können wir mit der
Schleife beginnen.
Am Anfang der Schleife können Sie die Eigenschaften der Klasse Match verwenden, um
die Details der ersten Übereinstimmung zu nutzen. In Rezept 3.7 wird die Eigenschaft
Value beschrieben, in Rezept 3.8 die Eigenschaften Index und Length und in Rezept 3.9
die Collection Groups.

3.11 Durch alle Übereinstimmungen iterieren | 169


Wenn Sie mit der ersten Übereinstimmung fertig sind, rufen Sie die Member-Funktion
NextMatch() der Variablen matchResult auf. Match.NextMatch() liefert eine Instanz der
Klasse Match zurück – so wie es auch Regex.Match() tut. Die neue Instanz enthält die
Details der zweiten Übereinstimmung.
Durch das Zuweisen des Ergebnisses von matchResult.NextMatch() an die gleiche Variable
matchResult ist es einfach, über alle Übereinstimmungen zu iterieren. Wir müssen immer
nur matchResult.Success prüfen, um herauszufinden, ob NextMatch() tatsächlich noch
eine Übereinstimmung gefunden hat. Wenn NextMatch() fehlschlägt, gibt es dennoch ein
Match-Objekt zurück, aber dessen Eigenschaft Success ist dann auf false gesetzt. Indem
Sie nur eine Variable matchResult nutzen, kann der erste Test und der Test nach dem Auf-
ruf von NextMatch() in einer einzelnen while-Anweisung kombiniert werden.
Der Aufruf von NextMatch() macht das Match-Objekt, für das Sie es aufgerufen haben,
nicht ungültig. Wenn Sie möchten, können Sie das komplette Match-Objekt für jede
Übereinstimmung aufheben.
Der Methode NextMatch() können keine weitere Parameter übergeben werden. Sie nutzt
den gleichen regulären Ausdruck und den gleichen Ausgangstext, den Sie der Methode
Regex.Match() übergeben haben. Das Match-Objekt hält die Verweise auf Ihren regulären
Ausdruck und Ausgangstext.
Sie können die statische Version von Regex.Match() nutzen, auch wenn Ihr Ausgangstext
eine große Zahl von Übereinstimmungen enthält. Regex.Match() kompiliert Ihren regulä-
ren Ausdruck einmal, und das zurückgegebene Match-Objekt merkt sich eine Referenz auf
diese kompilierte Regex. Match.MatchAgain() verwendet den vorher kompilierten regulä-
ren Ausdruck, der vom Match-Objekt referenziert wurde, auch wenn Sie den statischen
Aufruf von Regex.Match() genutzt haben. Sie müssen die Klasse Regex nur dann instantiie-
ren, wenn Sie Regex.Match() wiederholt aufrufen wollen, also die gleiche Regex für ver-
schiedene Strings verwenden.

Java
Das Iterieren über alle Übereinstimmungen in einem String ist in Java sehr einfach. Rufen
Sie die in Rezept 3.7 vorgestellte Methode find() in einer while-Schleife auf. Jeder Aufruf
von find() aktualisiert das Matcher-Objekt mit der nächsten Übereinstimmung und der
Startposition für den folgenden Versuch.

JavaScript
Bevor Sie beginnen, sollten Sie sicherstellen, dass Sie die Option /g gesetzt haben, wenn
Sie Ihre Regex in einer Schleife nutzen wollen. Diese Option wird in Rezept 3.4 beschrie-
ben. while (regexp.exec()) findet alle Zahlen im Ausgangstext, wenn regexp = /\d+/g
gilt. Ist regexp = /\d+/, findet while (regexp.exec()) nur die erste Zahl – und zwar wie-
der und wieder, bis Ihr Skript abstürzt oder vom Browser zum Aufgeben gezwungen
wird.

170 | Kapitel 3: Mit regulären Ausdrücken programmieren


Beachten Sie, dass while (/\d+/g.exec()) (eine Schleife über eine literale Regex mit /g)
ebenfalls in einer Endlosschleife stecken bleiben kann, zumindest bei bestimmten Java-
Script-Implementierungen. Denn der reguläre Ausdruck wird mit jeder Iteration der
while-Schleife neu kompiliert. Dabei wird auch die Startposition für die Übereinstim-
mungssuchen auf den Anfang des Strings zurückgesetzt. Weisen Sie daher den regulären
Ausdruck außerhalb der Schleife einer Variablen zu, um sicherzustellen, dass er nur ein-
mal kompiliert wird.
Die Rezepte 3.8 und 3.9 beschreiben das von regexp.exec() zurückgelieferte Objekt. Hier
ist es das gleiche Objekt, auch wenn Sie exec() in einer Schleife verwenden. Sie können
mit diesem Objekt machen, was Sie wollen.
Einziger Effekt von /g ist das Aktualisieren der Eigenschaft lastIndex des regexp-Objekts,
für das Sie exec() aufrufen. Das funktioniert auch, wenn Sie einen literalen regulären
Ausdruck verwenden, wie in der zweiten JavaScript-Lösung für dieses Rezept gezeigt
wird. Wenn Sie exec() das nächste Mal aufrufen, wird die Übereinstimmungssuche bei
lastIndex beginnen. Weisen Sie lastIndex einen neuen Wert zu, wird die Suche dort wei-
tergehen.
Es gibt bei lastIndex allerdings ein größeres Problem. Wenn Sie den ECMA-262v3-Stan-
dard für JavaScript wörtlich nehmen, sollte exec() den Wert von lastIndex auf das erste
Zeichen nach der Übereinstimmung setzen. Wenn die Übereinstimmung die Länge null
hat, bedeutet das, dass die nächste Suche an der gerade gefundenen Position starten wird
– das führt zu einer Endlosschleife.
Alle Regex-Engines, die in diesem Buch behandelt werden (mit Ausnahme von Java-
Script), umgehen dieses Problem, indem sie den nächsten Suchvorgang automatisch ein
Zeichen weiter beginnen, wenn die vorherige Übereinstimmung die Länge null hat. Das
ist der Grund dafür, dass Rezept 3.7 beschreibt, dass Sie mit lastIndex nicht das Ende der
Übereinstimmung finden können, weil Sie im Internet Explorer falsche Werte erhalten.
Die Firefox-Entwickler waren bei der Implementierung des ECMA-262v3-Standards
allerdings gnadenlos, obwohl das bedeutet, dass regexp.exec() damit in einer Endlos-
schleife landen kann. Und das ist gar nicht so unwahrscheinlich. So können Sie zum Bei-
spiel mit re = /^.*$/gm; while (re.exec()) über alle Zeilen eines mehrzeiligen Strings
iterieren – wenn der String Leerzeilen enthält, wird Firefox dort hängen bleiben.
Sie können das umgehen, indem Sie lastIndex in Ihrem Code um eins erhöhen, wenn die
Funktion exec() das noch nicht selbst getan hat. Die erste JavaScript-Lösung in diesem
Rezept zeigt, wie’s geht. Wenn Sie unsicher sind, kopieren Sie diese Codezeile einfach in
Ihren Code und verschwenden danach keine Gedanken mehr daran.
Bei string.replace() (Rezept 3.14) oder beim Finden aller Übereinstimmungen mit
string.match() (Rezept 3.10) gibt es dieses Problem nicht. Sie nutzen lastIndex zwar
intern, aber der ECMA-262v3-Standard gibt hier an, dass lastIndex für Übereinstimmun-
gen der Länge null erhöht werden muss.

3.11 Durch alle Übereinstimmungen iterieren | 171


PHP
Der Funktion preg_match() kann ein optionaler fünfter Parameter mitgegeben werden.
Dieser gibt die Position im String an, an der die Suche beginnen soll. Sie könnten
Rezept 3.8 anpassen, indem Sie $matchstart + $matchlength beim zweiten Aufruf von
preg_match() als fünften Parameter übergeben, um die zweite Übereinstimmung im
String zu finden, und das für die weiteren Übereinstimmungen wiederholen, bis
preg_match() den Wert 0 zurückgibt. Rezept 3.18 nutzt diese Methode.
Neben dem zusätzlichen Code zum Berechnen der Startposition für jeden Suchvorgang
ist ein wiederholtes Aufrufen von preg_match() ineffizient, da es keine Möglichkeit gibt,
einen kompilierten regulären Ausdruck in einer Variablen zu speichern. preg_match()
muss jedes Mal in seinem Cache nach dem kompilierten regulären Ausdruck suchen,
wenn Sie es aufrufen.
Einfacher und effizienter ist es, preg_match_all() aufzurufen, wie es schon im vorherge-
henden Rezept beschrieben wurde, und über das Array mit den Suchergebnissen zu ite-
rieren.

Perl
In Rezept 3.4 ist beschrieben, dass Sie den Modifikator /g nutzen müssen, um mehr als
eine Übereinstimmung im Ausgangstext zu finden. Wenn Sie eine globale Regex in einem
skalaren Kontext verwenden, wird es die nächste Übereinstimmung suchen. In diesem
Rezept sorgt die while-Anweisung für den skalaren Kontext. Alle speziellen Variablen,
wie zum Beispiel $& (beschrieben in Rezept 3.7), stehen innerhalb der while-Schleife zur
Verfügung.

Python
Die Funktion finditer() aus re liefert einen Iterator zurück, den Sie nutzen können, um
alle Übereinstimmungen des regulären Ausdrucks zu finden. Übergeben Sie Ihren regulä-
ren Ausdruck als ersten und den Ausgangstext als zweiten Parameter. Optionen für den
regulären Ausdruck können Sie optional als dritten Parameter übergeben.
Die Funktion re.finditer() ruft re.compile() und dann die Methode finditer() für das
kompilierte Regex-Objekt auf. Diese Methode hat nur einen Pflichtparameter: den Aus-
gangstext.
Der Methode finditer() können zwei optionale Parameter übergeben werden, die die
globale Funktion re.finditer() nicht unterstützt. Nach dem Ausgangstext können Sie
die Zeichenposition angeben, an der finditer() mit ihrer Suche beginnen soll. Lassen Sie
diesen Parameter weg, wird der Iterator den gesamten Text verarbeiten. Wenn Sie eine
Startposition angeben, können Sie auch eine Endposition festlegen. Geben Sie keine End-
position an, läuft die Suche bis zum Ende des Strings.

172 | Kapitel 3: Mit regulären Ausdrücken programmieren


Ruby
Die Methode scan() der Klasse String erwartet als einzigen Parameter einen regulären
Ausdruck. Dann iteriert sie über alle Übereinstimmungen im String. Wenn sie mit einem
Block aufgerufen wird, können Sie jede Übereinstimmung direkt verarbeiten.
Enthält Ihr regulärer Ausdruck keine einfangenden Gruppen, geben Sie im Block eine Ite-
rator-Variable an. In dieser Variablen findet sich dann ein String mit dem vom regulären
Ausdruck gefundenen Text.
Wenn sich in Ihrer Regex eine oder mehrere einfangende Gruppen befinden, geben Sie
eine Variable für jede Gruppe an. In der ersten Variablen steht der String mit dem von der
ersten Gruppe gefundenen Text, in der zweiten der von der zweiten Gruppe und so wei-
ter. Es wird aber keine Variable mit dem gesamten Suchergebnis gefüllt. Brauchen Sie
aber das gesamte Suchergebnis, müssen Sie Ihren kompletten regulären Ausdruck mit
einer zusätzlichen einfangenden Gruppe umschließen.
subject.scan(/(a)(b)(c)/) {|a, b, c|
# a, b und c enthalten den Text, der von den
# drei einfangenden Gruppen gefunden wurde
}

Geben Sie weniger Variablen an, als es einfangenden Gruppen gibt, werden Sie nur auf
die Gruppen zugreifen können, für die Sie Variablen angegeben haben. Wenn Sie mehr
Variablen angeben, als Gruppen vorhanden sind, werden die zusätzlichen Variablen auf
nil gesetzt.
Wenn Sie nur eine Iterator-Variable angeben und Ihre Regex eine oder mehrere einfan-
gende Gruppen besitzt, wird die Variable mit einem Array aus Strings gefüllt. Das Array
enthält dann einen String für jede einfangende Gruppe. Gibt es lediglich eine Gruppe,
wird das Array auch nur ein Element enthalten:
subject.scan(/(a)(b)(c)/) {|abc|
# abc[0], abc[1] und abc[2] enthalten den von den drei
# einfangenden Gruppen gefundenen Text
}

Siehe auch
Rezepte 3.7, 3.8, 3.10 und 3.12.

3.12 Übereinstimmungen in prozeduralem Code überprüfen


Problem
In Rezept 3.10 wird gezeigt, wie Sie eine Liste aller Übereinstimmungen eines regulären
Ausdrucks in einem String finden können, wenn die Regex wiederholt auf den jeweils
verbleibenden Rest angewendet wird. Jetzt wollen Sie eine Liste mit Übereinstimmungen

3.12 Übereinstimmungen in prozeduralem Code überprüfen | 173


haben, die bestimmte Bedingungen erfüllen sollen. Diese Bedingungen lassen sich aber
nicht (einfach) mit einem regulären Ausdruck beschreiben. Wenn Sie zum Beispiel eine
Liste mit Gewinnzahlen auslesen, möchten Sie nur diejenigen behalten, die sich (ohne
Rest) durch 13 teilen lassen.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
StringCollection resultList = new StringCollection();
Match matchResult = Regex.Match(subjectString, @"\d+");
while (matchResult.Success) {
if (int.Parse(matchResult.Value) % 13 == 0) {
resultList.Add(matchResult.Value);
}
matchResult = matchResult.NextMatch();
}

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
StringCollection resultList = new StringCollection();
Regex regexObj = new Regex(@"\d+");
matchResult = regexObj.Match(subjectString);
while (matchResult.Success) {
if (int.Parse(matchResult.Value) % 13 == 0) {
resultList.Add(matchResult.Value);
}
matchResult = matchResult.NextMatch();
}

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck verarbeiten:
Dim ResultList = New StringCollection
Dim MatchResult = Regex.Match(SubjectString, "\d+")
While MatchResult.Success
If Integer.Parse(MatchResult.Value) Mod 13 = 0 Then
ResultList.Add(MatchResult.Value)
End If
MatchResult = MatchResult.NextMatch
End While

174 | Kapitel 3: Mit regulären Ausdrücken programmieren


Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer gro-
ßen Zahl an Strings verarbeiten wollen:
Dim ResultList = New StringCollection
Dim RegexObj As New Regex("\d+")
Dim MatchResult = RegexObj.Match(SubjectString)
While MatchResult.Success
If Integer.Parse(MatchResult.Value) Mod 13 = 0 Then
ResultList.Add(MatchResult.Value)
End If
MatchResult = MatchResult.NextMatch
End While

Java
List<String> resultList = new ArrayList<String>();
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
if (Integer.parseInt(regexMatcher.group()) % 13 == 0) {
resultList.add(regexMatcher.group());
}
}

JavaScript
var list = [];
var regex = /\d+/g;
var match = null;
while (match = regex.exec(subject)) {
// Browser wie Firefox sollen nicht in einer Endlosschleife hängen bleiben
if (match.index == regex.lastIndex) regex.lastIndex++;
// Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten
if (match[0] % 13 == 0) {
list.push(match[0]);
}
}

PHP
preg_match_all('/\d+/', $subject, $matchdata, PREG_PATTERN_ORDER);
for ($i = 0; $i < count($matchdata[0]); $i++) {
if ($matchdata[0][$i] % 13 == 0) {
$list[] = $matchdata[0][$i];
}
}

Perl
while ($subject =~ m/\d+/g) {
if ($& % 13 == 0) {
push(@list, $&);
}
}

3.12 Übereinstimmungen in prozeduralem Code überprüfen | 175


Python
Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen,
können Sie die globale Funktion nutzen:
list = []
for matchobj in re.finditer(r"\d+", subject):
if int(matchobj.group()) % 13 == 0:
list.append(matchobj.group())

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt:
list = []
reobj = re.compile(r"\d+")
for matchobj in reobj.finditer(subject):
if int(matchobj.group()) % 13 == 0:
list.append(matchobj.group())

Ruby
list = []
subject.scan(/\d+/) {|match|
list << match if (Integer(match) % 13 == 0)
}

Diskussion
Bei regulären Ausdrücken geht es um Text. Auch wenn ‹\d+› zu etwas passt, das wir als
Zahl bezeichnen, ist es für die Regex-Engine nur ein String mit einer oder mehreren Zif-
fern.
Möchten Sie bestimmte Zahlen finden, wie beispielsweise solche, die durch 13 teilbar
sind, ist es viel einfacher, eine allgemeine Regex zu schreiben, die alle Zahlen findet, und
dann ein bisschen prozeduralen Code zu nutzen, um alle Regex-Übereinstimmungen zu
überspringen, an denen Sie kein Interesse haben.
Die Lösungen für dieses Rezept basieren alle auf den Lösungen des vorherigen Rezepts,
in dem gezeigt wird, wie man über alle Übereinstimmungen iteriert. Innerhalb der
Schleife wandeln wir das Suchergebnis des regulären Ausdrucks in eine Zahl um.
Bei manchen Sprachen geschieht das automatisch, bei anderen muss explizit eine Funk-
tion aufgerufen werden, um den String in eine Integer-Zahl umzuwandeln. Dann wird
überprüft, ob die Zahl durch 13 teilbar ist. Wenn das geht, wird die Regex-Übereinstim-
mung der Liste hinzugefügt. Ansonsten wird sie übersprungen.

Siehe auch
Rezepte 3.7, 3.10 und 3.11.

176 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.13 Eine Übereinstimmung in einer anderen
Übereinstimmung finden
Problem
Sie wollen alle Übereinstimmungen eines bestimmten regulären Ausdrucks finden – aber
nur innerhalb bestimmter Abschnitte des Ausgangstexts. Ein anderer regulärer Ausdruck
bestimmt jeden dieser Abschnitte im String.
Stellen Sie sich vor, Sie haben eine HTML-Datei, in der eine Reihe von Abschnitten mit
<b>-Tags als fett gekennzeichnet sind. Sie wollen nun alle Zahlen finden, die als fett mar-
kiert sind. Enthält ein fetter Text mehrere Zahlen, wollen Sie alle getrennt finden. Wenn
Sie also zum Beispiel den String 1 <b>2</b> 3 4 <b>5 6 7</b> verarbeiten, wollen Sie vier
Übereinstimmungen finden: 2, 5, 6 und 7.

Lösung
C#
StringCollection resultList = new StringCollection();
Regex outerRegex = new Regex("<b>(.*?)</b>;", RegexOptions.Singleline);
Regex innerRegex = new Regex(@"\d+");
// Ersten Abschnitt finden
Match outerMatch = outerRegex.Match(subjectString);
while (outerMatch.Success) {
// Übereinstimmungen im Abschnitt finden
Match innerMatch = innerRegex.Match(outerMatch.Groups[1].Value);
while (innerMatch.Success) {
resultList.Add(innerMatch.Value);
innerMatch = innerMatch.NextMatch();
}
// Nächsten Abschnitt finden
outerMatch = outerMatch.NextMatch();
}

VB.NET
Dim ResultList = New StringCollection
Dim OuterRegex As New Regex("<b>(.*?)</b>", RegexOptions.Singleline)
Dim InnerRegex As New Regex("\d+")
'Ersten Abschnitt finden
Dim OuterMatch = OuterRegex.Match(SubjectString)
While OuterMatch.Success
'Übereinstimmungen im Abschnitt finden
Dim InnerMatch = InnerRegex.Match(OuterMatch.Groups(1).Value)
While InnerMatch.Success
ResultList.Add(InnerMatch.Value)
InnerMatch = InnerMatch.NextMatch

3.13 Eine Übereinstimmung in einer anderen Übereinstimmung finden | 177


End While
OuterMatch = OuterMatch.NextMatch
End While

Java
Iterieren mithilfe von zwei Matchern ist einfach und funktioniert ab Java 4:
List<String> resultList = new ArrayList<String>();
Pattern outerRegex = Pattern.compile("<b>(.*?)</b>", Pattern.DOTALL);
Pattern innerRegex = Pattern.compile("\\d+");
Matcher outerMatcher = outerRegex.matcher(subjectString);
while (outerMatcher.find()) {
Matcher innerMatcher = innerRegex.matcher(outerMatcher.group());
while (innerMatcher.find()) {
resultList.add(innerMatcher.group());
}
}

Der folgende Code ist effizienter (weil innerMatcher nur einmal erzeugt wird), aber man
benötigt mindestens Java 5:
List<String> resultList = new ArrayList<String>();
Pattern outerRegex = Pattern.compile("<b>(.*?)</b>", Pattern.DOTALL);
Pattern innerRegex = Pattern.compile("\\d+");
Matcher outerMatcher = outerRegex.matcher(subjectString);
Matcher innerMatcher = innerRegex.matcher(subjectString);
while (outerMatcher.find()) {
innerMatcher.region(outerMatcher.start(), outerMatcher.end());
while (innerMatcher.find()) {
resultList.add(innerMatcher.group());
}
}

JavaScript
var result = [];
var outerRegex = /<b>([\s\S]*?)<\/b>/g;
var innerRegex = /\d+/g;
var outerMatch = null;
while (outerMatch = outerRegex.exec(subject)) {
if (outerMatch.index == outerRegex.lastIndex)
outerRegex.lastIndex++;
var innerSubject = subject.substr(outerMatch.index,
outerMatch[0].length);
var innerMatch = null;
while (innerMatch = innerRegex.exec(innerSubject)) {
if (innerMatch.index == innerRegex.lastIndex)
innerRegex.lastIndex++;
result.push(innerMatch[0]);
}
}

178 | Kapitel 3: Mit regulären Ausdrücken programmieren


PHP
$list = array();
preg_match_all('%<b>(.*?)</b>%s', $subject, $outermatches,
PREG_PATTERN_ORDER);
for ($i = 0; $i < count($outermatches[0]); $i++) {
if (preg_match_all('/\d+/', $outermatches[0][$i], $innermatches,
PREG_PATTERN_ORDER)) {
$list = array_merge($list, $innermatches[0]);
}
}

Perl
while ($subject =~ m!<b>(.*?)</b>!gs) {
push(@list, ($& =~ m/\d+/g));
}

Das funktioniert nur, wenn der innere reguläre Ausdruck (in diesem Beispiel ‹\d+›) keine
einfangenden Gruppen enthält, daher sollten Sie dort nicht-einfangende Gruppen ver-
wenden. Details dazu finden Sie in Rezept 2.9.

Python
list = []
innerre = re.compile(r"\d+")
for outermatch in re.finditer("(?s)<b>(.*?)</b>", subject):
list.extend(innerre.findall(outermatch.group(1)))

Ruby
list = []
subject.scan(/<b>(.*?)<\/b>/m) {|outergroups|
list += outergroups[0].scan(/\d+/)
}

Diskussion
Reguläre Ausdrücke sind wunderbar dazu geeignet, Eingaben in Tokens aufzuteilen, aber
weniger dazu, Eingaben zu parsen. Aufteilen in Tokens bedeutet, verschiedene Teile
eines Strings zu erkennen, wie zum Beispiel Zahlen, Wörter, Symbole, Tags, Kommen-
tare und so weiter. Dazu muss der Text von links nach rechts gescannt und unterschied-
liche Alternativen und Mengen von Buchstaben gefunden werden. Reguläre Ausdrücke
können damit sehr gut umgehen.
Parsen bedeutet, die Beziehungen zwischen diesen Tokens herzustellen. So bilden zum
Beispiel in einer Programmiersprache die Kombinationen solcher Tokens Anweisungen,
Funktionen, Klassen, Namensräume und so weiter. Es ist am einfachsten, das Erfassen
der Bedeutung von Tokens innerhalb des größeren Eingabekontexts prozeduralem Code

3.13 Eine Übereinstimmung in einer anderen Übereinstimmung finden | 179


zu überlassen. Insbesondere können reguläre Ausdrücke keine nicht linearen Kontexte
verfolgen, wie zum Beispiel bei verschachtelten Konstrukten.1
Das Finden einer Art von Token innerhalb einer anderen Art von Token ist eine Aufgabe,
die man gern mit regulären Ausdrücken angeht. Ein Paar HTML-Tags für Fettschrift lässt
sich leicht durch den regulären Ausdruck ‹<b>(.*?)</b>› finden.2 Eine Zahl ist noch ein-
facher durch die Regex ‹\d+› gefunden. Aber wenn Sie versuchen, dies beides zu einer
einzelnen Regex zu kombinieren, landen Sie bei etwas deutlich anderem:
\d+(?=(?:.(?!<b>).)*</b>)
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck ist zwar eine Lösung für das Problem dieses Rezepts, aber er ist
nicht wirklich intuitiv. Selbst ein Experte für reguläre Ausdrücke wird die Regex sorgfäl-
tig analysieren müssen, um herauszufinden, was sie tut, oder ein Tool nutzen, um
die Fundstellen hervorzuheben. Und das ist die Kombination aus nur zwei einfachen
Regexes.
Es ist besser, die beiden regulären Ausdrücke so zu belassen, wie sie sind, und sie nur mit
prozeduralem Code zu kombinieren. Der sich daraus ergebende Code ist zwar ein biss-
chen länger, aber viel einfacher zu verstehen und zu warten. Und schließlich ist ein
Hauptgrund für die Verwendung regulärer Ausdrücke ja schließlich, einfachen Code
erstellen zu können. Eine Regex wie ‹<b>(.*?)</b>› lässt sich von jedermann leicht ver-
stehen, der ein bisschen Erfahrungen mit regulären Ausdrücken besitzt. Zudem würde
man ohne sie viel mehr Zeilen Code brauchen, der sich auch noch schlechter warten
ließe.
Obwohl die Lösungen für diese Rezepte zu den komplexesten in diesem Kapitel gehören,
sind sie doch immer noch recht einfach. Es werden zwei reguläre Ausdrücke verwendet.
Der „äußere“ reguläre Ausdruck passt zu den HTML-Bold-Tags, und dem Text zwischen
dem Start- und dem End-Tag wird durch die erste einfangende Gruppe erfasst. Dieser
reguläre Ausdruck ist mit dem gleichen Code implementiert, der auch in Rezept 3.11
genutzt wurde. Den Unterschied bildet nur der Platzhalter-Kommentar, der nun durch
Code ersetzt wurde, der mit dem „inneren“ regulären Ausdruck arbeitet.
Der zweite reguläre Ausdruck passt zu einer Ziffer. Diese Regex ist mit dem gleichen
Code implementiert, der in Rezept 3.10 vorgestellt wurde. Nur wird dieses Mal der Aus-
gangstext nicht komplett verarbeitet, sondern die zweite Regex wird auf den Teil des
Texts angewandt, der von der ersten einfangenden Gruppe des äußeren regulären Aus-
drucks gefunden wurde.

1 Ein paar moderne Regex-Varianten haben versucht, Features für ein balanciertes oder rekursives Finden ein-
zuführen. Diese Features führen aber zu so komplexen regulären Ausdrücken, dass sie nur unsere These
unterstützen, das Parsen am besten prozeduralem Code zu überlassen.
2 Damit das Tag auch mehrere Zeilen umfassen kann, müssen Sie den „Punkt passt zu Zeilenumbruch“-Modus
aktivieren. In JavaScript verwenden Sie ‹<b>([\s\S]*?)</b>›.

180 | Kapitel 3: Mit regulären Ausdrücken programmieren


Es gibt zwei Möglichkeiten, den inneren regulären Ausdruck auf den Text einzuschrän-
ken, der vom äußeren regulären Ausdruck oder einer einfangenden Gruppe des äußeren
regulären Ausdrucks gefunden wurde. Manche Sprachen bieten eine Funktion an, mit
der der reguläre Ausdruck nur auf einen Teil eines Strings angewandt werden kann.
Damit erspart man sich eine zusätzliche String-Kopie, wenn die Match-Funktion nicht
sowieso eine Struktur mit dem gefundenen Text aus den einfangenden Gruppen füllt.
Wir können auf jeden Fall immer den Substring auslesen, der von der einfangenden
Gruppe gefunden wurde, und die innere Regex auf ihn anwenden.
Bei beiden Varianten ist es schneller, beide reguläre Ausdrücke zusammen in einer
Schleife zu nutzen als den einen regulären Ausdruck mit seinen verschachtelten Look-
ahead-Gruppen. Bei Letzterem muss die Regex-Engine eine ganze Menge Backtracking
vornehmen. Bei großen Dateien wird die Verwendung dieser einen Regex viel langsamer
sein, da sie die Abschnittsgrenzen (die HTML-Bold-Tags) bei allen Zahlen im Ausgangs-
text finden muss – auch bei solchen, die sich gar nicht zwischen <b>-Tags befinden. Die
Lösung mit den zwei regulären Ausdrücken sucht erst nach Zahlen, wenn sie die
Abschnittsgrenzen gefunden hat. Und das geschieht mit linearem Zeitaufwand.

Siehe auch
Rezepte 3.8, 3.10 und 3.11.

3.14 Alle Übereinstimmungen ersetzen


Problem
Sie wollen alle Übereinstimmungen des regulären Ausdrucks ‹vorher› durch den Text
«danach» ersetzen.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string resultString = Regex.Replace(subjectString, "vorher", "danach");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
string resultString = null;
try {
resultString = Regex.Replace(subjectString, "vorher", "danach");
} catch (ArgumentNullException ex) {
// Regulärer Ausdruck, Ausgangstext oder Ersetzungstext

3.14 Alle Übereinstimmungen ersetzen | 181


// ist null
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex("vorher");
string resultString = regexObj.Replace(subjectString, "danach");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung des
Regex-Objekts mit einem kompletten Exception Handling versehen:
string resultString = null;
try {
Regex regexObj = new Regex("vorher");
try {
resultString = regexObj.Replace(subjectString, "danach");
} catch (ArgumentNullException ex) {
// Ausgangstext oder Ersetzungstext ist null
}
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim ResultString = Regex.Replace(SubjectString, "vorher", "danach")

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
Dim ResultString As String = Nothing
Try
ResultString = Regex.Replace(SubjectString, "before", "after")
Catch ex As ArgumentNullException
'Regulärer Ausdruck, Ausgangstext oder Ersetzungstext
'ist null
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("vorher")
Dim ResultString = RegexObj.Replace(SubjectString, "danach")

182 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung des
Regex-Objekts mit einem kompletten Exception Handling versehen:
Dim ResultString As String = Nothing
Try
Dim RegexObj As New Regex("vorher")
Try
ResultString = RegexObj.Replace(SubjectString, "danach")
Catch ex As ArgumentNullException
'Ausgangstext oder Ersetzungstext ist null
End Try
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Java
Sie können den statischen Aufruf nutzen, wenn Sie nur einen String mit dem gleichen
regulären Ausdruck bearbeiten wollen:
String resultString = subjectString.replaceAll("vorher", "danach");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
try {
String resultString = subjectString.replaceAll("vorher", "danach");
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
} catch (IllegalArgumentException ex) {
// Syntaxfehler im Ersetzungstext (unmaskierte $-Zeichen?)
} catch (IndexOutOfBoundsException ex) {
// Nicht vorhandene Rückwärtsreferenzen im Ersetzungstext verwendet
}

Erstellen Sie ein Matcher-Objekt, wenn Sie den gleichen regulären Ausdruck für viele
Strings verwenden wollen:
Pattern regex = Pattern.compile("vorher");
Matcher regexMatcher = regex.matcher(subjectString);
String resultString = regexMatcher.replaceAll("danach");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung der
Pattern- und Matcher-Objekte mit einem kompletten Exception Handling versehen:
String resultString = null;
try {
Pattern regex = Pattern.compile("vorher");
Matcher regexMatcher = regex.matcher(subjectString);
try {
resultString = regexMatcher.replaceAll("danach");
} catch (IllegalArgumentException ex) {
// Syntaxfehler im Ersetzungstext (unmaskierte $-Zeichen?)
} catch (IndexOutOfBoundsException ex) {
// Nicht vorhandene Rückwärtsreferenzen im Ersetzungstext verwendet

3.14 Alle Übereinstimmungen ersetzen | 183


}
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
}

JavaScript
result = subject.replace(/vorher/g, "danach");

PHP
$result = preg_replace('/vorher/', 'danach', $subject);

Perl
Befindet sich der Ausgangstext in der speziellen Variablen $_ und kann das Ergebnis in $_
genutzt werden:
s/vorher/danach/g;

Befindet sich der Ausgangstext in der Variablen $subject und soll das Ergebnis dort auch
wieder abgelegt werden:
$subject =~ s/vorher/danach/g;

Befindet sich der Ausgangstext in der Variablen $subject und soll das Ergebnis in $result
abgelegt werden:
($result = $subject) =~ s/vorher/danach/g;

Python
Wenn Sie nur ein paar Strings verarbeiten müssen, können Sie die globale Funktion ver-
wenden:
result = re.sub("vorher", "danach", subject)

Um die gleiche Regex wiederholt zu nutzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile("vorher")
result = reobj.sub("danach", subject)

Ruby
result = subject.gsub(/vorher/, 'danach')

Diskussion
.NET
In .NET werden Sie immer die Methode Regex.Replace() nutzen, um mithilfe eines regu-
lären Ausdrucks zu suchen und zu ersetzen. Die Methode Replace() ist zehnfach über-
laden. Die Hälfte – die hier besprochenen Versionen – erwartet einen String als

184 | Kapitel 3: Mit regulären Ausdrücken programmieren


Ersetzungstext. Die andere Hälfte erwartet ein MatchEvaluator-Delegate als Ersetzungsob-
jekt. Diese werden in Rezept 3.16 besprochen.
Der erste Parameter, der an Replace() übergeben werden muss, ist immer der String mit
dem Ausgangstext, in dem Sie suchen und ersetzen wollen. Dieser Parameter sollte nie-
mals null sein. Ansonsten wird Replace() eine ArgumentNullException werfen. Der Rück-
gabewert von Replace() ist immer der String mit den angewandten Ersetzungen.
Wenn Sie den regulären Ausdruck nur ein paar Mal nutzen wollen, können Sie einen sta-
tischen Aufruf einsetzen. Der zweite Parameter ist dann der reguläre Ausdruck, der anzu-
wenden ist. Der Ersetzungstext wird als dritter Parameter übergeben. Optionen für die
Regex lassen sich als optionaler vierter Parameter definieren. Wenn Ihr regulärer Aus-
druck einen Syntaxfehler enthält, wird eine ArgumentException geworfen.
Möchten Sie den gleichen regulären Ausdruck auf viele Strings anwenden, können Sie
Ihren Code effizienter machen, indem Sie zunächst ein Regex-Objekt erstellen und dann
für dieses Objekt Replace() aufrufen. Übergeben Sie dabei den Ausgangstext als ersten
und den Ersetzungstext als zweiten Parameter. Das sind die einzigen erforderlichen Para-
meter.
Wenn Sie Replace() für eine Instanz der Regex-Klasse aufrufen, können Sie zusätzliche
Parameter übergeben, um den Bereich zum Suchen und Ersetzen einzuschränken. Lassen
Sie diese Parameter weg, werden alle Übereinstimmungen des regulären Ausdrucks im
Ausgangstext ersetzt. Die statisch überladenen Versionen von Replace() ermöglichen
diese zusätzlichen Parameter nicht, sie ersetzen immer alle Übereinstimmungen.
Als optionalen dritten Parameter nach dem Text und dem Ersetzungstext können Sie die
Anzahl der vorzunehmenden Ersetzungen angeben. Wenn Sie eine Zahl größer als eins
übergeben, ist das die maximale Anzahl an zu ersetzenden Übereinstimmungen. So
ersetzt zum Beispiel Replace(subject, replacement, 3) nur die ersten drei Übereinstim-
mungen der Regex. Weitere Übereinstimmungen werden ignoriert. Wenn es weniger als
drei mögliche Übereinstimmungen im String gibt, werden alle Übereinstimmungen
ersetzt. Sie erhalten aber keine Information darüber, dass weniger ersetzt wurde, als Sie
angefordert haben. Wenn Sie als dritten Parameter den Wert 0 übergeben, werden kei-
nerlei Ersetzungen vorgenommen, und der Ausgangstext wird unverändert zurückgege-
ben. Geben Sie -1 an, werden alle Regex-Übereinstimmungen ersetzt. Eine Zahl kleiner
als -1 führt dazu, dass Replace() eine ArgumentOutOfRangeException wirft.
Geben Sie über den dritten Parameter die Anzahl der Ersetzungen an, können Sie einen
optionalen vierten Parameter festlegen, der bestimmt, an welcher Zeichenposition mit der
Suche begonnen werden soll. Im Prinzip ist das die Anzahl der Zeichen am Anfang des
Strings, die der reguläre Ausdruck ignorieren soll. Das kann nützlich sein, wenn Sie den
String schon bis zu einer bestimmten Position verarbeitet haben und nun nur noch den
Rest des Strings durchsuchen und ersetzen lassen wollen. Geben Sie diese Zahl an, muss sie
zwischen null und der Länge des Ausgangstexts liegen. Ansonsten wirft Replace() eine
ArgumentOutOfRangeException. Anders als Match() ermöglicht Replace() nicht die Angabe
eines Parameters, der die Länge des zu durchsuchenden Substrings definiert.

3.14 Alle Übereinstimmungen ersetzen | 185


Java
Wenn Sie nur einen String mit einer Regex durchsuchen und ersetzen lassen wollen, kön-
nen Sie entweder die Methode replaceFirst() oder replaceAll() für Ihren String direkt
aufrufen. Beide Methoden erwarten zwei Parameter: einen String mit Ihrem regulären
Ausdruck und einen mit Ihrem Ersetzungstext. Das sind sehr praktische Funktionen, die
intern Pattern.compile("vorher").matcher(subjectString).replaceFirst("danach") und
Pattern.compile("vorher").matcher(subjectString).replaceAll("danach") aufrufen.
Möchten Sie die gleiche Regex für mehrere Strings aufrufen, sollten Sie das Matcher-
Objekt wie in Rezept 3.3 beschrieben erstellen. Dann rufen Sie für Ihren Matcher
replaceFirst() oder replaceAll() auf und übergeben den Ersetzungstext als einzigen
Parameter.
Es gibt drei verschiedene Exception-Klassen, die Sie berücksichtigen müssen, wenn die
Regex und der Ersetzungstext vom Endanwender bereitgestellt werden. Die Exception-
Klasse PatternSyntaxException wird von Pattern.compile(), String.replaceFirst() und
String.replaceAll() geworfen, wenn der reguläre Ausdruck einen Syntaxfehler enthält.
IllegalArgumentException wird von replaceFirst() und replaceAll() geworfen, wenn es
einen Syntaxfehler im Ersetzungstext gibt. Ist der Ersetzungstext syntaktisch korrekt, ver-
weist aber auf eine einfangende Gruppe, die nicht vorhanden ist, wird stattdessen Index-
OutOfBoundsException geworfen.

JavaScript
Um einen String mit einem regulären Ausdruck zu durchsuchen und Ersetzungen vorzu-
nehmen, rufen Sie für den String die Funktion replace() auf. Übergeben Sie Ihren regulä-
ren Ausdruck als ersten und Ihren String mit dem Ersetzungstext als zweiten Parameter.
Die Funktion replace() gibt einen neuen String zurück, in dem die Ersetzungen ange-
wendet wurden.
Wenn Sie alle Regex-Übereinstimmungen im String ersetzen wollen, setzen Sie die Option
/g beim Erzeugen Ihres Regex-Objekts. In Rezept 3.4 wird beschrieben, wie das funktio-
niert. Nutzen Sie die Option /g nicht, wird nur die erste Übereinstimmung ersetzt.

PHP
Sie können in einem String ganz einfach suchen und ersetzen, indem Sie die Funktion
preg_replace() aufrufen. Übergeben Sie Ihren regulären Ausdruck als ersten, den Erset-
zungstext als zweiten und den Ausgangstext als dritten Parameter. Der zurückgegebene
Wert ist ein String mit den angewendeten Ersetzungen.
Der optionale vierte Parameter ermöglicht es Ihnen, die Anzahl der Ersetzungen zu
beschränken. Lassen Sie den Parameter weg oder geben -1 an, werden alle Regex-
Übereinstimmungen ersetzt. Geben Sie 0 an, werden keine Ersetzungen vorgenommen.
Nutzen Sie eine positive Zahl, wird preg_replace() nur höchstens so viele Regex-Über-
einstimmungen ersetzen, wie Sie angegeben haben. Gibt es weniger Übereinstimmungen,
werden alle ohne Fehlermeldung ersetzt.

186 | Kapitel 3: Mit regulären Ausdrücken programmieren


Möchten Sie wissen, wie viele Ersetzungen vorgenommen wurden, können Sie dem Auf-
ruf einen fünften Parameter mitgeben. In dieser Variablen wird eine Integer-Zahl zurück-
geliefert, die die Anzahl der tatsächlich vorgenommenen Ersetzungen angibt.
Ein besonderes Feature von preg_replace() ist, dass Sie für die ersten drei Parameter auch
Arrays statt Strings übergeben können. Übergeben Sie als dritten Parameter ein Array aus
Strings statt einen einzelnen String, liefert preg_replace() ein Array mit allen Strings
zurück, in denen Ersetzungen vorgenommen wurden.
Übergeben Sie als ersten Parameter ein Array mit Regex-Strings, nutzt preg_replace() die
regulären Ausdrücke nacheinander, um im Ausgangstext zu suchen und zu ersetzen.
Übergeben Sie ein Array mit Ausgangs-Strings, werden alle regulären Ausdrücke auf alle
Ausgangs-Strings angewendet. Suchen Sie mit einem Array mit mehreren regulären Aus-
drücken, können Sie entweder einen einzelnen String als Ersetzungstext angeben (der
dann von allen Regexes genutzt wird) oder ein Array mit Ersetzungs-Strings übergeben.
Nutzen Sie zwei Arrays, arbeitet sich preg_replace() sowohl durch das Regex- als auch
durch das Ersetzungstext-Array und nutzt für jede Regex einen anderen Ersetzungstext.
preg_replace() geht dabei nach der Reihenfolge im Speicher vor, die nicht notwendiger-
weise der numerischen Reihenfolge der Indexe im Array entsprechen muss. Wenn Sie das
Array nicht in numerischer Reihenfolge aufgebaut haben, rufen Sie für die Arrays mit den
regulären Ausdrücken und den Ersetzungstexten die Funktion ksort() auf, bevor Sie sie
an preg_replace() übergeben.
Dieses Beispiel baut das Array $replace in umgekehrter Reihenfolge auf:
$regex[0] = '/a/';
$regex[1] = '/b/';
$regex[2] = '/c/';
$replace[2] = '3';
$replace[1] = '2';
$replace[0] = '1';

echo preg_replace($regex, $replace, "abc");


ksort($replace);
echo preg_replace($regex, $replace, "abc");

Der erste Aufruf von preg_replace() gibt 321 aus, was vermutlich nicht Ihr Wunschergeb-
nis ist. Nach dem Anwenden von ksort() liefert die Ersetzung wie gewünscht 123.
ksort() verändert die ihr übergebene Variable. Übergeben Sie nicht ihren Rückgabewert
(true oder false) an preg_replace.

Perl
In Perl ist s/// der Substitutionsoperator. Nutzen Sie s/// allein, wird es die Variable $_
durchsuchen und ersetzen und das Ergebnis wieder in $_ ablegen.
Wenn Sie den Substitutionsoperator für eine andere Variable nutzen wollen, verwenden
Sie den Bindungsoperator =~, um den Substitutionsoperator mit Ihrer Variablen zu ver-
knüpfen. Binden Sie den Substitutionsoperator an einen String, wird das Suchen und

3.14 Alle Übereinstimmungen ersetzen | 187


Ersetzen direkt ausgeführt. Das Ergebnis wird wieder in der Variablen abgelegt, in der
sich der Ausgangstext befindet.
Der Operator s/// verändert immer die Variable, an den Sie ihn binden. Wenn Sie das
Ergebnis eines Such- und Ersetzungsvorgangs in einer neuen Variablen ablegen wollen,
ohne das Original zu verändern, weisen Sie zunächst den ursprünglichen String der
Ergebnisvariablen zu und binden dann den Substitutionsoperator an diese Variable. Die
Perl-Lösung für dieses Rezept zeigt, wie Sie diese beiden Schritte in einer Codezeile
zusammenfassen können.
Mit dem Modifikator /g, der in Rezept 3.4 erklärt wurde, ersetzen Sie alle Regex-Über-
einstimmungen. Ohne ihn ersetzt Perl nur die erste Übereinstimmung.

Python
Die Funktion sub() im Modul re sucht und ersetzt mithilfe eines regulären Ausdrucks.
Übergeben Sie Ihren regulären Ausdruck als ersten Parameter, den Ersetzungstext als
zweiten und den Ausgangstext als dritten Parameter. Der globalen Funktion sub() kann
kein Parameter mit Regex-Optionen übergeben werden.
Die Funktion re.sub() ruft re.compile() und dann die Methode sub() des kompilierten
Regex-Objekts auf. Diese Methode besitzt zwei Parameter, die übergeben werden müs-
sen: den Ersetzungstext und den Ausgangstext.
Beide Formen von sub() geben einen String zurück, in dem alle Übereinstimmungen
ersetzt wurden. Sie können einen optionalen Parameter übergeben, der die Anzahl der
Ersetzungen begrenzt. Wenn Sie ihn weglassen oder auf null setzen, werden alle Regex-
Übereinstimmungen ersetzt. Übergeben Sie eine positive Zahl, ist dies die maximale
Anzahl an Ersetzungen, die vorgenommen werden. Werden weniger Übereinstimmun-
gen gefunden, werden alle Übereinstimmungen ohne Fehlermeldung ersetzt.

Ruby
Die Methode gsub() der Klasse String sucht und ersetzt mithilfe eines regulären Aus-
drucks. Übergeben Sie den regulären Ausdruck als ersten Parameter und einen String mit
dem Ersetzungstext als zweiten Parameter. Der Rückgabewert ist ein neuer String mit
den durchgeführten Ersetzungen. Konnten keine Regex-Übereinstimmungen gefunden
werden, gibt gsub() den ursprünglichen String zurück.
gsub() verändert nicht den String, für den Sie sie aufgerufen haben. Soll der ursprüngli-
che String angepasst werden, nutzen Sie stattdessen gsub!(). Wird keine Regex-Überein-
stimmung gefunden, liefert gsub!() den Wert nil zurück. Ansonsten wird der veränderte
String zurückgegeben.

Siehe auch
„Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und die Rezepte 3.15 und
3.16.

188 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.15 Übereinstimmungen durch Teile des gefundenen Texts
ersetzen
Problem
Sie wollen einen Text durchsuchen und Teile der gefundenen Texte wiederum beim
Ersetzen verwenden. Die erneut einzusetzenden Teile sind in Ihrem regulären Ausdruck
durch einfangende Gruppen definiert, die in Rezept 2.9 beschrieben wurden.
So wollen Sie in diesem Beispiel Wortpaare finden, die durch ein Gleichheitszeichen
getrennt sind, und diese Wörter beim Ersetzen austauschen.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string resultString = Regex.Replace(subjectString, @"(\w+)=(\w+)", "$2=$1");

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex(@"(\w+)=(\w+)");
string resultString = regexObj.Replace(subjectString, "$2=$1");

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim ResultString = Regex.Replace(SubjectString, "(\w+)=(\w+)", "$2=$1")

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("(\w+)=(\w+)")
Dim ResultString = RegexObj.Replace(SubjectString, "$2=$1")

Java
Sie können String.replaceAll() aufrufen, wenn Sie nur einen String mit dem gleichen
regulären Ausdruck bearbeiten wollen:
String resultString = subjectString.replaceAll("(\\w+)=(\\w+)", "$2=$1");

3.15 Übereinstimmungen durch Teile des gefundenen Texts ersetzen | 189


Erstellen Sie ein Matcher-Objekt, wenn Sie den gleichen regulären Ausdruck für viele
Strings verwenden wollen:
Pattern regex = Pattern.compile("(\\w+)=(\\w+)");
Matcher regexMatcher = regex.matcher(subjectString);
String resultString = regexMatcher.replaceAll("$2=$1");

JavaScript
result = subject.replace(/(\w+)=(\w+)/g, "$2=$1");

PHP
$result = preg_replace('/(\w+)=(\w+)/', '$2=$1', $subject);

Perl
$subject =~ s/(\w+)=(\w+)/$2=$1/g;

Python
Wenn Sie nur ein paar Strings verarbeiten müssen, können Sie die globale Funktion ver-
wenden:
result = re.sub(r"(\w+)=(\w+)", r"\2=\1", subject)

Um die gleiche Regex mehrfach zu nutzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile(r"(\w+)=(\w+)")
result = reobj.sub(r"\2=\1", subject)

Ruby
result = subject.gsub(/(\w+)=(\w+)/, '\2=\1')

Diskussion
Der reguläre Ausdruck ‹(\w+)=(\w+)› passt auf ein Wortpaar und „fängt“ jedes Wort in
seiner eigenen einfangenden Gruppe. Das Wort vor dem Gleichheitszeichen wird durch
die erste Gruppe eingefangen, das Wort nach dem Zeichen durch die zweite Gruppe.
Beim Ersetzen müssen Sie angeben, dass Sie den von der zweiten einfangenden Gruppe
gefundenen Text nutzen wollen, gefolgt von einem Gleichheitszeichen und dem Text,
der von der ersten einfangenden Gruppe gefunden wurde. Das erreichen Sie mit speziel-
len Platzhaltern im Ersetzungstext. Die Syntax für den Ersetzungstext ist in den verschie-
denen Programmiersprachen unterschiedlich. In „Suchen und Ersetzen mit regulären
Ausdrücken“ in Kapitel 1 werden die Ersetzungstextvarianten beschrieben, und in
Rezept 2.21 wird erklärt, wie im Ersetzungstext auf einfangende Gruppen verwiesen
wird.

190 | Kapitel 3: Mit regulären Ausdrücken programmieren


.NET
In .NET können Sie die gleiche Methode Regex.Replace() verwenden, die schon im vor-
hergehenden Rezept mit einem String als Ersatztext verwendet wurde. Die Syntax für die
Verwendung von Rückwärtsreferenzen im Ersetzungstext entspricht der .NET-Variante
für Ersetzungstexte. Diese wird in Rezept 2.21 beschrieben.

Java
In Java können Sie die gleichen Methoden replaceFirst() und replaceAll() verwenden,
die schon im vorhergehenden Rezept beschrieben wurden. Die Syntax für das Hinzufü-
gen von Rückwärtsreferenzen entspricht der hier im Buch beschriebenen Java-Variante
für Ersetzungstexte.

JavaScript
In JavaScript können Sie die gleiche Methode string.replace() verwenden, die schon im
vorhergehenden Rezept beschrieben wurde. Die Syntax für das Hinzufügen von Rück-
wärtsreferenzen entspricht der hier im Buch beschriebenen JavaScript-Variante für Erset-
zungstexte.

PHP
In PHP können Sie die gleiche Funktion preg_replace() verwenden, die schon im vorher-
gehenden Rezept beschrieben wurde. Die Syntax für das Hinzufügen von Rückwärtsrefe-
renzen entspricht der hier im Buch beschriebenen PHP-Variante für Ersetzungstexte.

Perl
In Perl wird der replace-Teil in s/regex/replace/ einfach als String in doppelten Anfüh-
rungszeichen interpretiert. Sie können die speziellen Variablen $&, $1, $2 und so weiter
verwenden, die in Rezept 3.7 und Rezept 3.9 für den Ersetzungstext beschrieben wurden.
Die Variablen werden gesetzt, nachdem die Regex-Übereinstimmung gefunden wurde
und bevor der Text ersetzt wird. Sie können diese Variablen auch an beliebigen anderen
Stellen im Perl-Code nutzen. Ihr Wert bleibt bestehen, bis Sie Perl anweisen, eine weitere
Regex-Suche durchzuführen.
Alle anderen Programmiersprachen in diesem Buch stellen eine Funktion bereit, die den
Ersetzungstext als String übernimmt. Dabei wird dieser String geparst, um Rückwärtsre-
ferenzen wie $1 oder \1 zu verarbeiten. Aber außerhalb des Ersetzungstexts hat $1 in die-
sen Sprachen keinerlei Bedeutung.

Python
In Python können Sie die gleiche Funktion sub() verwenden, die schon im vorhergehen-
den Rezept beschrieben wurde. Die Syntax für das Hinzufügen von Rückwärtsreferenzen
entspricht der hier im Buch beschriebenen Python-Variante für Ersetzungstexte.

3.15 Übereinstimmungen durch Teile des gefundenen Texts ersetzen | 191


Ruby
In Ruby können Sie die gleiche Methode String.gsub() verwenden, die schon im vorher-
gehenden Rezept beschrieben wurde. Die Syntax für das Hinzufügen von Rückwärtsrefe-
renzen entspricht der hier im Buch beschriebenen Ruby-Variante für Ersetzungstexte.
Sie können im Ersetzungstext Variablen wie $1 nicht auswerten. Das liegt daran, dass
Ruby die Variablenauswertung durchführt, bevor gsub() aufgerufen wird. Zu diesem
Zeitpunkt hat gsub() aber noch keine Übereinstimmungen gefunden, daher können
Rückwärtsreferenzen nicht ersetzt werden. Versuchen Sie, $1 auszuwerten, erhalten Sie
den Text, der von der ersten einfangenden Gruppe in der letzten Regex-Übereinstim-
mung gefunden wurde, noch bevor gsub() aufgerufen wird.
Stattdessen müssen Sie Ersetzungstext-Tokens wie «\1» verwenden. Die Funktion gsub()
ersetzt diese Tokens im Ersetzungstext für jede Regex-Übereinstimmung. Ich empfehle,
für den Ersetzungstext Strings mit einfachen Anführungszeichen zu verwenden. Bei
Strings mit doppelten Anführungszeichen wird der Backslash als Maskierungszeichen
verwendet, und maskierte Ziffern stehen für oktale Werte. '\1' und "\\1" verwenden den
von der ersten einfangenden Gruppe gefundenen Text zum Ersetzen, während "\1" mit
dem einzelnen literalen Zeichen 0x01 ersetzt wird.

Benannte Captures
Wenn Sie in Ihrem regulären Ausdruck benannte einfangende Gruppen verwenden, kön-
nen Sie die Gruppen in Ihrem Ersetzungstext über den Namen ansprechen.

C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string resultString = Regex.Replace(subjectString,
@"(?<left>\w+)=(?<right>\w+)", "${right}=${left}");

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex(@"(?<left>\w+)=(?<right>\w+)");
string resultString = regexObj.Replace(subjectString, "${right}=${left}");

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim ResultString = Regex.Replace(SubjectString,
"(?<left>\w+)=(?<right>\w+)", "${right}=${left}")

192 | Kapitel 3: Mit regulären Ausdrücken programmieren


Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("(?<left>\w+)=(?<right>\w+)")
Dim ResultString = RegexObj.Replace(SubjectString, "${right}=${left}")

PHP
$result = preg_replace('/(?P<left>\w+)=(?P<right>\w+)/',
'$2=$1', $subject);
Die PHP-preg-Funktionen verwenden die PCRE-Bibliothek, die benannte einfangende
Captures unterstützt. Die Funktionen preg_match() und preg_match_all() ergänzen das
Array mit den gefundenen Ergebnissen um benannte einfangende Gruppen. Leider stellt
preg_replace() keine Möglichkeit bereit, benannte Rückwärtsreferenzen im Ersetzungs-
text zu verwenden. Wenn Ihre Regex benannte einfangende Gruppen nutzt, müssen Sie
sowohl die benannten als auch die nummerierten einfangenden Gruppen von links nach
rechts durchzählen, um die Nummer der Rückwärtsreferenz zu ermitteln, die Sie dann
auch im Ersetzungstext verwenden können.

Perl
$subject =~ s/(?<left>\w+)=(?<right>\w+)/$+{right}=$+{left}/g;

Perl unterstützt benannte einfangende Gruppen seit Version 5.10. Der Hash $+ speichert
den von allen benannten einfangenden Gruppen gefundenen Text ab, die im letzten regu-
lären Ausdruck verwendet wurden. Sie können diesen Hash im Ersetzungstext nutzen,
aber auch an anderen Stellen im Quellcode.

Python
Wenn Sie nur ein paar Strings verarbeiten müssen, können Sie die globale Funktion ver-
wenden:
result = re.sub(r"(?P<left>\w+)=(?P<right>\w+)", r"\g<right>=\g<left>",
subject)
Um die gleiche Regex mehrfach zu nutzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile(r"(?P<left>\w+)=(?P<right>\w+)")
result = reobj.sub(r"\g<right>=\g<left>", subject)

Ruby
result = subject.gsub(/(?<left>\w+)=(?<right>\w+)/, '\k<left>=\k<right>')

Siehe auch
„Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 beschreibt die Ersetzungs-
textvarianten.
Rezept 2.21 erklärt, wie man im Ersetzungstext auf einfangende Gruppen zugreifen kann.

3.15 Übereinstimmungen durch Teile des gefundenen Texts ersetzen | 193


3.16 Übereinstimmungen durch Text ersetzen, der im Code
erzeugt wurde
Problem
Sie wollen alle Übereinstimmungen eines regulären Ausdrucks durch einen neuen String
ersetzen, den Sie in Ihrem prozeduralen Code erzeugt haben. Sie wollen jede Überein-
stimmung durch einen anderen String ersetzen können, der davon abhängen soll, was
tatsächlich gefunden wurde.
Stellen Sie sich zum Beispiel vor, Sie wollen alle Zahlen in einem String mit zwei multipli-
zieren und wieder einsetzen.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string resultString = Regex.Replace(subjectString, @"\d+",
new MatchEvaluator(ComputeReplacement));
Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex(@"\d+");
string resultString = regexObj.Replace(subjectString,
new MatchEvaluator(ComputeReplacement));
Beide Codeschnipsel nutzen die Funktion ComputeReplacement. Sie sollten diese Methode
der Klasse hinzufügen, in der Sie diese Lösung implementieren:
public String ComputeReplacement(Match matchResult) {
int twiceasmuch = int.Parse(matchResult.Value) * 2;
return twiceasmuch.ToString();
}

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim MyMatchEvaluator As New MatchEvaluator(AddressOf ComputeReplacement)
Dim ResultString = Regex.Replace(SubjectString, "\d+", MyMatchEvaluator)
Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("\d+")
Dim MyMatchEvaluator As New MatchEvaluator(AddressOf ComputeReplacement)
Dim ResultString = RegexObj.Replace(SubjectString, MyMatchEvaluator)

194 | Kapitel 3: Mit regulären Ausdrücken programmieren


Beide Codeschnipsel nutzen die Funktion ComputeReplacement. Sie sollten diese Methode
der Klasse hinzufügen, in der Sie diese Lösung implementieren:
Public Function ComputeReplacement(ByVal MatchResult As Match) As String
Dim TwiceAsMuch = Int.Parse(MatchResult.Value) * 2;
Return TwiceAsMuch.ToString();
End Function

Java
StringBuffer resultString = new StringBuffer();
Pattern regex = Pattern.compile("\\d+");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
Integer twiceasmuch = Integer.parseInt(regexMatcher.group()) * 2;
regexMatcher.appendReplacement(resultString, twiceasmuch.toString());
}
regexMatcher.appendTail(resultString);

JavaScript
var result = subject.replace(/\d+/g,
function(match) { return match * 2; }
);

PHP
Mit einer deklarierten Callback-Funktion:
$result = preg_replace_callback('/\d+/', compute_replacement, $subject);

function compute_replacement($groups) {
return $groups[0] * 2;
}

Mit einer anonymen Callback-Funktion:


$result = preg_replace_callback(
'/\d+/',
create_function(
'$groups',
'return $groups[0] * 2;'
),
$subject
);

Perl
$subject =~ s/\d+/$& * 2/eg;

3.16 Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde | 195
Python
Wenn Sie nur ein paar Strings verarbeiten müssen, können Sie die globale Funktion ver-
wenden:
result = re.sub(r"\d+", computereplacement, subject)

Um die gleiche Regex wiederholt zu nutzen, verwenden Sie ein kompiliertes Objekt:
reobj = re.compile(r"\d+")
result = reobj.sub(computereplacement, subject))
Beide Codeschnipsel rufen die Funktion computereplacement auf. Diese Funktion muss
deklariert werden, bevor Sie sie an sub() übergeben können.
def computereplacement(matchobj):
return str(int(matchobj.group()) * 2)

Ruby
result = subject.gsub(/\d+/) {|match|
Integer(match) * 2
}

Diskussion
Wenn Sie einen String als Ersetzungstext verwenden, können Sie auch nur einfache Text-
ersetzungen vornehmen. Um jede Übereinstimmung mit etwas anderem zu ersetzen, das
auch noch vom gefundenen Text abhängt, müssen Sie den Ersetzungstext in Ihrem eige-
nen Code erstellen.

C#
In Rezept 3.14 werden die verschiedenen Möglichkeiten besprochen, die Methode
Regex.Replace() aufzurufen und einen String als Ersetzungstext zu übergeben. Wenn ein
statischer Aufruf genutzt wird, ist der Ersetzungstext der dritte Parameter – nach dem
Ausgangstext und dem regulären Ausdruck. Haben Sie den regulären Ausdruck dem
Konstruktor Regex() übergeben, können Sie Replace() mit dem Ersetzungstext als zwei-
tem Parameter für dieses Objekt aufrufen.
Statt einen String als zweiten oder dritten Parameter zu verwenden, können Sie auch ein
MatchEvaluator-Delegate übergeben. Dieses Delegate ist eine Referenz auf eine Member-
Funktion, die Sie in der Klasse definieren können, in der Sie das Suchen und Ersetzen
durchführen wollen. Um das Delegate zu erstellen, verwenden Sie das Schlüsselwort new,
um den Konstruktor MatchEvaluator() aufzufrufen. Übergeben Sie dabei Ihre Member-
Funktion als einzigen Parameter.
Die Funktion, die Sie für das Delegate nutzen wollen, sollte einen String zurückgeben
und einen Parameter der Klasse System.Text.RegularExpressions.Match erwarten. Das ist
die gleiche Klasse Match, die vom Member Regex.Match() in nahezu allen bisherigen
Rezepten in diesem Kapitel genutzt wurde.

196 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wenn Sie Replace() mit einem MatchEvaluator als Ersetzung aufrufen, wird Ihre Funktion
für jede Regex-Übereinstimmung aufgerufen, die ersetzt werden muss. Ihre Funktion
muss dann den Ersetzungstext liefern. Sie können beliebige Eigenschaften des Match-
Objekts nutzen, um Ihren Ersetzungstext zu erzeugen. Das weiter oben gezeigte Beispiel
hat matchResult.Value verwendet, um den String mit der vollständigen Regex-Überein-
stimmung zu erhalten. Häufig werden Sie matchResult.Groups[] nutzen, um Ihren eige-
nen Ersetzungstext aus den einfangenden Gruppen in Ihrem regulären Ausdruck zu
erstellen.
Wollen Sie bestimmte Regex-Übereinstimmungen nicht ersetzen, sollte Ihre Funktion
matchResult.Value zurückgeben. Liefern Sie null oder einen leeren String, wird die Regex-
Übereinstimmung nur entfernt (also durch einen leeren String ersetzt).

VB.NET
In Rezept 3.14 werden die verschiedenen Möglichkeiten besprochen, die Methode
Regex.Replace() aufzurufen und einen String als Ersetzungstext zu übergeben. Wenn ein
statischer Aufruf genutzt wird, ist der Ersetzungstext der dritte Parameter – nach dem
Ausgangstext und dem regulären Ausdruck. Haben Sie das Schlüsselwort Dim genutzt,
um eine Variable mit Ihrem regulären Ausdruck zu erstellen, können Sie Replace() mit
dem Ersetzungstext als zweitem Parameter für dieses Objekt aufrufen.
Statt einen String als zweiten oder dritten Parameter zu verwenden, können Sie auch ein
MatchEvaluator-Objekt übergeben. Dieses Objekt ist eine Referenz auf eine Funktion, die
Sie in der Klasse definieren können, in der Sie das Suchen und Ersetzen durchführen wol-
len. Mit dem Schlüsselwort Dim erstellen Sie eine neue Variable des Typs MatchEvaluator.
Der Operator AddressOf gibt eine Referenz auf Ihre Funktion zurück, ohne sie selbst in
diesem Moment aufzurufen.
Die Funktion, die Sie für MatchEvaluator nutzen wollen, sollte einen String zurückgeben
und einen Parameter der Klasse System.Text.RegularExpressions.Match erwarten. Das ist
die gleiche Klasse Match, die vom Member Regex.Match() in nahezu allen bisherigen
Rezepten in diesem Kapitel genutzt wurde. Der Parameter wird als Wert übergeben,
daher sollten Sie ihn mit ByVal definieren.
Wenn Sie Replace() mit einem MatchEvaluator als Ersetzung aufrufen, wird Ihre Funktion
für jede Regex-Übereinstimmung aufgerufen, die ersetzt werden muss. Ihre Funktion
muss dann den Ersetzungstext liefern. Sie können beliebige Eigenschaften des Match-
Objekts nutzen, um Ihren Ersetzungstext zu erzeugen. Das weiter oben gezeigte Beispiel
hat MatchResult.Value verwendet, um den String mit der vollständigen Regex-Überein-
stimmung zu erhalten. Häufig werden Sie MatchResult.Groups() nutzen, um Ihren eige-
nen Ersetzungstext aus den einfangenden Gruppen in Ihrem regulären Ausdruck zu
erstellen.
Wenn Sie bestimmte Regex-Übereinstimmungen nicht ersetzen wollen, sollte Ihre Funk-
tion MatchResult.Value zurückgeben. Liefern Sie Nothing oder einen leeren String, wird
die Regex-Übereinstimmung nur entfernt (also durch einen leeren String ersetzt).

3.16 Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde | 197
Java
Die Lösung in Java ist sehr geradlinig. Wir iterieren, wie in Rezept 3.11 beschrieben, über
alle Regex-Übereinstimmungen. Innerhalb der Schleife rufen wir für das Matcher-Objekt
die Funktion appendReplacement() auf. Hat find() keinen weiteren Erfolg mehr, rufen wir
appendTail() auf. Die beiden Methoden appendReplacement() und appendTail() machen
es sehr leicht, für jede Regex-Übereinstimmung einen anderen Ersetzungstext zu verwen-
den.
appendReplacement() erwartet zwei Parameter. Der erste ist der StringBuffer, in dem Sie
(temporär) das Ergebnis des aktuellen Such- und Ersetzungsvorgangs abspeichern. Der
zweite ist der Ersetzungstext, der für die Übereinstimmung genutzt werden soll, die von
find() gefunden wurde. Dieser Ersetzungstext kann Verweise auf einfangende Gruppen
enthalten, wie zum Beispiel "$1". Gibt es in diesem Text einen Syntaxfehler, wird eine
IllegalArgumentException geworfen. Verweist der Ersetzungstext auf eine einfangende
Gruppe, die nicht existiert, wird stattdessen eine IndexOutOfBoundsException geworfen.
Rufen Sie appendReplacement() ohne einen vorherigen erfolgreichen Aufruf von find()
auf, führt das zu einer IllegalStateException.
Wird appendReplacement() korrekt aufgerufen, geschehen zwei Dinge. Zuerst wird der
Text in den aktuellen String-Buffer kopiert, der sich zwischen der vorherigen und der
aktuellen Regex-Übereinstimmung befindet, ohne ihn zu verändern. Danach wird Ihr
Ersetzungstext angehängt, wobei alle Rückwärtsreferenzen durch den Text der entspre-
chenden einfangenden Gruppen ersetzt werden.
Möchten Sie eine bestimmte Übereinstimmung löschen, ersetzen Sie sie einfach durch
einen leeren String. Wollen Sie eine Übereinstimmung nicht ändern, können Sie für diese
eine den Aufruf von appendReplacement() weglassen. Wenn ich „vorherige Regex-Über-
einstimmung“ sage, meine ich die vorherige Übereinstimmung, für die Sie appendReplace-
ment() aufgerufen haben. Nutzen Sie appendReplacement() nicht für bestimmte Über-
einstimmungen, werden diese Teil des Texts zwischen den Übereinstimmungen, die Sie
ersetzen. Dieser Text wird dann unverändert in den Ziel-String-Buffer kopiert.
Wenn Sie Ihre Ersetzungen durchgeführt haben, rufen Sie appendTail() auf. Damit wird
der Text am Ende des Strings hinter der letzten Regex-Übereinstimmung kopiert, für die
Sie appendReplacement() aufgerufen haben.

JavaScript
In JavaScript ist eine Funktion einfach ein weiteres Objekt, das Sie einer Variablen zuwei-
sen können. Statt einen literalen String oder eine Variable mit einem String an die Funktion
string.replace() zu übergeben, nutzen wir eine Funktion, die einen String zurückgibt.
Diese Funktion wird dann jedes Mal aufgerufen, wenn eine Ersetzung ansteht.
Sie können Ihre Ersetzungsfunktion mehr als einen Parameter akzeptieren lassen. Wenn
Sie das tun, wird im ersten Parameter der Text übergeben, der vom regulären Ausdruck
gefunden wurde. Enthält Ihr regulärer Ausdruck einfangende Gruppen, finden Sie im
zweiten Parameter den Text, der von der ersten einfangenden Gruppe gefunden wurde,

198 | Kapitel 3: Mit regulären Ausdrücken programmieren


im dritten Parameter den Text der zweiten einfangenden Gruppe und so weiter. Sie kön-
nen diese Parameter nutzen, wenn Sie diese Teile des regulären Ausdrucks zum Aufbau
Ihres Ersetzungstexts verwenden wollen.
Die Ersetzungsfunktion in der JavaScript-Lösung für dieses Rezept übernimmt einfach
den Text, der vom regulären Ausdruck gefunden wurde, und liefert ihn mit zwei multi-
pliziert zurück. JavaScript kümmert sich implizit um das Umwandeln des Strings in eine
Zahl und umgekehrt.

PHP
Die Funktion preg_replace_callback() funktioniert genau so wie die in Rezept 3.14
beschriebene Funktion preg_replace(). Sie erwartet einen regulären Ausdruck, eine
Ersetzung, den Ausgangstext, eine optionale maximale Ersetzungsanzahl und eine
optionale Variable, in der die Anzahl der vorgenommenen Ersetzungen abgelegt wird.
Der reguläre Ausdruck und der Ausgangstext können wieder einfache Strings oder Arrays
sein.
Der Unterschied liegt darin, dass man preg_replace_callback() keinen String und auch
kein Array mit Strings als Ersetzung mitgeben kann, sondern nur eine Funktion. Sie kön-
nen diese Funktion in Ihrem Code deklarieren oder create_function() nutzen, um eine
anonyme Funktion zu erzeugen. Die Funktion sollte einen Parameter übernehmen und
einen String zurückgeben (oder etwas, das in einen String umgewandelt werden kann).
Jedes Mal, wenn preg_replace_callback() eine Regex-Übereinstimmung findet, wird Ihre
Callback-Funktion aufgerufen. Der Parameter wird mit einem String-Array gefüllt. Im
nullten Element findet sich das gesamte Regex-Suchergebnis. Die folgenden Elemente
enthalten den Text, der durch die einfangenden Gruppen gefunden wurde. Sie können
dieses Array nutzen, um Ihren Ersetzungstext aus dem Suchergebnis oder aus den einfan-
genden Gruppen aufzubauen.

Perl
Der Operator s/// unterstützt einen zusätzlichen Modifikator, der vom Operator m//
ignoriert wird: /e. Dieser Modifikator, der auch für „Execute“ steht, weist den Substitu-
tionsoperator an, den Ersetzungsteil als Perl-Code zu interpretieren statt als Inhalt eines
Strings in doppelten Anführungszeichen. Mit diesem Modifikator können wir den gefun-
denen Text einfach über die Variable $& ansprechen und mit zwei multiplizieren. Das
Ergebnis des Codes wird dann als Ersetzungstext genutzt.

Python
Die Python-Funktion sub() ermöglicht es Ihnen, den Namen einer Funktion statt einen
String als Ersetzungstext zu übergeben. Diese Funktion wird dann für jede Regex-Über-
einstimmung aufgerufen, die ersetzt werden soll.

3.16 Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde | 199
Sie müssen diese Funktion deklarieren, bevor Sie auf sie verweisen können. Sie sollte
einen Parameter haben, um eine Instanz eines MatchObject zu erhalten. Das ist das gleiche
Objekt, das auch von der Funktion search() zurückgegeben wird. Sie können es nutzen,
um mit der Regex-Übereinstimmung oder Teilen davon einen Ersetzungstext zu bauen.
In Rezept 3.7 und Rezept 3.9 finden Sie Details dazu.
Ihre Funktion soll einen String mit dem Ersetzungstext zurückgeben.

Ruby
Die vorhergehenden zwei Rezepte haben die Methode gsub() der Klasse String mit zwei
Parametern aufgerufen – der Regex und dem Ersetzungstext. Diese Methode gibt es auch
in einer Blockform.
In der Blockform erwartet gsub() nur Ihren regulären Ausdruck als einzigen Parameter.
Sie füllt dann eine Iterator-Variable mit einem String, der den vom regulären Ausdruck
gefundenen Text enthält. Geben Sie zusätzliche Iterator-Variablen an, werden diese auf
nil gesetzt, auch wenn Ihr regulärer Ausdruck einfangende Gruppen enthält.
Innerhalb des Blocks setzen Sie einen Ausdruck, der zu dem String wird, den Sie als
Ersetzungstext verwenden wollen. Sie können innerhalb des Blocks die speziellen Regex-
Variablen nutzen, wie zum Beispiel $~, $& und $1. Deren Wert ändert sich bei jeder Aus-
wertung des Blocks. In den Rezepten 3.7, 3.8 und 3.9 finden Sie die Details dazu.
Sie können keine Ersetzungstext-Tokens nutzen, wie zum Beispiel «\1». Diese werden als
literaler Text behandelt.

Siehe auch
Rezepte 3.9 und 3.15.

3.17 Alle Übereinstimmungen innerhalb der Überein-


stimmungen einer anderen Regex ersetzen
Problem
Sie wollen alle Übereinstimmungen eines bestimmten regulären Ausdrucks ersetzen, aber
nur in bestimmten Bereichen des Ausgangstexts. Ein weiterer regulärer Ausdruck findet
jeden dieser Bereiche im String.
Stellen Sie sich beispielsweise vor, Sie haben eine HTML-Datei, in der eine Reihe von
Abschnitten mit <b>-Tags als fett markiert ist. Zwischen jedem Paar Bold-Tags wollen Sie
alle Übereinstimmungen des regulären Ausdrucks ‹vorher› durch den Ersetzungstext
‹danach› austauschen. Wenn Sie zum Beispiel den String vorher <b>erster vorher</b>
vorher <b>vorher vorher</b> bearbeiten, erwarten Sie als Ergebnis vorher <b>erster
danach</b> vorher <b>danach danach</b>.

200 | Kapitel 3: Mit regulären Ausdrücken programmieren


Lösung
C#
Regex outerRegex = new Regex("<b>.*?</b>", RegexOptions.Singleline);
Regex innerRegex = new Regex("vorher");
string resultString = outerRegex.Replace(subjectString,
new MatchEvaluator(ComputeReplacement));

public String ComputeReplacement(Match matchResult) {


// Inneres Suchen und Ersetzen für jede Übereinstimmung
// der äußeren Regex ausführen
return innerRegex.Replace(matchResult.Value, "danach");
}

VB.NET
Dim OuterRegex As New Regex("<b>.*?</b>", RegexOptions.Singleline)
Dim InnerRegex As New Regex("vorher")
Dim MyMatchEvaluator As New MatchEvaluator(AddressOf ComputeReplacement)
Dim ResultString = OuterRegex.Replace(SubjectString, MyMatchEvaluator)

Public Function ComputeReplacement(ByVal MatchResult As Match) As String


'Inneres Suchen und Ersetzen für jede Übereinstimmung
'der äußeren Regex ausführen
Return InnerRegex.Replace(MatchResult.Value, "danach");
End Function

Java
StringBuffer resultString = new StringBuffer();
Pattern outerRegex = Pattern.compile("<b>.*?</b>");
Pattern innerRegex = Pattern.compile("vorher");
Matcher outerMatcher = outerRegex.matcher(subjectString);
while (outerMatcher.find()) {
outerMatcher.appendReplacement(resultString,
innerRegex.matcher(outerMatcher.group()).replaceAll("danach"));
}
outerMatcher.appendTail(resultString);

JavaScript
var result = subject.replace(/<b>.*?<\/b>/g,
function(match) {
return match.replace(/vorher/g, "danach");
}
);

3.17 Alle Übereinstimmungen innerhalb der Übereinstimmungen einer anderen Regex ersetzen | 201
PHP
$result = preg_replace_callback('%<b>.*?</b>%',
replace_within_tag, $subject);

function replace_within_tag($groups) {
return preg_replace('/vorher/', 'danach', $groups[0]);
}

Perl
$subject =~ s%<b>.*?</b>%($match = $&) =~ s/vorher/danach/g; $match;%eg;

Python
innerre = re.compile("vorher")
def replacewithin(matchobj):
return innerre.sub("danach", matchobj.group())

result = re.sub("<b>.*?</b>", replacewithin, subject)

Ruby
innerre = /vorher/
result = subject.gsub(/<b>.*?<\/b>/) {|match|
match.gsub(innerre, 'danach')
}

Diskussion
Diese Lösung ist wieder eine Kombination aus zwei schon besprochenen Lösungen,
wobei zwei reguläre Ausdrücke verwendet werden. Der „äußere“ reguläre Ausdruck
‹<b>.*?</b>› passt zu den HTML-Bold-Tags und dem Text dazwischen, der „innere“
reguläre Ausdruck passt zu „vorher”, was wir durch „danach“ ersetzen werden.
In Rezept 3.16 wird erläutert, wie Sie suchen und dabei jede Regex-Übereinstimmung
durch Text ersetzen können, den Sie selbst im Code zusammengestellt haben. Hier
machen wir das mit dem äußeren regulären Ausdruck. Jedes Mal, wenn er ein Paar öff-
nender und schließender <b>-Tags findet, suchen und ersetzen wir mit der inneren Regex
– genau so, wie wir es in Rezept 3.14 gemacht haben. Der Ausgangstext für das Suchen
und Ersetzen mit der inneren Regex ist der Text, der von der äußeren Regex gefunden
wurde.

Siehe auch
Rezepte 3.11, 3.13 und 3.16.

202 | Kapitel 3: Mit regulären Ausdrücken programmieren


3.18 Alle Übereinstimmungen zwischen den Überein-
stimmungen einer anderen Regex ersetzen
Problem
Sie wollen alle Übereinstimmungen eines bestimmten regulären Ausdrucks ersetzen –
aber nur in bestimmten Bereichen des Ausgangstexts. Ein anderer regulärer Ausdruck
findet den Text zwischen diesen Bereichen. Sie wollen also nur in den Teilen des Aus-
gangstexts suchen und ersetzen, die nicht von dem anderen regulären Ausdruck gefun-
den werden.
Stellen Sie sich vor, Sie haben eine HTML-Datei, in der Sie gerade doppelte Anführungs-
zeichen durch typografisch korrekte doppelte Anführungszeichen ersetzen wollen – aber
nur außerhalb von HTML-Tags. Anführungszeichen innerhalb von HTML-Tags müssen
als echte ASCII-Anführungszeichen erhalten bleiben, da Ihr Webbrowser den HTML-
Code ansonsten nicht mehr parsen kann. So wollen Sie zum Beispiel "Text" <span
class="middle">"Text"</span> "Text" in “Text” <span class="middle">“Text”</span>
“Text” ändern.

Lösung
C#
string resultString = null;
Regex outerRegex = new Regex("<[^<>]*>");
Regex innerRegex = new Regex("\"([^\"]*)\"");
// Ersten Bereich finden
int lastIndex = 0;
Match outerMatch = outerRegex.Match(subjectString);
while (outerMatch.Success) {
// Suchen und Ersetzen im Text zwischen dieser
// und der vorigen Übereinstimmung
string textBetween =
subjectString.Substring(lastIndex, outerMatch.Index - lastIndex);
resultString = resultString +
innerRegex.Replace(textBetween, "\u201E$1\u201C");
lastIndex = outerMatch.Index + outerMatch.Length;
// Text im Bereich unverändert kopieren
resultString = resultString + outerMatch.Value;
// Nächsten Bereich finden
outerMatch = outerMatch.NextMatch();
}
// Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
string textAfter = subjectString.Substring(lastIndex,
subjectString.Length - lastIndex);
resultString = resultString + innerRegex.Replace(textAfter,
"\u201E$1\u201C");

3.18 Alle Übereinstimmungen zwischen den Übereinstimmungen einer anderen Regex ersetzen | 203
VB.NET
Dim ResultString As String = Nothing
Dim OuterRegex As New Regex("<[^<>]*>")
Dim InnerRegex As New Regex("""([^""]*)""")
'Ersten Bereich finden
Dim LastIndex = 0
Dim OuterMatch = OuterRegex.Match(SubjectString)
While OuterMatch.Success
'Suchen und Ersetzen im Text zwischen dieser
'und der vorigen Übereinstimmung
Dim TextBetween = SubjectString.Substring(LastIndex,
OuterMatch.Index - LastIndex);
ResultString = ResultString + InnerRegex.Replace(TextBetween,
ChrW(&H201E) + "$1" + ChrW(&H201C))
LastIndex = OuterMatch.Index + OuterMatch.Length
'Text im Bereich unverändert kopieren
ResultString = ResultString + OuterMatch.Value
'Nächsten Bereich finden
OuterMatch = OuterMatch.NextMatch
End While
'Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
Dim TextAfter = SubjectString.Substring(LastIndex,
SubjectString.Length - LastIndex);
ResultString = ResultString +
InnerRegex.Replace(TextAfter, ChrW(&H201E) + "$1" + ChrW(&H201C))

Java
StringBuffer resultString = new StringBuffer();
Pattern outerRegex = Pattern.compile("<[^<>]*>");
Pattern innerRegex = Pattern.compile("\"([^\"]*)\"");
Matcher outerMatcher = outerRegex.matcher(subjectString);
int lastIndex = 0;
while (outerMatcher.find()) {
// Suchen und Ersetzen im Text zwischen dieser
// und der vorigen Übereinstimmung
String textBetween = subjectString.substring(lastIndex,
outerMatcher.start());
Matcher innerMatcher = innerRegex.matcher(textBetween);
resultString.append(innerMatcher.replaceAll("\u201E$1\u201C"));
lastIndex = outerMatcher.end();
// Regex-Übereinstimmung selbst unverändert anhängen
resultString.append(outerMatcher.group());
}
// Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
String textAfter = subjectString.substring(lastIndex);
Matcher innerMatcher = innerRegex.matcher(textAfter);
resultString.append(innerMatcher.replaceAll("\u201E$1\u201C"));

JavaScript
var result = "";
var outerRegex = /<[^<>]*>/g;

204 | Kapitel 3: Mit regulären Ausdrücken programmieren


var innerRegex = /"([^"]*)"/g;
var outerMatch = null;
var lastIndex = 0;
while (outerMatch = outerRegex.exec(subject)) {
if (outerMatch.index == outerRegex.lastIndex) outerRegex.lastIndex++;
// Suchen und Ersetzen im Text zwischen dieser
// und der vorigen Übereinstimmung
var textBetween = subject.substring(lastIndex, outerMatch.index);
result = result + textBetween.replace(innerRegex, "\u201E$1\u201C");
lastIndex = outerMatch.index + outerMatch[0].length;
// Regex-Übereinstimmung selbst unverändert anhängen
result = result + outerMatch[0];
}
// Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
var textAfter = subject.substr(lastIndex);
result = result + textAfter.replace(innerRegex, "\u201E$1\u201C");

PHP
$result = '';
$lastindex = 0;
while (preg_match('/<[^<>]*>/', $subject, $groups, PREG_OFFSET_CAPTURE,
$lastindex)) {
$matchstart = $groups[0][1];
$matchlength = strlen($groups[0][0]);
// Suchen und Ersetzen im Text zwischen dieser
// und der vorigen Übereinstimmung
$textbetween = substr($subject, $lastindex, $matchstart-$lastindex);
$result .= preg_replace('/"([^"]*)"/', '“$1”', $textbetween);
// Regex-Übereinstimmung selbst unverändert anhängen
$result .= $groups[0][0];
// Startposition für die nächste Suche setzen
$lastindex = $matchstart + $matchlength;
if ($matchlength == 0) {
// Nicht in einer Endlosschleife hängen bleiben,
// wenn die Regex Übereinstimmungen der Länge null erlaubt
$lastindex++;
}
}
// Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
$textafter = substr($subject, $lastindex);
$result .= preg_replace('/"([^"]*)"/', '“$1”', $textafter);

Perl
use encoding "utf-8";
$result = '';
while ($subject =~ m/<[^<>]*>/g) {
$match = $&;
$textafter = $';
($textbetween = $`) =~ s/"([^"]*)"/\x{201E}$1\x{201C}/g;
$result .= $textbetween . $match;
}
$textafter =~ s/"([^"]*)"/\x{201E}$1\x{201C}/g;
$result .= $textafter;

3.18 Alle Übereinstimmungen zwischen den Übereinstimmungen einer anderen Regex ersetzen | 205
Python
innerre = re.compile('"([^"]*)"')
result = "";
lastindex = 0;
for outermatch in re.finditer("<[^<>]*>", subject):
# Suchen und Ersetzen im Text zwischen dieser
# und der vorigen Übereinstimmung
textbetween = subject[lastindex:outermatch.start()]
result += innerre.sub(u"\u201E\\1\u201C", textbetween)
lastindex = outermatch.end()
# Regex-Übereinstimmung selbst unverändert anhängen
result += outermatch.group()
# Suchen und Ersetzen im Rest nach der letzten Regex-Übereinstimmung
textafter = subject[lastindex:]
result += innerre.sub(u"\u201E\\1\u201C", textafter)

Ruby
result = '';
textafter = ''
subject.scan(/<[^<>]*>/) {|match|
textafter = $'
textbetween = $`.gsub(/"([^"]*)"/, '“\1”')
result += textbetween + match
}
result += textafter.gsub(/"([^"]*)"/, '“\1”')

Diskussion
In Rezept 3.13 wird beschrieben, wie man zwei reguläre Ausdrücke nutzt, um Überein-
stimmungen (der zweiten Regex) nur in bestimmten Abschnitten der Datei (Übereinstim-
mungen der ersten Regex) zu finden. Die Lösung für dieses Rezept verwendet die gleiche
Technik zum Suchen und Ersetzen in ausgewählten Bereichen des Ausgangstexts.
Es ist wichtig, dass der reguläre Ausdruck, den Sie zum Finden der Abschnitte nutzen,
immer auf dem ursprünglichen String arbeitet. Wenn Sie den ursprünglichen Ausgangs-
text verändern und die innere Regex dabei Zeichen ergänzt oder entfernt, müssen Sie die
Startposition für die Regex, die die Abschnitte findet, verschieben. Vor allem können die
Änderungen unerwünschte Nebeneffekte haben. Wenn Ihre äußere Regex zum Beispiel
den Anker ‹^› nutzt, um etwas am Anfang einer Zeile zu finden, Ihre innere Regex aber
einen Zeilenumbruch am Ende des von der äußeren Regex gefundenen Abschnitts ein-
fügt, wird ‹^› aufgrund dieses neu eingefügten Zeilenumbruchs direkt nach dem vorigen
Abschnitt passen.
Auch wenn die Lösungen für dieses Rezept ziemlich lang sind, sind alle doch recht gerad-
linig angelegt. Es werden zwei reguläre Ausdrücke genutzt. Der „äußere“ reguläre Aus-
druck ‹<[^<>]*>› passt zu einem Paar spitzer Klammern und allem dazwischen –
abgesehen von spitzen Klammern. Das ist ein recht kruder Weg, ein HTML-Tag zu fin-
den. Diese Regex funktioniert wunderbar, solange die HTML-Datei keine literalen spit-

206 | Kapitel 3: Mit regulären Ausdrücken programmieren


zen Klammern enthält, die (fälschlicherweise) nicht als Entitäten kodiert sind. Wir
implementieren diesen regulären Ausdruck mit dem gleichen Code, der auch schon in
Rezept 3.11 genutzt wurde. Der einzige Unterschied ist, dass der Platzhalter-Kommentar
im dortigen Code nun durch tatsächlichen Code ersetzt wurde, der sucht und ersetzt.
Der Code für das Suchen und Ersetzen innerhalb der Schleife orientiert sich am Code aus
Rezept 3.14. Der Ausgangstext für das Suchen und Ersetzen ist der Text zwischen der
vorigen Übereinstimmung der äußeren Regex und der aktuellen Übereinstimmung. Wir
fügen das Ergebnis des inneren Suchens und Ersetzens an den Gesamtergebnis-String an.
Zudem ergänzen wir die aktuelle Übereinstimmung des äußeren regulären Ausdrucks
unverändert.
Wenn die äußere Regex keine weiteren Übereinstimmungen findet, führen wir das innere
Suchen und Ersetzen ein weiteres Mal durch, jetzt aber für den Text nach der letzten
Übereinstimmung der äußeren Regex.
Die Regex ‹"([^"]*)"›, die für das Suchen und Ersetzen innerhalb der Schleife verwendet
wird, findet ein Paar doppelte Anführungszeichen und alles dazwischen, mit Ausnahme
der doppelten Anführungszeichen. Der Text zwischen den Anführungszeichen wird
durch die erste einfangende Gruppe gesichert.
Für den Ersetzungstext nutzen wir eine Referenz auf die erste einfangende Gruppe, die
zwischen typografischen (deutschen) Anführungszeichen gesetzt wird. Diese finden sich
an den Unicode-Codepoints U+201E und U+201C. Normalerweise können Sie die typo-
grafischen Anführungszeichen direkt in Ihren Quellcode einfügen. Visual Studio 2008
versucht allerdings, schlau zu sein, und ersetzt die literalen typografischen Anführungs-
zeichen durch ASCII-Anführungszeichen.
In einem regulären Ausdruck können Sie einen Unicode-Codepoint durch ‹\u201E› oder
‹\x{201E}› finden, aber keine der in diesem Buch behandelten Programmiersprachen
unterstützt solche Tokens als Teil des Ersetzungstexts. Wenn ein Endanwender typogra-
fische Anführungszeichen in ein Eingabefeld eintragen will, wird er sie direkt aus einer
Zeichentabelle kopieren. In Ihrem Quellcode können Sie Unicode-Maskierungszeichen
im Ersetzungstext verwenden, wenn Ihre Sprache solche Maskierungszeichen als Teil
eines literalen Strings unterstützt. So lässt sich zum Beispiel in C# und Java \u201E direkt
im String angeben, aber VB.NET bietet keine Möglichkeit, Unicode-Zeichen in Strings zu
maskieren. In VB.NET können Sie dafür die Funktion ChrW nutzen, um einen Unicode-
Codepoint in ein Zeichen umzuwandeln.

Perl und Ruby


Die Lösungen für Perl und Ruby verwenden zwei spezielle Variablen, die in diesen Spra-
chen zur Verfügung stehen, von uns aber noch nicht erklärt wurden. In $` (Dollar plus
Gravis) findet sich der Teil des Texts links von der Übereinstimmung, während $' (Dol-
lar plus einfaches Anführungszeichen) den Teil des Texts rechts von der Übereinstim-
mung enthält. Anstatt über die Übereinstimmungen im ursprünglichen Ausgangstext zu
iterieren, beginnen wir eine neue Suche für den Teil des Strings nach der vorherigen

3.18 Alle Übereinstimmungen zwischen den Übereinstimmungen einer anderen Regex ersetzen | 207
Übereinstimmung. So können wir mit $` bequem auf den Text zwischen der aktuellen
und der vorherigen Übereinstimmung zugreifen.

Python
Das Ergebnis dieses Codes ist ein Unicode-String, weil der Ersetzungstext als Unicode-
String spezifiziert wurde. Es kann sein, dass Sie encode() aufrufen müssen, um den Text
anzeigen zu können, zum Beispiel mit:
print result.encode('1252')

Siehe auch
Rezepte 3.11, 3.13 und 3.16.

3.19 Einen String aufteilen


Problem
Sie wollen einen String mithilfe eines regulären Ausdrucks aufteilen. Nach dem Aufteilen
werden Sie ein Array oder eine Liste mit Strings haben, in denen sich der Text zwischen
den Regex-Übereinstimmungen findet.
So wollen Sie beispielsweise einen String mit HTML-Tags an den Tags aufteilen. Eine
Bearbeitung von Ichzmagz<b>fette</b>zundz<i>kursive</i>zFonts sollte zu einem Array
von fünf Strings führen: Ichzmagz, fette, zundz, kursive und zFonts.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string[] splitArray = Regex.Split(subjectString, "<[^<>]*>");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
string[] splitArray = null;
try {
splitArray = Regex.Split(subjectString, "<[^<>]*>");
} catch (ArgumentNullException ex) {
// Regulärer Ausdruck und Ausgangstext dürfen nicht null sein
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

208 | Kapitel 3: Mit regulären Ausdrücken programmieren


Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex("<[^<>]*>");
string[] splitArray = regexObj.Split(subjectString);

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung des
Regex-Objekts mit einem kompletten Exception Handling versehen:
string[] splitArray = null;
try {
Regex regexObj = new Regex("<[^<>]*>");
try {
splitArray = regexObj.Split(subjectString);
} catch (ArgumentNullException ex) {
// Ausgangstext darf nicht null sein
}
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim SplitArray = Regex.Split(SubjectString, "<[^<>]*>")

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf
per Exception Handling absichern:
Dim SplitArray As String()
Try
SplitArray = Regex.Split(SubjectString, "<[^<>]*>")
Catch ex As ArgumentNullException
'Regulärer Ausdruck und Ausgangstext dürfen nicht null sein
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("<[^<>]*>")
Dim SplitArray = RegexObj.Split(SubjectString)

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung des
Regex-Objekts mit einem kompletten Exception Handling versehen:
Dim SplitArray As String()
Try
Dim RegexObj As New Regex("<[^<>]*>")
Try
SplitArray = RegexObj.Split(SubjectString)
Catch ex As ArgumentNullException
'Ausgangstext darf nicht null sein

3.19 Einen String aufteilen | 209


End Try
Catch ex As ArgumentException
'Syntaxfehler im regulären Ausdruck
End Try

Java
Sie können String.Split() direkt aufrufen, wenn Sie nur einen String mit einem regulä-
ren Ausdruck aufteilen wollen:
String[] splitArray = subjectString.split("<[^<>]*>");

Wenn die Regex vom Endanwender bereitgestellt wird, sollten Sie Exception Handling
berücksichtigen:
try {
String[] splitArray = subjectString.split("<[^<>]*>");
} catch (PatternSyntaxException ex) {
// Syntaxfehler im regulären Ausdruck
}

Erstellen Sie ein Pattern-Objekt, wenn Sie den gleichen regulären Ausdruck für eine
Reihe von Strings nutzen wollen:
Pattern regex = Pattern.compile("<[^<>]*>");
String[] splitArray = regex.split(subjectString);

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie die Verwendung des
Pattern-Objekts mit einem kompletten Exception Handling versehen:
String[] splitArray = null;
try {
Pattern regex = Pattern.compile("<[^<>]*>");
splitArray = regex.split(subjectString);
} catch (ArgumentException ex) {
// Syntaxfehler im regulären Ausdruck
}

JavaScript
Die Funktion string.split() kann einen String mit einem regulären Ausdruck aufteilen:
result = subject.split(/<[^<>]*>/);

Leider gibt es eine Reihe von Problemen in den verschiedenen Browsern, wenn man
string.split() mit einem regulären Ausdruck verwendet. Es ist zuverlässiger, die Liste
mit eigenem Code aufzubauen:
var list = [];
var regex = /<[^<>]*>/g;
var match = null;
var lastIndex = 0;
while (match = regex.exec(subject)) {
// Browser wie Firefox sollen nicht in einer Endlosschleife hängen bleiben
if (match.index == regex.lastIndex) regex.lastIndex++;
// Text vor der Übereinstimmung hinzufügen

210 | Kapitel 3: Mit regulären Ausdrücken programmieren


list.push(subject.substring(lastIndex, match.index));
lastIndex = match.index + match[0].length;
}
// Rest nach der letzten Übereinstimmung hinzufügen
list.push(subject.substr(lastIndex));

PHP
$result = preg_split('/<[^<>]*>/', $subject);

Perl
@result = split(m/<[^<>]*>/, $subject);

Python
Wenn Sie nur ein paar Strings aufteilen wollen, können Sie die globale Funktion verwen-
den:
result = re.split("<[^<>]*>", subject))

Um die gleiche Regex mehrfach zu verwenden, können Sie ein kompiliertes Objekt nut-
zen:
reobj = re.compile("<[^<>]*>")
result = reobj.split(subject)

Ruby
result = subject.split(/<[^<>]*>/)

Diskussion
Das Aufteilen eines Strings mithilfe eines regulären Ausdrucks sorgt im Prinzip für ein
Ergebnis, das genau das Gegenteil von Rezept 3.10 ist. Statt eine Liste mit allen Regex-
Übereinstimmungen zu erhalten, bekommen Sie eine Liste mit dem Text zwischen den
Übereinstimmungen, einschließlich des Texts vor der ersten und nach der letzten Über-
einstimmung. Die Regex-Übereinstimmungen selbst werden bei der Ausgabe der Split-
Funktion weggelassen.

C# und VB.NET
In .NET werden Sie stets die Methode Regex.Split() verwenden, um einen String mit-
hilfe eines regulären Ausdrucks aufzuteilen. Der erste Parameter von Split() ist immer
der String mit dem Ausgangstext, den Sie aufteilen wollen. Dieser Parameter darf nicht
null sein, da Split() ansonsten eine ArgumentNullException wirft. Der Rückgabewert von
Split() ist immer ein Array mit Strings.
Wenn Sie den regulären Ausdruck nur ein paar Mal nutzen wollen, können Sie einen sta-
tischen Aufruf verwenden. Der zweite Parameter ist dann der reguläre Ausdruck, den Sie

3.19 Einen String aufteilen | 211


verwenden wollen. Sie können als dritten, optionalen Parameter Regex-Optionen mitge-
ben. Wenn Ihr regulärer Ausdruck einen Syntaxfehler enthält, wird eine ArgumentExcep-
tion geworfen.
Möchten Sie den gleichen regulären Ausdruck mit mehreren Strings nutzen, können Sie
Ihren Code effizienter gestalten, indem Sie zuerst ein Regex-Objekt erzeugen und dann
für dieses Objekt die Methode Split() aufrufen. In dem Fall ist der Ausgangstext der ein-
zige erforderliche Parameter.
Wenn Sie für eine Instanz der Regex-Klasse die Methode Split() aufrufen, können Sie
zusätzliche Parameter angeben, mit denen der Arbeitsbereich der Split-Operation einge-
schränkt wird. Lassen Sie diese Parameter weg, wird der String an allen Übereinstimmun-
gen im Ausgangstext aufgeteilt. Die statisch überladenen Versionen von Split() besitzen
diese zusätzlichen Parameter nicht. Sie teilen immer den ganzen String an allen Überein-
stimmungen auf.
Als optionalen zweiten Parameter nach dem Ausgangstext können Sie die maximale
Anzahl an aufgeteilten Strings angeben, die Sie haben wollen. Rufen Sie zum Beispiel
regexObj.Split(subject, 3) auf, erhalten Sie ein Array mit höchstens drei Strings. Die
Funktion Split() wird versuchen, zwei Regex-Übereinstimmungen zu finden und ein
Array mit dem Text vor der ersten Übereinstimmung, dem Text zwischen der ersten und
der zweiten Übereinstimmung und dem Text nach der zweiten Übereinstimmung
zurückzugeben. Alle weiteren möglichen Regex-Übereinstimmungen im Rest des Texts
werden ignoriert und verbleiben im letzten String des Arrays.
Wenn es nicht genug Regex-Übereinstimmungen gibt, um Ihre Grenze zu erreichen, wird
Split() die Aufteilung an allen verfügbaren Regex-Übereinstimmungen vornehmen und
ein Array mit weniger Strings als angegeben zurückliefern. regexObj.Split(subject, 1)
teilt den String gar nicht auf und gibt ein Array mit dem ursprünglichen String als einzi-
gem Element zurück. regexObj.Split(subject, 0) teilt den String an allen Regex-Über-
einstimmungen auf, so wie es Split() ohne den zweiten Parameter tut. Geben Sie eine
negative Zahl an, wirft Split() eine ArgumentOutOfRangeException.
Geben Sie den zweiten Parameter mit der maximalen Zahl an Strings für das zurückzuge-
bende Array an, können Sie auch einen optionalen dritten Parameter spezifizieren, mit
dem der Zeichenindex festgelegt wird, an dem der reguläre Ausdruck mit seiner Suche
beginnen soll. Im Prinzip ist das die Anzahl an Zeichen am Anfang des Strings, die der
reguläre Ausdruck ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis
zu einer bestimmten Position verarbeitet haben und nun nur noch den Rest des Strings
aufteilen wollen.
Die vom regulären Ausdruck übersprungenen Zeichen werden aber dennoch im zurück-
gelieferten Array enthalten sein. Der erste String im Array ist der gesamte Substring vor
der ersten Regex-Übereinstimmung, auch wenn die Suche nicht am Anfang des Strings
begann. Geben Sie den dritten Parameter an, muss er zwischen null und der Länge des
Ausgangstexts liegen. Sonst wirft Split() eine ArgumentOutOfRangeException. Anders als

212 | Kapitel 3: Mit regulären Ausdrücken programmieren


Match() ermöglicht Split() es nicht, einen Parameter anzugeben, mit dem die Länge des
Substrings definiert wird, in dem der reguläre Ausdruck suchen darf.
Wenn es eine Übereinstimmung direkt am Anfang des Ausgangstexts gibt, wird der erste
String im zurückgegebenen Array ein leerer String sein. Werden zwei Regex-Übereinstim-
mungen direkt nebeneinander gefunden, wird dem Array ebenfalls ein leerer String hin-
zugefügt. Tritt am Ende des Strings eine Übereinstimmung auf, wird das letzte Element
des Arrays ein leerer String sein.

Java
Haben Sie nur einen String, der aufgeteilt werden soll, können Sie die Methode split()
direkt für Ihren Ausgangstext aufrufen. Übergeben Sie den regulären Ausdruck als einzi-
gen Parameter. Diese Methode ruft einfach Pattern.compile("Regex").split(subject-
String) auf.
Wollen Sie mehrere Strings aufteilen, greifen Sie auf die Fabrikmethode Pattern.com-
pile() zurück, um ein Pattern-Objekt zu erstellen. So muss Ihr regulärer Ausdruck nur
einmal kompiliert werden. Danach rufen Sie die Methode split() für Ihre Pattern-
Instanz auf und übergeben den Ausgangstext als Parameter. Sie müssen hier kein
Matcher-Objekt erzeugen. Die Klasse Matcher besitzt nicht einmal eine Methode split().
Pattern.split() kann ein optionaler Parameter übergeben werden, was bei
String.split() nicht möglich ist. Sie können diesen zweiten Parameter nutzen, um die
maximale Anzahl an aufgeteilten Strings anzugeben, die Sie erhalten wollen. Rufen Sie
zum Beispiel Pattern.split(subject, 3) auf, erhalten Sie ein Array mit höchstens drei
Strings. Die Funktion split() wird versuchen, zwei Regex-Übereinstimmungen zu finden
und ein Array mit dem Text vor der ersten Übereinstimmung, dem Text zwischen der
ersten und der zweiten Übereinstimmung und dem Text nach der zweiten Übereinstim-
mung zurückzugeben. Alle weiteren möglichen Regex-Übereinstimmungen im Rest des
Texts werden ignoriert und verbleiben im letzten String des Arrays. Wenn es nicht genug
Regex-Übereinstimmungen gibt, um Ihre Grenze zu erreichen, wird split() die Auftei-
lung an allen verfügbaren Regex-Übereinstimmungen vornehmen und ein Array mit
weniger Strings als angegeben zurückliefern. Pattern.split(subject, 1) teilt den String
gar nicht auf und gibt ein Array mit dem ursprünglichen String als einzigem Element
zurück.
Wenn es eine Übereinstimmung direkt am Anfang des Ausgangstexts gibt, wird der erste
String im zurückgegebenen Array ein leerer String sein. Werden zwei Regex-Übereinstim-
mungen direkt nebeneinander gefunden, wird dem Array ebenfalls ein leerer String hin-
zugefügt. Tritt am Ende des Strings eine Übereinstimmung auf, wird das letzte Element
des Arrays ein leerer String sein.
Java entfernt allerdings leere Strings am Ende des Arrays. Wollen Sie, dass die leeren
Strings trotzdem enthalten sind, übergeben Sie Pattern.split() eine negative Zahl als
zweiten Parameter. Damit wird Java angewiesen, den String so oft wie möglich aufzutei-
len und leere Strings am Ende des Arrays zu erhalten. Der tatsächliche Wert des zweiten

3.19 Einen String aufteilen | 213


Parameters ist unwichtig, solange er nur negativ ist. Sie können Java nicht anweisen,
einen String nur n-mal aufzuteilen und gleichzeitig noch leere Strings am Ende des Arrays
zu erhalten.

JavaScript
In JavaScript rufen Sie für den String, den Sie aufteilen wollen, die Methode split() auf.
Übergeben Sie den regulären Ausdruck als einzigen Parameter, um ein Array zu erhalten,
in dem der String so oft wie möglich aufgeteilt wurde. Sie können auch optional einen
zweiten Parameter angeben, der die maximale Anzahl an Strings festlegt, die Sie im
zurückgegebenen Array haben wollen. Dabei sollte es sich um eine positive Zahl han-
deln. Übergeben Sie null, erhalten Sie ein leeres Array. Lassen Sie den zweiten Parameter
weg oder übergeben eine negative Zahl, wird der String so häufig wie möglich aufgeteilt.
Es ist egal, ob Sie die Option /g für die Regex (Rezept 3.4) setzen.
Leider implementiert keiner der verbreiteten Webbrowser jeden Aspekt der Methode
split() so, wie er im JavaScript-Standard spezifiziert wurde. Beispielsweise übernehmen
einige der Browser den von einfangenden Gruppen gefundenen Text, während andere
das nicht tun. Diejenigen, die das tun, behandeln nicht teilnehmende Gruppen auch
nicht konsistent. Um solche Probleme zu vermeiden, nutzen Sie in regulären Ausdrü-
cken, die Sie an split() übergeben, nur nicht-einfangende Gruppen (Rezept 2.9).
Manche JavaScript-Implementierungen übernehmen keine leeren Strings in das Array.
Solche Strings sollten dann vorkommen, wenn zwei Regex-Übereinstimmungen direkt
nebeneinanderliegen oder wenn die Regex direkt am Anfang oder am Ende des aufzutei-
lenden Strings gefunden wird. Da Sie das nicht einfach mit einer kleinen Modifizierung
Ihres regulären Ausdrucks ändern können, ist es vermutlich sicherer, die längere Java-
Script-Lösung in diesem Rezept zu verwenden. Dabei werden alle leeren Strings über-
nommen, aber Sie können die Lösung leicht so anpassen, dass sie weggelassen werden.
Die lange Lösung ist eine Anpassung von Rezept 3.12. Sie fügt dem Array die Texte zwi-
schen den Regex-Übereinstimmungen hinzu. Dazu nutzen wir die in Rezept 3.8 beschrie-
bene Vorgehensweise.
Wenn Sie eine Implementierung von String.prototype.split benötigen, die sich an den
Standard hält und mit allen Browsern funktioniert, finden Sie bei Steven Levithan eine
Lösung unter http://blog.stevenlevithan.com/archives/cross-browser-split.

PHP
Rufen Sie preg_split() auf, um einen String in ein Array mit Strings aufzuteilen, die
durch die Regex-Übereinstimmungen getrennt sind. Übergeben Sie den regulären Aus-
druck als ersten und den Ausgangstext als zweiten Parameter. Lassen Sie den zweiten
Parameter weg, wird $_ als Ausgangstext genutzt.
Sie können einen optionalen dritten Parameter angeben, um die maximale Anzahl an auf-
geteilten Strings festzulegen, die Sie herausbekommen wollen. Rufen Sie zum Beispiel

214 | Kapitel 3: Mit regulären Ausdrücken programmieren


preg_split($regex, $subject, 3) auf, erhalten Sie ein Array mit höchstens drei Strings.
Die Funktion preg_split() wird versuchen, zwei Regex-Übereinstimmungen zu finden
und ein Array mit dem Text vor der ersten Übereinstimmung, dem Text zwischen der
ersten und der zweiten Übereinstimmung und dem Text nach der zweiten Übereinstim-
mung zurückzugeben. Alle weiteren möglichen Regex-Übereinstimmungen im Rest des
Texts werden ignoriert und verbleiben im letzten String des Arrays. Wenn es nicht genug
Regex-Übereinstimmungen gibt, um Ihre Grenze zu erreichen, wird preg_split() die
Aufteilung an allen verfügbaren Regex-Übereinstimmungen vornehmen und ein Array
mit weniger Strings als angegeben zurückliefern. Lassen Sie den dritten Parameter weg
oder setzen ihn auf -1, wird der String so häufig wie möglich aufgeteilt.
Wenn es eine Übereinstimmung direkt am Anfang des Ausgangstexts gibt, wird der erste
String im zurückgegebenen Array ein leerer String sein. Werden zwei Regex-Übereinstim-
mungen direkt nebeneinander gefunden, wird dem Array ebenfalls ein leerer String hin-
zugefügt. Tritt am Ende des Strings eine Übereinstimmung auf, wird das letzte Element
des Arrays ein leerer String sein. Standardmäßig belässt preg_split() diese leeren Strings
im zurückgegebenen Array. Wollen Sie diese Strings nicht haben, übergeben Sie die
Konstante PREG_SPLIT_NO_EMPTY als vierten Parameter.

Perl
Rufen Sie die Funktion split() auf, um einen String in ein Array mit Strings zu untertei-
len, die durch die Regex-Übereinstimmungen getrennt sind. Übergeben Sie als ersten
Parameter einen Regex-Operator und den Ausgangstext als zweiten Parameter.
Sie können einen optionalen dritten Parameter übergeben, um die maximale Anzahl an
Strings im Array festzulegen, die Sie herausbekommen wollen. Rufen Sie zum Beispiel
split(/regex/, subject, 3) auf, erhalten Sie ein Array mit höchstens drei Strings. Die
Funktion split() wird versuchen, zwei Regex-Übereinstimmungen zu finden und ein
Array mit dem Text vor der ersten Übereinstimmung, dem Text zwischen der ersten und
der zweiten Übereinstimmung und dem Text nach der zweiten Übereinstimmung
zurückzugeben. Alle weiteren möglichen Regex-Übereinstimmungen im Rest des Texts
werden ignoriert und verbleiben im letzten String des Arrays. Wenn es nicht genug
Regex-Übereinstimmungen gibt, um Ihre Grenze zu erreichen, wird split() die Auftei-
lung an allen verfügbaren Regex-Übereinstimmungen vornehmen und ein Array mit
weniger Strings als angegeben zurückliefern.
Lassen Sie den dritten Parameter weg, wird Perl die passende Grenze bestimmen. Weisen
Sie das Ergebnis einer Array-Variablen zu, wie es in der Lösung für dieses Rezept
geschieht, wird der String so häufig wie möglich aufgeteilt. Geben Sie als Ziel des Funk-
tionsaufrufs eine Liste von skalaren Variablen an, setzt Perl die Grenze auf die Anzahl der
Variablen plus eins. Perl wird also versuchen, alle Variablen zu füllen, und den nicht auf-
geteilten Rest verwerfen. So teilt ($one, $two, $three) = split(/,/) die Variable $_ mit
einer Grenze von 4.

3.19 Einen String aufteilen | 215


Wenn es eine Übereinstimmung direkt am Anfang des Ausgangstexts gibt, wird der erste
String im zurückgegebenen Array ein leerer String sein. Werden zwei Regex-Übereinstim-
mungen direkt nebeneinander gefunden, wird dem Array ebenfalls ein leerer String hin-
zugefügt. Tritt am Ende des Strings eine Übereinstimmung auf, wird das letzte Element
des Arrays ein leerer String sein.

Python
Die Funktion split() im Modul re teilt einen String mithilfe eines regulären Ausdrucks
auf. Übergeben Sie Ihren regulären Ausdruck als ersten und den Ausgangstext als zwei-
ten Parameter. Der globalen Funktion split() kann man keinen Parameter mit Regex-
Optionen übergeben.
Die Funktion re.split() ruft re.compile() und dann die Methode split() des kompilier-
ten Regex-Objekts auf. Diese Methode hat nur einen erforderlichen Parameter – den
Ausgangstext.
Beide Versionen von split() geben eine Liste mit dem Text zwischen allen Regex-Über-
einstimmungen zurück. Beiden kann ein optionaler Parameter übergeben werden, mit
dem Sie die maximale Anzahl an Übereinstimmungen definieren, an denen der String
aufgeteilt werden soll. Lassen Sie ihn weg oder setzen ihn auf null, wird der String so häu-
fig wie möglich geteilt. Übergeben Sie eine positive Zahl, ist das die maximale Anzahl an
Regex-Übereinstimmungen. Die Ergebnisliste wird einen String mehr enthalten, als Sie
angegeben haben. Der letzte String ist der ungeteilte Rest des Ausgangstexts, der sich hin-
ter der letzten Regex-Übereinstimmung befindet. Gibt es weniger Übereinstimmungen,
als Sie angegeben haben, wird der String ohne Fehlermeldung an allen Übereinstimmun-
gen aufgeteilt.

Ruby
Rufen Sie die Methode split() für den Ausgangstext auf und übergeben Sie den regulä-
ren Ausdruck als ersten Parameter, um den String in ein Array mit Strings aufzuteilen, die
durch die Regex-Übereinstimmungen getrennt sind.
Der Methode split() kann ein optionaler zweiter Parameter mitgegeben werden, mit dem
Sie die maximale Anzahl an aufgeteilten Strings definieren, die Sie herausbekommen wol-
len. Rufen Sie zum Beispiel subject.split(re, 3) auf, erhalten Sie ein Array mit höchstens
drei Strings. Die Funktion split() wird versuchen, zwei Regex-Übereinstimmungen zu fin-
den und ein Array mit dem Text vor der ersten Übereinstimmung, dem Text zwischen der
ersten und der zweiten Übereinstimmung und dem Text nach der zweiten Übereinstim-
mung zurückzugeben. Alle weiteren möglichen Regex-Übereinstimmungen im Rest des
Texts werden ignoriert und verbleiben im letzten String des Arrays. Wenn es nicht genug
Regex-Übereinstimmungen gibt, um Ihre Grenze zu erreichen, wird split() die Aufteilung
an allen verfügbaren Regex-Übereinstimmungen vornehmen und ein Array mit weniger
Strings als angegeben zurückliefern. split(re, 1) teilt den String gar nicht auf und gibt ein
Array mit dem ursprünglichen String als einzigem Element zurück.

216 | Kapitel 3: Mit regulären Ausdrücken programmieren


Wenn es eine Übereinstimmung direkt am Anfang des Ausgangstexts gibt, wird der erste
String im zurückgegebenen Array ein leerer String sein. Werden zwei Regex-Übereinstim-
mungen direkt nebeneinander gefunden, wird dem Array ebenfalls ein leerer String hin-
zugefügt. Tritt am Ende des Strings eine Übereinstimmung auf, wird das letzte Element
des Arrays ein leerer String sein.
Ruby entfernt allerdings leere Strings am Ende des Arrays. Wollen Sie, dass die leeren
Strings trotzdem enthalten sind, übergeben Sie split() eine negative Zahl als zweiten
Parameter. Damit wird Ruby angewiesen, den String so oft wie möglich aufzuteilen und
leere Strings am Ende des Arrays zu erhalten. Der tatsächliche Wert des zweiten Parame-
ters ist unwichtig, solange er nur negativ ist. Sie können Ruby nicht anweisen, einen
String nur n-mal aufzuteilen und gleichzeitig noch leere Strings am Ende des Arrays zu
erhalten.

Siehe auch
Rezept 3.20.

3.20 Einen String aufteilen und die Regex-


Übereinstimmungen behalten
Problem
Sie wollen einen String mithilfe eines regulären Ausdrucks aufteilen. Danach haben Sie
ein Array oder eine Liste mit Strings mit dem Text zwischen den Regex-Übereinstimmun-
gen, aber auch mit den Übereinstimmungen selbst.
Stellen Sie sich vor, dass Sie zum Beispiel einen String mit HTML-Tags an den Tags
aufteilen, aber auch die HTML-Tags selbst behalten wollen. Das Aufteilen von Ichz
magz<b>fette</b>zundz<i>kursive</i>zFonts sollte zu einem Array mit neun Strings
führen: Ichzmagz, <b>, fette, </b>, zundz, <i>, kursive, </i> und zFonts.

Lösung
C#
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
string[] splitArray = Regex.Split(subjectString, "(<[^<>]*>)");
Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Regex regexObj = new Regex("(<[^<>]*>)");
string[] splitArray = regexObj.Split(subjectString);

3.20 Einen String aufteilen und die Regex-Übereinstimmungen behalten | 217


VB.NET
Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen
regulären Ausdruck bearbeiten wollen:
Dim SplitArray = Regex.Split(SubjectString, "(<[^<>]*>)")

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck für viele Strings
nutzen wollen:
Dim RegexObj As New Regex("(<[^<>]*>)")
Dim SplitArray = RegexObj.Split(SubjectString)

Java
List<String> resultList = new ArrayList<String>();
Pattern regex = Pattern.compile("<[^<>]*>");
Matcher regexMatcher = regex.matcher(subjectString);
int lastIndex = 0;
while (regexMatcher.find()) {
resultList.add(subjectString.substring(lastIndex,
regexMatcher.start()));
resultList.add(regexMatcher.group());
lastIndex = regexMatcher.end();
}
resultList.add(subjectString.substring(lastIndex));

JavaScript
var list = [];
var regex = /<[^<>]*>/g;
var match = null;
var lastIndex = 0;
while (match = regex.exec(subject)) {
// Browser wie Firefox sollen nicht in einer Endlosschleife hängen bleiben
if (match.index == regex.lastIndex) regex.lastIndex++;
// Text vor der Übereinstimmung und sie selbst hinzufügen
list.push(subject.substring(lastIndex, match.index), match[0]);
lastIndex = match.index + match[0].length;
}
// Rest nach der letzten Übereinstimmung hinzufügen
list.push(subject.substr(lastIndex));

PHP
$result = preg_split('/(<[^<>]*>)/', $subject, -1,
PREG_SPLIT_DELIM_CAPTURE);

Perl
@result = split(m/(<[^<>]*>)/, $subject);

218 | Kapitel 3: Mit regulären Ausdrücken programmieren


Python
Wenn Sie nur ein paar Strings aufteilen müssen, können Sie die globale Funktion verwen-
den:
result = re.split("(<[^<>]*>)", subject))

Um die gleiche Regex mehrfach anzuwenden, greifen Sie auf ein kompiliertes Objekt
zurück:
reobj = re.compile("(<[^<>]*>)")
result = reobj.split(subject)

Ruby
list = []
lastindex = 0;
subject.scan(/<[^<>]*>/) {|match|
list << subject[lastindex..$~.begin(0)-1];
list << $&
lastindex = $~.end(0)
}
list << subject[lastindex..subject.length()]

Diskussion
.NET
In .NET übernimmt die Methode Regex.Split() die von den einfangenden Gruppen
gefundenen Texte in das Array. .NET 1.0 und 1.1 übernehmen nur die erste einfangende
Gruppe. Ab .NET 2.0 werden alle einfangenden Gruppen als separate Strings in das
Array übernommen. Wenn Sie auch das komplette Suchergebnis im Array haben wollen,
umgeben Sie den gesamten regulären Ausdruck mit einer einfangenden Gruppe. Ab
.NET 2.0 sollten alle anderen Gruppen nicht-einfangend sein, da sie ansonsten mit ins
Array aufgenommen werden.
Die einfangenden Gruppen werden nicht mitgezählt, wenn Sie eine maximale Ober-
grenze für die Funktion Split() festlegen. Rufen Sie regexObj.Split(subject, 4) mit dem
Beispiel-String und der Regex dieses Rezepts auf, werden Sie ein Array mit sieben Strings
erhalten. Dabei handelt es sich um vier Strings mit dem Text vor der ersten Übereinstim-
mung, zwischen den Übereinstimmungen und nach der letzten Übereinstimmung plus
drei Strings mit den Regex-Übereinstimmungen, die von der einzigen einfangenden
Gruppe ermittelt wurden. Sie bekommen also ein Array mit diesen Einträgen: Ichzmagz,
<b>, fette, </b>, zundz, <italic> und kursive</italic>zFonts. Hat Ihre Regex zehn ein-
fangende Gruppen und nutzen Sie .NET 2.0 oder neuer, liefert regexObj.Split(subject,
4) ein Array mit 34 Strings zurück.
.NET bietet keine Möglichkeit, die einfangenden Gruppen aus dem Array auszuschlie-
ßen. Sie haben nur die Möglichkeit, alle benannten und nummerierten einfangenden
Gruppen durch nicht einfangende Gruppen zu ersetzen. Das können Sie in .NET leicht

3.20 Einen String aufteilen und die Regex-Übereinstimmungen behalten | 219


erreichen, indem Sie RegexOptions.ExplicitCapture nutzen und alle benannten Gruppen
durch normale Gruppen ersetzen (also einfach ein Paar Klammern).

Java
Die Java-Methode Pattern.split() stellt keine Möglichkeit zur Verfügung, die Regex-
Übereinstimmungen dem zurückzugebenden Array hinzuzufügen. Stattdessen können
wir Rezept 3.12 so anpassen, dass der Text zwischen den Regex-Übereinstimmungen
zusammen mit den Übereinstimmungen selbst in einer Liste gesammelt wird. Um den
Text zwischen den Übereinstimmungen zu erhalten, nutzen wir die Übereinstimmungs-
details, die in Rezept 3.8 beschrieben werden.

JavaScript
Die JavaScript-Funktion string.split() bietet keine Möglichkeit, zu bestimmen, ob die
Regex-Übereinstimmungen mit in das Array gelangen sollen. Nach dem JavaScript-Stan-
dard sollen alle einfangenden Gruppen mit in das Array geschrieben werden. Leider
machen das die verbreiteten Webbrowser entweder gar nicht oder nur sehr inkonsistent.
Für eine Lösung, die mit allen Browsern funktioniert, können wir Rezept 3.12 so anpas-
sen, dass der Text zwischen den Regex-Übereinstimmungen zusammen mit den Über-
einstimmungen selbst in eine Liste eingetragen wird. Um den Text zwischen den
Übereinstimmungen zu erhalten, nutzen wir die Übereinstimmungsdetails, die in
Rezept 3.8 beschrieben werden.

PHP
Übergeben Sie PREG_SPLIT_DELIM_CAPTURE als vierten Parameter an die Funktion
preg_split(), erhalten Sie im zurückgegebenen Array ebenfalls die Inhalte der einfan-
genden Gruppen. Sie können mithilfe des Operators | auch die Konstanten PREG_
SPLIT_DELIM_CAPTURE und PREG_SPLIT_NO_EMPTY kombinieren.
Die einfangenden Gruppen werden nicht mitgezählt, wenn Sie eine maximale Ober-
grenze als drittes Argument der Funktion preg_split() angeben. Setzen Sie die Grenze
auf vier, erhalten Sie beim Beispiel-String und der Regex dieses Rezepts ein Array mit sie-
ben Strings. Dabei handelt es sich um vier Strings mit dem Text vor der ersten Überein-
stimmung, zwischen den Übereinstimmungen und nach der letzten Übereinstimmung
plus drei Strings mit den Regex-Übereinstimmungen, die von der einzigen einfangenden
Gruppe ermittelt wurden. Sie bekommen also ein Array mit diesen Einträgen: Ichzmagz,
<b>, fette, </b>, zundz, <italic> und kursive</italic>zFonts.

Perl
Die Perl-Funktion split() nimmt alle Texte mit in das Array auf, die von einfangenden
Gruppen gefunden werden. Wollen Sie auch das komplette Suchergebnis der Regex im
Array wiederfinden, setzen Sie den gesamten regulären Ausdruck in eine einfangende
Gruppe.

220 | Kapitel 3: Mit regulären Ausdrücken programmieren


Die einfangenden Gruppen werden nicht mitgezählt, wenn Sie eine maximale Ober-
grenze an die Funktion split() übergeben. Rufen Sie split(/(<[^<>]*>)/, $subject, 4)
mit dem Beispiel-String und der Regex dieses Rezepts auf, erhalten Sie ein Array mit sie-
ben Strings. Dabei handelt es sich um vier Strings mit dem Text vor der ersten Überein-
stimmung, zwischen den Übereinstimmungen und nach der letzten Übereinstimmung
plus drei Strings mit den Regex-Übereinstimmungen, die von der einzigen einfangenden
Gruppe ermittelt wurden. Sie bekommen also ein Array mit diesen Einträgen: Ichzmagz,
<b>, fette, </b>, zundz, <italic> und kursive</italic>zFonts. Hat Ihre Regex zehn ein-
fangende Gruppen, liefert split($regex, $subject, 4) ein Array mit 34 Strings zurück.
Perl bietet keine Möglichkeit an, die einfangenden Gruppen aus dem Array herauszuhal-
ten. Sie haben nur die Möglichkeit, alle benannten und nummerierten einfangenden
Gruppen durch nicht-einfangende Gruppen zu ersetzen.

Python
Die Python-Funktion split() nimmt alle Texte mit in das Array auf, die von einfangen-
den Gruppen gefunden werden. Wollen Sie auch das komplette Suchergebnis der Regex
im Array wiederfinden, setzen Sie den gesamten regulären Ausdruck in eine einfangende
Gruppe.
Die einfangenden Gruppen werden nicht mitgezählt, wenn Sie eine maximale Ober-
grenze für das Aufteilen festlegen. Rufen Sie split(/(<[^<>]*>)/, $subject, 3) mit dem
Beispiel-String und der Regex dieses Rezepts auf, erhalten Sie ein Array mit sieben
Strings. Der String wird drei Mal aufgeteilt, was zu vier Strings mit dem Text vor der ers-
ten Übereinstimmung, zwischen den Übereinstimmungen und nach der letzten Überein-
stimmung führt plus drei Strings mit den Regex-Übereinstimmungen, die von der
einzigen einfangenden Gruppe ermittelt wurden. Sie bekommen also ein Array mit die-
sen Einträgen: "Ich mag", "<b>", "fette", "</b>", "und", "<italic>" und "kursive</italic>
Fonts". Hat Ihre Regex zehn einfangende Gruppen, liefert split($regex, $subject, 3) ein
Array mit 34 Strings zurück.
Python bietet keine Möglichkeit an, die einfangenden Gruppen aus dem Array herauszu-
halten. Sie haben nur die Möglichkeit, alle benannten und nummerierten einfangenden
Gruppen durch nicht-einfangende Gruppen zu ersetzen.

Ruby
Mit Rubys Methode String.split() können Sie die Regex-Übereinstimmungen nicht
dem resultierenden Array hinzufügen. Stattdessen können wir Rezept 3.11 so anpassen,
dass der Text zwischen den Regex-Übereinstimmungen zusammen mit den Übereinstim-
mungen selbst in einer Liste gesammelt wird. Um den Text zwischen den Übereinstim-
mungen zu erhalten, nutzen wir die Übereinstimmungsdetails, die in Rezept 3.8
beschrieben werden.

3.20 Einen String aufteilen und die Regex-Übereinstimmungen behalten | 221


Siehe auch
Rezept 2.9 beschreibt einfangende und nicht-einfangende Gruppen.
Rezept 2.11 beschreibt benannte Gruppen.

3.21 Zeile für Zeile suchen


Problem
Klassische grep-Tools wenden Ihren regulären Ausdruck immer nur auf eine Zeile Text
an und geben die passenden (oder auch die nicht passenden) Zeilen aus. Sie haben ein
Array mit Strings oder einen mehrzeiligen String, den Sie so verarbeiten wollen.

Lösung
C#
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
string[] lines = Regex.Split(subjectString, "\r?\n");

Dann iterieren Sie über das Array lines:


Regex regexObj = new Regex("Regex-Muster");
for (int i = 0; i < lines.Length; i++) {
if (regexObj.IsMatch(lines[i])) {
// Die Regex passt zu lines[i]
} else {
// Die Regex passt nicht zu lines[i]
}
}

VB.NET
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
Dim Lines = Regex.Split(SubjectString, "\r?\n")

Dann iterieren Sie über das Array Lines:


Dim RegexObj As New Regex("Regex-Muster")
For i As Integer = 0 To Lines.Length - 1
If RegexObj.IsMatch(Lines(i)) Then
'Die Regex passt zu Lines(i)
Else
'Die Regex passt nicht zu Lines(i)
End If
Next

222 | Kapitel 3: Mit regulären Ausdrücken programmieren


Java
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
String[] lines = subjectString.split("\r?\n");

Dann iterieren Sie über das Array lines:


Pattern regex = Pattern.compile("Regex-Muster");
Matcher regexMatcher = regex.matcher("");
for (int i = 0; i < lines.length; i++) {
regexMatcher.reset(lines[i]);
if (regexMatcher.find()) {
// Die Regex passt zu lines[i]
} else {
// Die Regex passt nicht zu lines[i]
}
}

JavaScript
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält. Wie in Rezept 3.19
erwähnt, entfernen manche Browser leere Zeilen aus dem Array.
var lines = subject.split(/\r?\n/);

Dann iterieren Sie über das Array lines:


var regexp = /Regex-Muster/;
for (var i = 0; i < lines.length; i++) {
if (lines[i].match(regexp)) {
// Die Regex passt zu lines[i]
} else {
// Die Regex passt nicht zu lines[i]
}
}

PHP
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
$lines = preg_split('/\r?\n/', $subject)

Dann iterieren Sie über das Array $lines:


foreach ($lines as $line) {
if (preg_match('/Regex-Muster/', $line)) {
// Die Regex passt zu $line
} else {
// Die Regex passt nicht zu $line
}
}

3.21 Zeile für Zeile suchen | 223


Perl
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
@lines = split(m/\r?\n/, $subject)

Dann iterieren Sie über das Array $lines:


foreach $line (@lines) {
if ($line =~ m/Regex-Muster/) {
# Die Regex passt zu $line
} else {
# Die Regex passt nicht zu $line
}
}

Python
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
lines = re.split("\r?\n", subject);

Dann iterieren Sie über das Array lines:


reobj = re.compile("Regex-Muster")
for line in lines[:]:
if reobj.search(line):
# Die Regex passt zu line
else:
# Die Regex passt nicht zu line

Ruby
Wenn Sie einen mehrzeiligen String haben, teilen Sie ihn zunächst in ein Array aus
Strings auf, wobei jeder String im Array eine Zeile Text enthält:
lines = subject.split(/\r?\n/)

Dann iterieren Sie über das Array lines:


re = /Regex-Muster/
lines.each { |line|
if line =~ re
# Die Regex passt zu line
else
# Die Regex passt nicht zu line
}

Diskussion
Wenn Sie mit zeilenbasierten Daten arbeiten, können Sie sich viel Ärger ersparen, indem
Sie die Daten in ein Array mit Zeilen aufteilen, statt mit einem großen String mit Zeilen-
umbrüchen zu werkeln. Dann können Sie nämlich Ihre Regex auf jeden String im Array

224 | Kapitel 3: Mit regulären Ausdrücken programmieren


anwenden, ohne sich darum kümmern zu müssen, ob sie auf mehr als eine Zeile passt.
Durch dieses Vorgehen ist es auch einfacher, die Beziehungen zwischen Zeilen im Auge
zu behalten. So können Sie zum Beispiel leicht über das Array iterieren, um mit einer
Regex eine Kopfzeile zu finden, während eine andere eine Fußzeile findet. Sind diese Zei-
len erkannt, können Sie mit einer dritten Regex nach den Datenzeilen suchen, an denen
Sie eigentlich interessiert sind. Das klingt vielleicht nach ziemlich viel Arbeit, aber es ist
ein sehr geradliniges Vorgehen, und der so aufgebaute Code ist sehr performant. Ver-
sucht man, eine einzelne Regex aufzubauen, mit der sowohl Kopfzeilen als auch Daten
und Fußzeilen gefunden werden können, ist dies viel komplizierter und führt zu einem
deutlich langsameren regulären Ausdruck.
Verarbeitet man einen String Zeile für Zeile, ist es ebenfalls einfacher, einen regulären
Ausdruck zu negieren. Reguläre Ausdrücke bieten keine einfache Möglichkeit, nach Zei-
len zu suchen, die ein bestimmtes Wort nicht enthalten. Nur Zeichenklassen lassen sich
leicht negieren. Haben Sie Ihren String aber schon in Zeilen aufgeteilt, ist es einfach, die
Zeilen zu finden, in denen ein bestimmtes Wort nicht vorkommt: Man sucht einfach das
Wort in den Zeilen und entfernt diejenigen, die das Wort enthalten.
In Rezept 3.19 wird gezeigt, wie Sie einen String einfach in ein Array aufteilen können.
Der reguläre Ausdruck ‹\r\n› findet die Zeichen (CR) und (LF), wenn sie hintereinan-
derliegen. Damit werden unter Microsoft Windows Zeilen getrennt. ‹\n› passt zu einem
(LF)-Zeichen, das Zeilen unter Unix und seinen Derivaten findet, wie zum Beispiel Linux
und sogar OS X. Da diese beiden regulären Ausdrücke nur einfachen Text enthalten,
brauchen Sie nicht einmal einen regulären Ausdruck. Wenn Ihre Programmiersprache
Strings auch mit literalem Text aufteilen kann, sollten Sie diese Möglichkeit hier nutzen.
Sind Sie sich nicht sicher, wie die Zeilenumbrüche in Ihren Daten aussehen, können Sie
mit dem regulären Ausdruck ‹\r?\n› aufteilen. Indem Sie das (CR) optional machen,
passt diese Regex entweder zu einem (CRLF)-Zeilenumbruch im Windows-Stil oder zu
einem (LF)-Zeilenumbruch im Unix-Stil.
Haben Sie Ihren String in ein String-Array umgewandelt, können Sie bequem mit einer
Schleife darüberlaufen. Innerhalb der Schleife nutzen Sie das Rezept aus Rezept 3.5, um
herauszufinden, welche Zeilen passen und welche nicht.

Siehe auch
Rezepte 3.11 und 3.19.

3.21 Zeile für Zeile suchen | 225


KAPITEL 4
Validierung und Formatierung

Dieses Kapitel enthält Rezepte für das Validieren und Formatieren von Benutzereinga-
ben. Manche der Lösungen zeigen, wie man gültige Eingaben überprüft, so zum Beispiel
Postleitzahlen in den USA, die entweder fünf oder neun Ziffern enthalten können.
Andere sind dazu da, allgemein anerkannte Formatierungsrichtlinien auf Daten anzu-
wenden, wie zum Beispiel bei Telefonnummern, Datumswerten und Kreditkartennum-
mern.
Abgesehen davon, dass Regexes Ihnen dabei helfen, ungültige Eingaben zu verhindern,
können diese Rezepte auch die Benutzerfreundlichkeit Ihrer Anwendungen verbessern.
Meldungen wie „keine Leerzeichen oder Bindestriche“ neben Feldern für Kreditkarten-
nummern frustrieren die Leute häufig oder werden einfach ignoriert. Reguläre Ausdrücke
sorgen dafür, dass die Anwender die Daten so eingeben können, wie es für sie am ein-
fachsten ist, ohne dass Sie hinterher zu viel Arbeit damit haben.
Einige Programmiersprachen stellen über eingebaute Klassen oder Bibliotheken Funktio-
nalitäten bereit, die der mancher Rezepte ähneln. Je nachdem, was Sie brauchen, kann es
sinn-voller sein, auf diese Optionen zurückzugreifen, daher werden wir sie – wo es passt
– erwähnen.

4.1 E-Mail-Adressen überprüfen


Problem
Sie haben ein Formular auf Ihrer Website oder ein Eingabefeld in Ihrer Anwendung, in
das der Benutzer eine E-Mail-Adresse eingeben soll. Sie wollen einen regulären Ausdruck
verwenden, um diese E-Mail-Adresse zu validieren, bevor Sie versuchen, eine E-Mail
dorthin zu schicken. Damit wird die Menge an E-Mails reduziert, die als unzustellbar an
Sie zurückgehen.

| 227
Lösung
Einfach
Die einfache Lösung führt eine sehr simple Validierung durch. Sie prüft nur, ob die E-
Mail-Adresse ein einzelnes At-Zeichen (@) besitzt und keinen Whitespace enthält:
^\S+@\S+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
\A\S+@\S+\Z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Einfach, mit Einschränkung der Zeichen


Der Domainname, also der Teil nach dem @-Zeichen, ist auf bestimmte Zeichen be-
schränkt. Der Benutzername, der Teil vor dem @-Zeichen, ist das ebenfalls, wobei die
meisten E-Mail-Clients und -Server bei der Akzeptanz etwas großzügiger verfahren:
^[A-Z0-9+_.-]+@[A-Z0-9.-]+$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
\A[A-Z0-9+_.-]+@[A-Z0-9.-]+\Z
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Einfach, mit allen Zeichen


Dieser reguläre Ausdruck erweitert den vorigen, indem er mehr (selten genutzte) Zeichen
im Benutzernamen zulässt. Nicht jede E-Mail-Software kann mit all diesen Zeichen
umgehen, aber wir haben alle Zeichen aufgenommen, die nach dem RFC 2822 zulässig
sind. In diesem RFC ist das E-Mail-Format definiert. Unter diesen Zeichen gibt es ein
paar, die durchaus ein Sicherheitsrisiko darstellen können, wenn sie als Benutzereingabe
direkt an eine SQL-Anweisung übergeben werden – so zum Beispiel das einfache Anfüh-
rungszeichen (') und der vertikale Strich (|). Stellen Sie immer sicher, dass kritische Zei-
chen maskiert sind, wenn Sie die E-Mail-Adresse in einen String für ein anderes
Programm einfügen. Damit vermeiden Sie Sicherheitslecks, zum Beispiel durch SQL-
Injection-Angriffe:
^[\w!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

228 | Kapitel 4: Validierung und Formatierung


\A[\w!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\Z
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Keine Punkte am Anfang, am Ende und direkt nacheinander


Sowohl der Benutzername als auch der Domainname dürfen mehr als einen Punkt ent-
halten, aber es dürfen keine zwei Punkte direkt aufeinanderfolgen. Zudem dürfen weder
das erste noch das letzte Zeichen im Benutzer- und Domainnamen Punkte sein:
^[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@
[A-Z0-9-]+(?:\.[A-Z0-9-]+)*$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
\A[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@
[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\Z
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Top-Level-Domain besitzt zwischen zwei und sechs Zeichen


Dieser reguläre Ausdruck ergänzt die vorherigen Versionen um weitere Regeln für den
Domainnamen: Er muss mindestens ein Zeichen enthalten, und der Teil des Domain-
namens nach dem Punkt darf nur aus Buchstaben bestehen. Somit muss die Domain
mindestens zwei Level besitzen, wie zum Beispiel secondlevel.de oder thirdlevel.
secondlevel.de. Die Top-Level-Domain, hier .de, muss zwischen zwei und sechs Buch-
staben enthalten. Alle länderspezifischen Top-Level-Domains haben nur zwei Buchsta-
ben. Die generischen Top-Level-Domains besitzen zwischen drei (.com) und sechs
Zeichen (.museum):
^[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@
(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
\A[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@
(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\Z
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion
E-Mail-Adressen
Wenn Sie dachten, für etwas konzeptionell so Einfaches wie das Validieren einer E-Mail-
Adresse gäbe es eine schlichte Regex-Lösung, die allen Erfordernissen gerecht wird, lie-

4.1 E-Mail-Adressen überprüfen | 229


gen Sie leider ziemlich falsch. Dieses Rezept ist ein wundervolles Beispiel für die Tatsa-
che, dass Sie vor dem Schreiben eines regulären Ausdrucks genau wissen müssen, was Sie
finden wollen. Es gibt keine allgemein anerkannte Regel dafür, welche E-Mail-Adressen
gültig sind und welche nicht. Das hängt nämlich stark von Ihrer Definition von gültig ab.
asdf@asdf.asdf ist laut RFC 2822 (der Syntaxdefinition für E-Mail-Adressen) gültig. Aber
sie ist nicht gültig, wenn Sie festlegen, dass eine valide E-Mail-Adresse auch Mails anneh-
men muss. Denn es gibt keine Top-Level-Domain asdf.
Die kurze Antwort auf das Gültigkeitsproblem ist, dass Sie nicht wissen können, ob
monika.mustermann@irgendwo.de eine E-Mail-Adresse ist, die tatsächlich E-Mails empfan-
gen kann, bis Sie versuchen, dorthin eine E-Mail zu schicken. Und selbst dann können
Sie nicht sicher sein, ob eine fehlende Reaktion auf die E-Mail zeigt, dass die Domain
irgendwo.de still und leise Mails verwirft, die an nicht vorhandene Mailboxen geschickt
werden, oder dass Monika Mustermann auf den Lösch-Button geklickt hat beziehungs-
weise der Spam-Filter diese Aufgabe übernommen hat.
Da Sie letztendlich immer eine E-Mail verschicken müssen, wenn Sie sicherstellen wol-
len, dass die entsprechende Adresse existiert, können Sie sich auch dazu entschließen,
einen einfacheren, etwas laxeren regulären Ausdruck zu nutzen. Es kann durchaus sinn-
voll sein, die eine oder andere ungültige E-Mail-Adresse durchrutschen zu lassen, statt
die Leute damit zu verärgern, ihre gültigen E-Mail-Adressen abzulehnen. Aus diesem
Grund verwenden Sie ruhig den regulären Ausdruck für „Einfach, mit allen Zeichen“.
Obwohl er ganz offensichtlich viele Einträge erlaubt, die keine E-Mail-Adressen sind, wie
zum Beispiel #$%@.-, ist die Regex schnell und einfach, zudem wird sie niemals gültige E-
Mail-Adressen ungerechtfertigterweise ablehnen.
Wenn Sie vermeiden wollen, zu viele nicht zustellbare E-Mails zu verschicken, aber trotz-
dem keine echten E-Mail-Adressen ablehnen wollen, ist die Regex in „Top-Level-Domain
besitzt zwischen zwei und sechs Zeichen“ auf Seite 229 eine gute Wahl.
Sie müssen sich überlegen, wie umfassend Ihr regulärer Ausdruck sein soll. Wenn Sie
Benutzereingaben validieren, werden Sie vermutlich eine eher komplexe Regex nutzen,
da der Anwender alles Mögliche eingeben kann. Aber wenn Sie Datenbankdateien durch-
suchen, bei denen Sie wissen, dass sie nur gültige E-Mail-Adressen enthalten, können Sie
eine sehr einfache Regex nutzen, die eigentlich nur die E-Mail-Adressen von den anderen
Daten separiert. Selbst die Lösung im Abschnitt „Einfach“ kann dann ausreichen.
Schließlich müssen Sie sich auch noch Gedanken darüber machen, wie zukunftssicher
Ihr regulärer Ausdruck sein soll. In der Vergangenheit war es sinnvoll, die Top-Level-
Domain auf Zwei-Buchstaben-Kombinationen für die Länderkürzel zu beschränken und
dazu eine vollständige Liste der generischen Top-Level-Domains anzugeben, also
‹com|net|org|mil|edu›. Dadurch dass jetzt immer wieder neue Top-Level-Domains hin-
zukommen, veralten solche regulären Ausdrücke aber recht schnell.

230 | Kapitel 4: Validierung und Formatierung


Regex-Syntax
Die in diesem Rezept vorgestellten regulären Ausdrücke enthalten bereits alle Grundele-
mente der Regex-Syntax. Wenn Sie sich dazu Kapitel 2 durchlesen, können Sie schon
90% aller Aufgaben erledigen, die sich durch reguläre Ausdrücke am besten lösen lassen.
Bei allen hier genutzten regulären Ausdrücken muss die Option zum Ignorieren von
Groß- und Kleinschreibung eingeschaltet sein. Ansonsten wären nur Großbuchstaben
zulässig. Schaltet man die Option ein, können Sie ‹[A-Z]› statt ‹[A-Za-z]› eingeben und
damit ein paar Tastendrücke sparen. Wenn Sie einen der letzten beiden regulären Aus-
drücke verwenden, ist diese Option sehr praktisch. Ansonsten müssten Sie jedes Zeichen
‹X› durch ‹[Xx]› ersetzen.
‹\S› und ‹\w› sind Zeichenklassen, die im Rezept in Rezept 2.3 beschrieben wurden. ‹\S›
passt zu jedem Nicht-Whitespace-Zeichen, während ‹\w› ein Wortzeichen findet.
‹@› und ‹\.› passen zu einem literalen @-Zeichen beziehungsweise zu einem Punkt. Da
der Punkt ein Metazeichen ist, wenn man ihn außerhalb von Zeichenklassen verwendet,
muss er mit einem Backslash maskiert werden. Das @-Zeichen hat in keiner der in die-
sem Buch behandelten Regex-Varianten eine besondere Bedeutung. In Rezept 2.1 finden
Sie eine Liste aller Metazeichen, die maskiert werden müssen.
‹[A-Z0-9.-]› und die anderen Sequenzen zwischen eckigen Klammern sind Zeichenklas-
sen. Diese lässt alle Zeichen von A bis Z, alle Ziffern von 0 bis 9 sowie einen literalen
Punkt und einen Bindestrich zu. Auch wenn der Bindestrich in einer Zeichenklasse nor-
malerweise für den Aufbau einer Sequenz genutzt wird, wird er als literaler Strich behan-
delt, wenn er das letzte Zeichen der Zeichenklasse ist. Das Rezept 2.3 erklärt alles
Notwendige über Zeichenklassen, einschließlich der Kombinationen mit Abkürzungen,
wie in ‹[\w!#$%&'*+/=?`{|}~^.-]›. Diese Klasse passt zu einem Wortzeichen, aber auch
zu einem beliebigen anderen der 19 aufgeführten Satzzeichen.
‹+› und ‹*› sind, wenn man sie außerhalb von Zeichenklassen verwendet, Quantoren.
Das Pluszeichen wiederholt das vorige Regex-Token ein Mal oder mehrmals, während
der Stern das Token null Mal oder mehrmals wiederholt. In diesen regulären Ausdrücken
ist das zu wiederholende Token normalerweise eine Zeichenklasse, manchmal auch eine
Gruppe. Daher passt ‹[A-Z0-9.-]+› zu einem oder mehreren Buchstaben, Ziffern, Punk-
ten und/oder Bindestrichen.
Als Beispiel für die Verwendung einer Gruppe passt ‹(?:[A-Z0-9-]+\.)+› zu einem oder
mehreren Zeichen, Ziffern und/oder Bindestrichen, gefolgt von einem literalen Punkt.
Das Pluszeichen wiederholt diese Gruppe ein Mal oder mehrmals. Die Gruppe muss min-
destens einmal passen, kann aber auch beliebig häufig vorkommen. In Rezept 2.12 wer-
den die Mechanismen solcher Konstrukte im Detail beschrieben.
‹(?:Gruppe)› ist eine nicht-einfangende Gruppe. Verwenden Sie sie, um eine Gruppe aus
einem Teil des regulären Ausdrucks zu erstellen, sodass Sie einen Quantor auf die ganze
Gruppe anwenden können. Die einfangende Gruppe ‹(Gruppe)› sorgt für das Gleiche mit
einer einfacheren Syntax, daher können Sie in allen bisher genutzten regulären Ausdrü-
cken ‹(?:› durch ‹(› ersetzen, ohne die Suchergebnisse zu verändern.

4.1 E-Mail-Adressen überprüfen | 231


Aber da wir nicht daran interessiert sind, Teile der E-Mail-Adresse getrennt einzufangen,
ist die nicht-einfangende Gruppe effizienter, auch wenn der reguläre Ausdruck dadurch
schlechter lesbar wird. In Rezept 2.9 erfahren Sie alles über einfangende und nicht-ein-
fangende Gruppen.
Die Anker ‹^› und ‹$› zwingen den regulären Ausdruck dazu, die Übereinstimmung am
Anfang beziehungsweise Ende des Ausgangstexts zu finden. Setzt man den gesamten
regulären Ausdruck zwischen diese Zeichen, wird damit dafür gesorgt, dass der gesamte
Text passen muss.
Das ist wichtig, wenn man Benutzereingaben validiert, denn Sie wollen ja drop database;
-- joe@server.com Haha! als gültige E-Mail-Adresse nicht wirklich akzeptieren. Ohne die
Anker würden alle aufgeführten regulären Ausdrücke passen, weil sie joe@server.com in
der Mitte des angegebenen Texts finden. In Rezept 2.5 wird die Sache detaillierter
beschrieben. Dieses Rezept erklärt auch, warum die Option Zirkumflex und Dollar pas-
sen auf Zeilenumbrüche abgeschaltet sein muss.
In Ruby passen Zirkumflex und Dollarzeichen immer auf Zeilenumbrüche. Der reguläre
Ausdruck mit Zirkumflex und Dollarzeichen funktioniert in Ruby korrekt, aber nur
wenn der String, den Sie validieren wollen, keine Zeilenumbrüche enthält. Enthält der
String eventuell Zeilenumbrüche, passen alle Regexes mit ‹^› und ‹$› auf die E-Mail-
Adresse in drop database; -- (LF)joe@server.com(LF) Haha!, wobei (LF) für einen Zei-
lenumbruch steht.
Um das zu vermeiden, sollten Sie stattdessen die Anker ‹\A› und ‹\Z› verwenden. Diese
passen nur am Anfang und am Ende des Strings – egal mit welcher Option und in wel-
cher Regex-Variante, jedoch mit der Ausnahme von JavaScript. JavaScript kennt ‹\A›
und ‹\Z› gar nicht. In Rezept 2.5 werden diese Anker erklärt.

Der Unterschied zwischen ‹^› und ‹$› sowie ‹\A› und ‹\Z› betrifft alle
regulären Ausdrücke, die Benutzereingaben überprüfen. Es gibt eine Reihe
von diesen Regexes in diesem Buch. Wir werden zwar gelegentlich an den
Unterschied erinnern, aber nicht ständig diesen Rat wiederholen oder
getrennte Lösungen für JavaScript und Ruby anbieten. In vielen Fällen
werden wir nur eine Lösung mit Zirkumflex und Dollar vorstellen und
Ruby als kompatible Variante aufführen. Nutzen Sie Ruby, sollten Sie
daran denken, ‹\A› und ‹\Z› zu verwenden, wenn Sie das Problem mit den
einzelnen Zeilen in einem mehrzeiligen String vermeiden wollen.

Eine Regex Schritt für Schritt aufbauen


Dieses Rezept zeigt Ihnen, wie Sie einen regulären Ausdruck Schritt für Schritt aufbauen
können. Diese Technik ist insbesondere bei einem interaktiven Regex-Tester nützlich,
wie zum Beispiel bei RegexBuddy.
Zunächst laden Sie eine Reihe gültiger und ungültiger Beispieldaten in das Tool. In die-
sem Fall wäre das eine Liste gültiger und ungültiger E-Mail-Adressen.

232 | Kapitel 4: Validierung und Formatierung


Dann schreiben Sie einen einfachen regulären Ausdruck, der alle gültigen E-Mail-Adres-
sen findet. Ignorieren Sie zunächst die ungültigen Adressen. ‹^\S+@\S+$› zeigt schon die
grundlegende Struktur einer E-Mail-Adresse: einen Benutzernamen, ein At-Zeichen und
einen Domainnamen.
Haben Sie die prinzipielle Struktur Ihres Textmusters definiert, können Sie jeden Teil so
lange verbessern, bis der reguläre Ausdruck keine ungültigen Daten mehr findet. Soll die
Regex nur mit schon vorhandenen Daten arbeiten, kann das schnell gehen. Muss aber
eine beliebige Benutzereingabe verarbeitet werden, ist es viel schwerer, ihren regulären
Ausdruck so restriktiv zu machen, dass er wirklich nur gültige Daten erfasst.

Variationen
Wenn Sie in größeren Textmengen nach E-Mail-Adressen suchen wollen, können Sie die
Anker ‹^› und ‹$› nicht verwenden. Sie einfach nur aus der Regex zu entfernen, ist aber
auch nicht korrekt. Bei der abschließenden Regex, die die Top-Level-Domain auf Buch-
staben beschränkt, würden Sie so zum Beispiel auch asdf@asdf.as in asdf@asdf.as99 fin-
den. Statt die Regex-Übereinstimmung am Anfang und Ende des Gesamttexts zu
verankern, müssen Sie stattdessen festlegen, dass der Anfang des Benutzernamens und
die Top-Level-Domain kein Teil längerer Wörter sein darf.
Das lässt sich leicht mit einem Paar Wortgrenzen erreichen. Ersetzen Sie ‹^› und ‹$›
durch ‹\b›. So wird zum Beispiel ‹^[A-Z0-9+_.-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$› zu
‹\b[A-Z0-9+_.-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b›.
Diese Regex kombiniert den Benutzernamen-Teil aus „Einfach, mit Einschränkung der
Zeichen“ auf Seite 228 mit dem Domainnamen-Teil aus „Top-Level-Domain besitzt zwi-
schen zwei und sechs Zeichen“ auf Seite 229. Wir haben festgestellt, dass dieser reguläre
Ausdruck in der Praxis ziemlich gut funktioniert.

Siehe auch
Der RFC 2822 legt Struktur und Syntax von E-Mail-Nachrichten fest – einschließlich der
E-Mail-Adressen, die in solchen Nachrichten verwendet werden. Sie können ihn unter
http://www.ietf.org/rfc/rfc2822.txt herunterladen.

4.2 Nordamerikanische Telefonnummern validieren


Problem
Sie wollen herausfinden, ob ein Benutzer eine Telefonnummer aus Nordamerika in
einem gebräuchlichen Format eingegeben hat – einschließlich der Vorwahl. Zu diesen
Formaten gehören 1234567890, 123-456-7890, 123.456.7890, 123 456 7890, (123) 456 7890
und alle entsprechenden Kombinationen. Ist die Telefonnummer gültig, wollen Sie sie in
Ihr Standardformat (123) 456-7890 umwandeln, sodass Ihre Telefonnummerndaten
konsistent sind.

4.2 Nordamerikanische Telefonnummern validieren | 233


Lösung
Ein regulärer Ausdruck kann leicht herausfinden, ob ein Benutzer etwas eingegeben hat,
das wie eine gültige Telefonnummer aussieht. Durch einfangende Gruppen, die sich
jeden Ziffernbereich merken, kann der gleiche reguläre Ausdruck auch genutzt werden,
um den Ausgangstext durch das Format zu ersetzen, das Sie haben wollen.

Regulärer Ausdruck
^\(?([0-9]{3})\)?[-.z]?([0-9]{3})[-.z]?([0-9]{4})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzungstext
($1)z$2-$3
Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP
(\1)z\2-\3
Ersetzungstextvarianten: Python, Ruby

C#
Regex regexObj =
new Regex(@"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$");

if (regexObj.IsMatch(subjectString)) {
string formattedPhoneNumber =
regexObj.Replace(subjectString, "($1) $2-$3");
} else {
// Ungültige Telefonnummer
}

JavaScript
var regexObj = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/;
if (regexObj.test(subjectString)) {
var formattedPhoneNumber =
subjectString.replace(regexObj, "($1) $2-$3");
} else {
// Ungültige Telefonnummer
}

Andere Programmiersprachen
In den Rezepten 3.5 und 3.15 finden Sie Beispiele für das Implementieren dieses regulä-
ren Ausdrucks mit anderen Programmiersprachen.

234 | Kapitel 4: Validierung und Formatierung


Diskussion
Dieser reguläre Ausdruck passt auf drei Zifferngruppen. Die erste Gruppe kann optional
von Klammern umschlossen sein, und auf die ersten beiden Gruppen kann optional eines
von drei Trennzeichen folgen (ein Bindestrich, ein Punkt oder ein Leerzeichen). Das
folgende Layout unterteilt den regulären Ausdruck in seine einzelnen Teile, wobei die
redundanten Zifferngruppen weggelassen werden:
^ # Übereinstimmung am String-Anfang sicherstellen.
\( # Literale "(" ...
? # null oder ein Mal finden.
( # Folgende Übereinstimmung in Rückwärtsreferenz 1 einfangen ...
[0-9] # Eine Ziffer ...
{3} # genau drei Mal finden.
) # Ende der einfangenden Gruppe 1.
\) # Literale ")" ...
? # null- oder einmal finden.
[-. ] # Eines der Zeichen aus "-. " ...
? # null oder ein Mal finden.
... # [Restliche Ziffern und Trennzeichen finden.]
$ # Übereinstimmung am String-Ende sicherstellen.

Lassen Sie uns jeden dieser Teile genauer anschauen.


Die Zeichen ‹^› und ‹$› am Anfang und Ende des regulären Ausdrucks sind besondere
Metazeichen, sogenannte Anker oder Assertions. Sie finden keinen Text, sondern eine
Position im Text. So stellt ‹^› sicher, dass die Übereinstimmung am Anfang des Texts lie-
gen muss, während ‹$› nur am Ende des Texts passt. Damit haben Sie veranlasst, dass
die Telefonnummern-Regex zu keinem längeren Text passt, wie zum Beispiel 123-456-
78901.
Wie wir wiederholt gesehen haben, sind Klammern in regulären Ausdrücken besondere
Zeichen, aber in diesem Fall wollen wir es dem Anwender ermöglichen, Klammern einzu-
geben und sie auch von unserer Regex erkennen zu lassen. Dies ist ein Paradebeispiel für
die Verwendung eines Backslashs, um ein Sonderzeichen so zu maskieren, dass der regu-
läre Ausdruck es als literale Eingabe behandelt. Daher passen die Sequenzen ‹\(› und
‹\)›, die die erste Zifferngruppe umschließen, auf literale Klammerzeichen. Beiden folgt
ein Fragezeichen, was bedeutet, dass sie optional sind. Wir werden das Fragezeichen
noch genauer behandeln, wollen aber erst die anderen Arten von Tokens in diesem regu-
lären Ausdruck besprechen.
Die Klammern, die ohne Backslashs genutzt werden, sind einfangende Gruppen, die spä-
ter verwendet werden, um sich die daHigefundenen Werte zu merken und sie wieder auf-
zurufen. In diesem Fall werden Rückwärtsreferenzen auf die eingefangenen Werte im
Ersetzungstext angewendet, damit wir die Telefonnummer wie gewünscht umformatie-
ren können.
Zwei weitere Arten von Tokens, die in diesem regulären Ausdruck verwendet werden,
sind Zeichenklassen und Quantoren. Zeichenklassen ermöglichen es Ihnen, ein beliebi-
ges Zeichen aus einer Menge von Zeichen zu finden. ‹[0-9]› ist eine Zeichenklasse, die

4.2 Nordamerikanische Telefonnummern validieren | 235


eine beliebige Ziffer findet. Die in diesem Buch behandelten Regex-Varianten bieten alle
die Zeichenklassenabkürzung ‹\d› an, die ebenfalls eine Ziffer findet, allerdings werden
in manchen Varianten damit auch Ziffern aus beliebigen Schriftsystemen oder Sprachen
gefunden. Das wollen wir hier natürlich nicht. In Rezept 2.3 erhalten Sie mehr Informa-
tionen über ‹\d›.
‹[-.z]› ist eine weitere Zeichenklasse, durch die eines von drei möglichen Trennzeichen
gefunden werden kann. Es ist wichtig, dass der Bindestrich als Erstes in der Zeichen-
klasse vorkommt, denn stünde er zwischen anderen Zeichen, würde er wie in ‹[0-9]› zu
einem Bereich führen. Man kann den Bindestrich auch in einer Zeichenklasse mit einem
Backslash maskieren. ‹[.\-z]› findet daher die gleichen Zeichen.
Quantoren schließlich ermöglichen es Ihnen, ein Token oder eine Gruppe zu wiederho-
len. ‹{3}› ist ein Quantor, der dafür sorgt, dass das vorherige Element genau drei Mal
wiederholt wird. Der reguläre Ausdruck ‹[0-9]{3}› entspricht daher ‹[0-9][0-9][0-9]›,
ist aber kürzer und hoffentlich leichter zu lesen. Ein (schon weiter oben erwähntes) Fra-
gezeichen ist ein besonderer Quantor, durch den das vorherige Element genau null oder
ein Mal wiederholt werden kann. Er lässt sich auch als ‹{0,1}› schreiben. Jeder Quantor,
durch den etwas auch null Mal wiederholt werden kann, sorgt im Endeffekt dafür, dass
das Element optional ist. Da hinter jedem Trennzeichen ein Fragezeichen steht, können
die Ziffern der Telefonnummer auch ohne Trennzeichen direkt hintereinanderstehen.
Beachten Sie, dass wir zwar von nordamerikanischen Telefonnummern reden, die Regex
aber präziser dafür gedacht ist, Telefonnummern nach dem North American Numbering
Plan (NANP) zu finden. Der NANP ist der Nummerierungsplan für die Länder, die
gemeinsam die Ländervorwahl „1“ nutzen. Dazu gehören die USA und seine Territorien,
Kanada, die Bermudas und 16 Karibikstaaten. Mexiko und die Staaten in Mittelamerika
gehören nicht dazu.

Variationen
Ungültige Telefonnummern eliminieren
Der reguläre Ausdruck passt so weit zu jeder zehnstelligen Nummer. Wenn Sie eine
erfolgreiche Suche aber auf Telefonnummern beschränken wollen, die nach dem North
American Numbering Plan gültig sind, müssen noch ein paar weitere Regeln beachtet
werden:
• Vorwahlen beginnen mit einer Ziffer von 2 bis 9, gefolgt von einer Ziffer von 0 bis 8.
Die dritte Ziffer kann beliebig sein.
• Die zweite dreistellige Gruppe, die auch als Central Office oder Exchange Code
bekannt ist, beginnt mit einer Ziffer zwischen 2 und 9, gefolgt von zwei weiteren
beliebigen Ziffern.
• Für die letzten vier Ziffern, auch bekannt als Station Code, gibt es keine Einschrän-
kungen.

236 | Kapitel 4: Validierung und Formatierung


Diese Regeln lassen sich leicht mit ein paar Zeichenklassen umsetzen:
^\(?([2-9][0-8][0-9])\)?[-.z]?([2-9][0-9]{2})[-.z]?([0-9]{4})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Neben den eben beschriebenen grundlegenden Regeln gibt es eine Reihe von reservier-
ten, nicht zugewiesenen und eingeschränkten Telefonnummern. Sofern Sie nicht unbe-
dingt so viele Telefonnummern wie möglich aussortieren müssen, sollten Sie es mit dem
Entfernen ungenutzter Nummern nicht zu weit treiben. Neue Vorwahlen, die den oben
aufgeführten Regeln entsprechen, werden immer wieder mal freigeschaltet, und selbst
wenn eine Telefonnummer gültig ist, muss das nicht unbedingt bedeuten, dass sie
geschaltet wurde oder aktuell genutzt wird.

Telefonnummern in Dokumenten finden


Durch zwei einfache Änderungen kann der vorherige reguläre Ausdruck auch Telefon-
nummern in längeren Texten finden:
\(?\b([0-9]{3})\)?[-.z]?([0-9]{3})[-.z]?([0-9]{4})\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Hier wurden die Zusicherungen ‹^› und ‹$›, mit denen der reguläre Ausdruck mit dem
Anfang und Ende des Texts verbunden wurde, entfernt. Stattdessen wurden Wortgren-
zen (‹\b›) ergänzt, mit denen sichergestellt wird, dass der gefundene Text für sich steht
und nicht Teil einer längeren Zahl oder eines Wortes ist.
Wie ‹^› und ‹$› ist ‹\b› eine Zusicherung, die eine Position anstelle von echtem Text fin-
det. Genauer gesagt, passt ‹\b› zu einer Position zwischen einem Wortzeichen und ent-
weder einem Nicht-Wortzeichen oder dem Anfang oder Ende des Texts. Dabei werden
Buchstaben, Ziffern und der Unterstrich als Wortzeichen angesehen (siehe Rezept 2.6).
Beachten Sie, dass die erste Wortgrenze erst nach der optionalen öffnenden Klammer
steht. Das ist wichtig, denn es gibt keine Wortgrenze, die man zwischen zwei Nicht-
Wortzeichen finden kann – wie zum Beispiel zwischen der öffnenden Klammer und
einem vorherigen Leerzeichen. Die erste Wortgrenze ist nur dann wichtig, wenn eine
Nummer ohne Klammern gefunden wird. Denn die Wortgrenze passt immer zwischen
der öffnenden Klammer und der ersten Ziffer einer Telefonnummer.

Eine führende „1“ zulassen


Sie können eine optionale führende „1“ für den Ländercode zulassen (durch den der
Bereich des North American Numbering Plan erreichbar ist), indem Sie die Ergänzungen
der folgenden Regex nutzen:
^(?:\+?1[-.z]?)?\(?([0-9]{3})\)?[-.z]?([0-9]{3})[-.z]?([0-9]{4})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

4.2 Nordamerikanische Telefonnummern validieren | 237


Neben den schon gezeigten Telefonnummerformaten passt dieser reguläre Ausdruck
auch zu Strings wie +1 (123) 456-7890 und 1-123-456-7890. Dabei wird eine nicht-einfan-
gende Gruppe genutzt, die die Syntax ‹(?:...)› besitzt. Wenn auf eine öffnende Klam-
mer ein Fragezeichen folgt, handelt es sich dabei nicht um einen Quantor, sondern damit
wird die Art der Gruppe festgelegt. Normale einfangende Gruppen zwingen die Regex-
Engine dazu, sich Rückwärtsreferenzen zu merken, daher ist es effizienter, nicht-einfan-
gende Gruppen zu verwenden, wenn der von einer Gruppe gefundene Text später nicht
referenziert werden muss. Ein anderer Grund für die Verwendung einer nicht-einfangen-
den Gruppe ist, die gleichen Ersetzungstexte zu verwenden wie in den vorherigen Bei-
spielen. Hätten wir eine einfangende Gruppe hinzugefügt, müssten wir im weiter oben
gezeigten Ersetzungstext $1 durch $2 ersetzen (und so weiter).
In dieser Regex wurde ‹(?:\+?1[-.z]?)?› hinzugefügt. Vor der „1“ steht in diesem Mus-
ter ein optionales Pluszeichen und dahinter eines von drei Trennzeichen (Bindestrich,
Punkt oder Leerzeichen). Die gesamte nicht-einfangende Gruppe ist zudem optional,
aber da die „1“ in der Gruppe notwendig ist, dürfen das vorhergehende Pluszeichen und
das Trennzeichen nicht erscheinen, wenn es keine „1“ gibt.

Telefonnummern mit sieben Ziffern zulassen


Um auch Telefonnummern zu finden, bei denen die Vorwahl weggelassen wurde,
umschließen Sie die erste Zifferngruppe zusammen mit seinen umgebenden Klammern
und dem folgenden Trennzeichen mit einer optionalen nicht-einfangenden Gruppe:
^(?:\(?([0-9]{3})\)?[-.z]?)?([0-9]{3})[-.z]?([0-9]{4})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Da die Vorwahl nun nicht mehr notwendigerweise Teil der Übereinstimmung ist, würde
ein einfaches Ersetzen durch «($1)z$2-$3» nun eventuell zu so etwas wie () 123-4567 füh-
ren. Um das zu umgehen, sollten Sie die Regex zusammen mit Code verwenden, der
prüft, ob in der ersten Gruppe überhaupt Text enthalten ist. Wenn nicht, muss der Erset-
zungstext dementsprechend angepasst werden.

Siehe auch
In Rezept 4.3 wird gezeigt, wie Sie internationale Telefonnummern überprüfen.
Der North American Numbering Plan (NANP) ist der Nummernplan für Telefonan-
schlüsse in den USA und seinen Territorien, in Kanada, auf den Bermudas und in 16 kari-
bischen Staaten. Unter http://www.nanpa.com erhalten Sie mehr Informationen dazu.

238 | Kapitel 4: Validierung und Formatierung


4.3 Internationale Telefonnummern überprüfen
Problem
Sie wollen internationale Telefonnummern überprüfen. Die Nummern sollen mit einem
Pluszeichen beginnen, auf das die Ländervorwahl und dann die Nummer innerhalb des
Landes folgt.

Lösung
Regulärer Ausdruck
^\+(?:[0-9]z?){6,14}[0-9]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

JavaScript
function validate (phone) {
var regex = /^\+(?:[0-9] ?){6,14}[0-9]$/;

if (regex.test(phone)) {
// Gültige internationale Telefonnummer
} else {
// Ungültige internationale Telefonnummer
}
}

Andere Programmiersprachen
In Rezept 3.5 finden Sie Informationen, wie dieser reguläre Ausdruck mit anderen Pro-
grammiersprachen implementiert werden kann.

Diskussion
Die Regeln und Richtlinien für die Ausgabe internationaler Telefonnummern unterschei-
den sich von Land zu Land, daher ist es schwierig, für solche Nummern eine sinnvolle
Validierung durchzuführen, wenn Sie sich nicht gerade an ein striktes Format halten.
Zum Glück gibt es eine einfache Standardnotation, die durch die ITU-T E.123 definiert
ist. Dabei gehört zu internationalen Telefonnummern ein führendes Pluszeichen (auch
als International Prefix Symbol bezeichnet), und als Trennzeichen sind nur Leerzeichen
zugelassen, um die Zifferngruppen zu unterteilen. Auch wenn das Tildezeichen (~)
innerhalb einer Telefonnummer auftauchen kann, um auf einen zusätzlichen Wählton
hinzuweisen, haben wir dieses nicht in den regulären Ausdruck integriert, da es sich eher
um ein prozedurales Element handelt (es wird also nicht mitgewählt) und auch nur sehr

4.3 Internationale Telefonnummern überprüfen | 239


selten zur Anwendung kommt. Aufgrund des internationalen Telefonnummernplans
(ITU-T E.164) können Telefonnummern mehr als 15 Ziffern enthalten. Die kürzeste
internationale Telefonnummer, die auch genutzt wird, besteht aus sieben Ziffern.
Mit all dem im Hinterkopf wollen wir uns den regulären Ausdruck anschauen, nachdem
wir ihn in seine Bestandteile zerlegt haben. Da diese Version im Freiform-Modus
geschrieben ist, wurden die literalen Leerzeichen durch ‹\x20› ersetzt:
^ # Sicherstellen, dass die Übereinstimmung am Anfang des Texts beginnt.
\+ # Ein literales Pluszeichen finden.
(?: # Gruppieren, aber nicht einfangen ...
[0-9] # Eine Ziffer finden.
\x20 # Ein Leerzeichen finden ...
? # null oder ein Mal.
) # Ende der nicht-einfangenden Gruppe.
{6,14} # Wiederholen der vorherigen Gruppe 6 bis 14 Mal.
[0-9] # Eine Ziffer finden.
$ # Sicherstellen, dass die Übereinstimmung am Ende des Texts endet.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Die Anker ‹^› und ‹$› am Anfang und am Ende des regulären Ausdrucks stellen sicher,
dass der gesamte Ausgangstext passen muss. Die nicht-einfangende Gruppe – umschlos-
sen von ‹(?:...)› – passt zu einer einzelnen Ziffer, gefolgt von einem optionalen Leer-
zeichen. Durch das Wiederholen dieser Gruppe mit einem Intervall-Quantor ‹{6,14}›
werden die minimale und die maximale Anzahl an Ziffern festgelegt, während gleichzei-
tig an beliebiger Stelle in der Nummer auch Leerzeichen stehen dürfen. Die zweite
Instanz der Zeichenklasse ‹[0-9]› vervollständigt die Regel für die Anzahl der Ziffern
(von 6 bis 14 auf 7 bis 15) und stellt sicher, dass die Telefonnummer nicht mit einem
Leerzeichen endet.

Variationen
Validieren von internationalen Telefonnummern im EPP-Format
^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck orientiert sich an der Notation für internationale Telefonnum-
mern, die durch das Extensible Provisioning Protocol (EPP) definiert ist. EPP ist ein recht
neues Protokoll (es wurde 2004 abgeschlossen) und dient dazu, die Kommunikation zwi-
schen Domain Name Registries und Registraren zu unterstützen. Es wird von einer wach-
senden Zahl von Domain Name Registries genutzt, so für .com, .info, .net, .org und .us.
Entscheidend dabei ist, dass internationale Telefonnummern im EPP-Stil zunehmend
verwendet und auch erkannt werden. Damit stellt es ein gutes alternatives Format für das
Speichern (und Validieren) internationaler Telefonnummern bereit.

240 | Kapitel 4: Validierung und Formatierung


Telefonnummern im EPP-Stil verwenden das Format +CCC.NNNNNNNNNNxEEEE, wobei C der
ein- bis dreistellige Ländercode ist, N aus bis zu 14 Ziffern bestehen kann und E eine
(optionale) Durchwahl ist. Das führende Pluszeichen und der Punkt, der auf den Länder-
code folgt, sind verpflichtend. Das literale „x“ ist nur dann notwendig, wenn eine Durch-
wahl angegeben wird.

Siehe auch
In Rezept 4.2 werden noch mehr Möglichkeiten für das Validieren nordamerikanischer
Telefonnummern vorgestellt.
Die ITU-T-Empfehlung E.123 (»Notation for national and international telephone
numbers, e-mail addresses and Web addresses«) kann unter http://www.itu.int/rec/
T-REC-E.123 heruntergeladen werden.
Die ITU-T-Empfehlung E.164 (»The international public telecommunication numbering
plan«) kann unter http://www.itu.int/rec/T-REC-E.164 heruntergeladen werden.
Nationale Nummernpläne lassen sich unter http://www.itu.int/ITU-T/inr/nnp herunterla-
den.
Der RFC 4933 definiert die Syntax und Semantik der EPP Contact Identifiers, zu denen
auch die internationalen Telefonnummern gehören. Sie können den RFC 4933 unter
http://tools.ietf.org/html/rfc4933 herunterladen.

4.4 Klassische Datumsformate validieren


Problem
Sie wollen Datumswerte in den klassischen Formaten mm/dd/yy, mm/dd/yyyy,
dd.mm.yy und dd.mm.yyyy validieren. Dafür wollen Sie eine einfache Regex verwenden,
die nur prüft, ob ein Wert wie ein Datum aussieht, ohne aber ungültige Datumswerte wie
den 31. Februar zu erkennen.

Lösung
Finden eines dieser Datumsformate, wobei führende Nullen weggelassen werden dürfen:
^[0-3]?[0-9][/.][0-3]?[0-9][/.](?:[0-9]{2})?[0-9]{2}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden eines dieser Datumsformate, wobei führende Nullen vorhanden sein müssen:
^[0-3][0-9][/.][0-3][0-9][/.](?:[0-9][0-9])?[0-9][0-9]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

4.4 Klassische Datumsformate validieren | 241


Finden von m/d/yy und mm/dd/yyyy, wobei jede Kombination aus einer oder zwei Zif-
fern für Tag und Monat zulässig sind und zwei oder vier Ziffern für das Jahr:
^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])/(?:[0-9]{2})?[0-9]{2}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden von mm/dd/yyyy, führende Nullen müssen vorhanden sein:
^(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden von d.m.yy und dd.mm.yyyy, wobei jede Kombination aus einer oder zwei Zif-
fern für Tag und Monat zulässig sind und zwei oder vier Ziffern für das Jahr:
^(3[01]|[12][0-9]|0?[1-9])\.(1[0-2]|0?[1-9])\.(?:[0-9]{2})?[0-9]{2}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden von dd.mm.yyyy, führende Nullen müssen vorhanden sein:
^(3[01]|[12][0-9]|0[1-9])\.(1[0-2]|0[1-9])\.[0-9]{4}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden eines dieser Datumsformate mit besserer Genauigkeit, wobei führende Nullen
weggelassen werden dürfen:
^(?:(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])/|
(3[01]|[12][0-9]|0?[1-9])\.(1[0-2]|0?[1-9])\.)(?:[0-9]{2})?[0-9]{2}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden eines dieser Datumsformate mit besserer Genauigkeit, führende Nullen müssen
vorhanden sein:
^(?:(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/|
(3[01]|[12][0-9]|0[1-9])\.(1[0-2]|0[1-9])\.)[0-9]{4}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Im Freiform-Modus sind die letzten beiden Regexes etwas besser lesbar:
^(?:
# m/d/ oder mm/dd/
(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])/
|
# d.m. oder dd.mm.

242 | Kapitel 4: Validierung und Formatierung


(3[01]|[12][0-9]|0?[1-9])\.(1[0-2]|0?[1-9])\.
)
# yy oder yyyy
(?:[0-9]{2})?[0-9]{2}$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^(?:
# mm/dd/
(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])
|
# dd.mm.
(3[01]|[12][0-9]|0[1-9])\.(1[0-2]|0[1-9])\.
)
# yyyy
[0-9]{4}$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion
Vielleicht gehen Sie davon aus, dass etwas konzeptionell so Einfaches wie ein Datums-
wert für einen regulären Ausdruck ein leichtes Spiel wäre. Aber dem ist nicht so, und
zwar aus zwei Gründen. Da Datumswerte tagtäglich verwendet werden, gehen Menschen
mit ihnen sehr salopp um. 4/1 ist für einen Amerikaner vielleicht der 1. April. Für jemand
anderen ist es dagegen eventuell der erste Arbeitstag im Jahr, wenn Neujahr auf einen
Freitag fällt. Die gezeigten Lösungen passen zu einigen der am häufigsten genutzten
Datumsformaten.
Das andere Problem ist, dass reguläre Ausdrücke nicht direkt mit Zahlen arbeiten. Sie
können einen regulären Ausdruck nicht anweisen, zum Beispiel „eine Zahl zwischen 1
und 31 zu finden“. Reguläre Ausdrücke gehen Zeichen für Zeichen vor. Mit
‹3[01]|[12][0-9]|0?[1-9]› finden wir eine 3, gefolgt von einer 0 oder 1, oder eine 1 oder
2, gefolgt von einer Ziffer, oder eine optionale 0, gefolgt von einer Ziffer zwischen 1 und
9. Bei Zeichenklassen können wir für einzelne Ziffern Bereiche nutzen, wie zum Beispiel
‹[1-9]›. Das liegt daran, dass die Zeichen für die Ziffern 0 bis 9 in den Zeichentabellen
von ASCII und Unicode direkt aufeinanderfolgen. In Kapitel 6 finden Sie mehr Details
über das Finden aller möglichen Arten von Zahlen mit regulären Ausdrücken.
Aus diesen Gründen müssen Sie sich entscheiden, wie einfach oder wie genau Ihr regulä-
rer Ausdruck sein soll. Wenn Sie schon wissen, dass der Ausgangstext keine ungültigen
Datumswerte enthält, können Sie eine einfache Regex wie ‹\d{2}/\d{2}/\d{4}› oder
‹\d{2}\.\d{2}\.\d{4}› nutzen. Damit wird zwar auch so etwas wie 99/99/9999 gefunden,
aber das ist unwichtig, weil Sie ja wissen, dass solche Werte im Ausgangstext nicht vor-
kommen. Sie können diese einfache Regex schnell eingeben, und sie wird auch schnell
ausgewertet.

4.4 Klassische Datumsformate validieren | 243


Die ersten beiden Lösungen für dieses Rezept sind ebenfalls schnell und einfach, und sie
passen auch zu ungültigen Datumswerten wie 0/0/00 und 31/31/2008. Dabei werden für
die Trennzeichen und für die Ziffern Zeichenklassen genutzt (siehe Rezept 2.3) sowie das
Fragezeichen (siehe Rezept 2.12), um bestimmte Ziffern optional zu machen. Mit ‹(?:[0-
9]{2})?[0-9]{2}› kann das Jahr aus zwei oder vier Ziffern bestehen. ‹[0-9]{2}› passt
genau zu zwei Ziffern, ‹(?:[0-9]{2})?› passt zu null oder zwei Ziffern. Die nicht-einfan-
gende Gruppe (siehe Rezept 2.9) ist hier erforderlich, da das Fragezeichen auf die Kombi-
nation aus Zeichenklasse und Quantor ‹{2}› angewandt werden muss. ‹[0-9]{2}?› passt
wie ‹[0-9]{2}› genau zu zwei Ziffern. Ohne die Gruppe würde das Fragezeichen dafür
sorgen, dass der Quantor genügsam wird, was hier keinen Effekt hat, da ‹{2}› nicht mehr
oder weniger als zwei Mal zu einer Wiederholung führen kann.
Die Lösungen 3 bis 6 grenzen den Monat auf Zahlen zwischen 1 und 12 und den Tag auf
Werte zwischen 1 und 31 ein. Mithilfe der Alternation (siehe Rezept 2.8) innerhalb einer
Gruppe finden wir einen bestimmten Bereich von zweistelligen Zahlen. Hier verwenden
wir einfangende Gruppen, weil Sie Tag und Monat vermutlich sowieso auslesen wollen.
Die letzten beiden Lösungen sind ein bisschen komplexer, daher zeigen wir sie auch im
Freiform-Modus. Der einzige Unterschied zwischen den beiden Formen ist die Lesbar-
keit. In JavaScript wird allerdings kein Freiform-Modus unterstützt. Diese letzten Lösun-
gen ermöglichen alle Datumsformate – so wie die ersten beiden Lösungen. Aber hier wird
durch eine zusätzliche Alternation dafür gesorgt, dass Datumswerte auf 12/31 und 31.12.
begrenzt werden und somit keine ungültigen Monate (31/31) möglich sind.

Variationen
Wenn Sie in einem umfangreicheren Text nach einem Datumswert suchen wollen, statt
eine Benutzereingabe als Ganzes zu prüfen, können Sie die Anker ‹^› und ‹$› nicht ver-
wenden. Sie einfach zu entfernen, wäre aber auch nicht die richtige Lösung. Damit würde
zum Beispiel in 9912/12/200199 der Wert 12/12/2001 gefunden werden. Statt die Regex-
Übereinstimmung mit dem Anfang und dem Ende des Texts zu verankern, müssen Sie
festlegen, dass das Datum kein Teil einer längeren Ziffernfolge sein darf.
Das lässt sich ganz einfach mit Wortgrenzen erreichen. In regulären Ausdrücken werden
Ziffern als Wortzeichen behandelt. Ersetzen Sie sowohl ‹^› als auch ‹$› durch ‹\b›. Ein
Beispiel:
\b(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Siehe auch
Rezepte 4.5, 4.6 und 4.7.

244 | Kapitel 4: Validierung und Formatierung


4.5 Klassische Datumsformate exakt validieren
Problem
Sie wollen Datumswerte in den klassischen Formaten mm/dd/yy, mm/dd/yyyy,
dd.mm.yy und dd.mm.yyyy validieren. Dabei wollen Sie aber ungültige Datumswerte
wie den 31. Februar ausschließen.

Lösung
C#
Monat vor Tag:
DateTime foundDate;
Match matchResult = Regex.Match(SubjectString,
"^(?<month>[0-3]?[0-9])/(?<day>[0-3]?[0-9])/" +
"(?<year>(?:[0-9]{2})?[0-9]{2})$");
if (matchResult.Success) {
int year = int.Parse(matchResult.Groups["year"].Value);
if (year < 50) year += 2000;
else if (year < 100) year += 1900;
try {
foundDate = new DateTime(year,
int.Parse(matchResult.Groups["month"].Value),
int.Parse(matchResult.Groups["day"].Value));
} catch {
// Ungültiges Datum
}
}

Tag vor Monat:


DateTime foundDate;
Match matchResult = Regex.Match(SubjectString,
"^(?<day>[0-3]?[0-9])\.(?<month>[0-3]?[0-9])\." +
"(?<year>(?:[0-9]{2})?[0-9]{2})$");
if (matchResult.Success) {
int year = int.Parse(matchResult.Groups["year"].Value);
if (year < 50) year += 2000;
else if (year < 100) year += 1900;
try {
foundDate = new DateTime(year,
int.Parse(matchResult.Groups["month"].Value),
int.Parse(matchResult.Groups["day"].Value));
} catch {
// Ungültiges Datum
}
}

4.5 Klassische Datumsformate exakt validieren | 245


Perl
Monat vor Tag:
@daysinmonth = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$validdate = 0;
if ($subject =~ m!^([0-3]?[0-9])/([0-3]?[0-9])/((?:[0-9]{2})?[0-9]{2})$!) {
$month = $1;
$day = $2;
$year = $3;
$year += 2000 if $year < 50;
$year += 1900 if $year < 100;
if ($month == 2 && $year % 4 == 0 && ($year % 100 != 0 ||
$year % 400 == 0)) {
$validdate = 1 if $day >= 1 && $day <= 29;
} elsif ($month >= 1 && $month <= 12) {
$validdate = 1 if $day >= 1 && $day <= $daysinmonth[$month-1];
}
}
Tag vor Monat:
@daysinmonth = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$validdate = 0;
if ($subject =~ m!^([0-3]?[0-9])\.([0-3]?[0-9])\.((?:[0-9]{2})?[0-9]{2})$!) {
$day = $1;
$month = $2;
$year = $3;
$year += 2000 if $year < 50;
$year += 1900 if $year < 100;
if ($month == 2 && $year % 4 == 0 && ($year % 100 != 0 ||
$year % 400 == 0)) {
$validdate = 1 if $day >= 1 && $day <= 29;
} elsif ($month >= 1 && $month <= 12) {
$validdate = 1 if $day >= 1 && $day <= $daysinmonth[$month-1];
}
}

Lösung nur mit einem regulären Ausdruck:


Monat vor Tag:
^(?:
# Februar (jedes Jahr 29 Tage)
(?<month>0?2)/(?<day>[12][0-9]|0?[1-9])
|
# Monate mit 30 Tagen
(?<month>0?[469]|11)/(?<day>30|[12][0-9]|0?[1-9])
|
# Monate mit 31 Tagen
(?<month>0?[13578]|1[02])/(?<day>3[01]|[12][0-9]|0?[1-9])
)
# Jahr
/(?<year>(?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Freiform
Regex-Varianten: .NET

246 | Kapitel 4: Validierung und Formatierung


^(?:
# Februar (jedes Jahr 29 Tage)
(0?2)/([12][0-9]|0?[1-9])
|
# Monate mit 30 Tagen
(0?[469]|11)/(30|[12][0-9]|0?[1-9])
|
# Monate mit 31 Tagen
(0?[13578]|1[02])/(3[01]|[12][0-9]|0?[1-9])
)
# Jahr
/((?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^(?:(0?2)/([12][0-9]|0?[1-9])|(0?[469]|11)/(30|[12][0-9]|0?[1-9])|
(0?[13578]|1[02])/(3[01]|[12][0-9]|0?[1-9]))/((?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Tag vor Monat:
^(?:
# Februar (jedes Jahr 29 Tage)
(?<day>[12][0-9]|0?[1-9])\.(?<month>0?2)
|
# Monate mit 30 Tagen
(?<day>30|[12][0-9]|0?[1-9])\.(?<month>0?[469]|11)
|
# Monate mit 31 Tagen
(?<day>3[01]|[12][0-9]|0?[1-9])\.(?<month>0?[13578]|1[02])
)
# Jahr
\.(?<year>(?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Freiform
Regex-Varianten: .NET
^(?:
# Februar (jedes Jahr 29 Tage)
([12][0-9]|0?[1-9])\.(0?2)
|
# Monate mit 30 Tagen
(30|[12][0-9]|0?[1-9])\.([469]|11)
|
# Monate mit 31 Tagen
(3[01]|[12][0-9]|0?[1-9])\.(0?[13578]|1[02])
)
# Jahr
\.((?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

4.5 Klassische Datumsformate exakt validieren | 247


^(?:([12][0-9]|0?[1-9])\.(0?2)|(30|[12][0-9]|0?[1-9])\.([469]|11)|
(3[01]|[12][0-9]|0?[1-9])\.(0?[13578]|1[02]))\.((?:[0-9]{2})?[0-9]{2})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Es gibt im Prinzip zwei Wege, Datumswerte mit einem regulären Ausdruck exakt zu vali-
dieren. Der eine ist, eine einfache Regex zu verwenden, die eigentlich nur die Zahlen-
gruppen einfängt, die wie eine Kombination aus Monat/Tag/Jahr beziehungsweise
Tag.Monat.Jahr aussehen, und dann mit prozeduralem Code zu prüfen, ob das Datum
korrekt ist. Ich habe die erste Regex aus dem vorhergehenden Rezept verwendet, die eine
beliebige Zahl zwischen 0 und 39 für Tag und Monat zuließ. Damit lässt sich das Format
leicht zwischen mm/dd/yy und dd.mm.yy wechseln, indem man sich aussucht, welche
Gruppe den Tag und welche den Monat einfängt (abgesehen vom unterschiedlichen
Trennzeichen).
Der Hauptvorteil dieser Methode ist, dass Sie einfach zusätzliche Einschränkungen
ergänzen können, wie zum Beispiel ein Einschränken auf bestimmte Zeiträume. Viele
Programmiersprachen stellen eine ganze Reihe von Möglichkeiten bereit, mit Datums-
werten zu arbeiten. Die C#-Lösung verwendet die .NET-Struktur DateTime, um zu prü-
fen, ob das Datum gültig ist, und es in einem sinnvollen Format zurückzugeben.
Der andere Weg ist, alles mit einem regulären Ausdruck abzudecken. Das lässt sich dann
erreichen, wenn wir uns die Freiheit nehmen, jedes Jahr als Schaltjahr zu betrachten. Wir
können die gleiche Technik für die Alternativen verwenden, die wir schon im letzten
Rezept genutzt haben.
Mit einem einzelnen regulären Ausdruck haben wir aber nun das Problem, dass Tag und
Monat nicht mehr übersichtlich in einer einfangenden Gruppe erfasst werden. Jetzt gibt
es drei einfangende Gruppen für den Monat und drei für den Tag. Passt die Regex auf ein
Datum, enthalten nur drei der sieben Gruppen in der Regex etwas. Handelt es sich um
ein Datum im Februar, fangen die Gruppen 1 und 2 den Monat und den Tag (bezie-
hungsweise umgekehrt). Hat der Monat 30 Tage, enthalten die Gruppen 3 und 4 diese
Werte. Bei Monaten mit 31 Tagen muss man sich die Gruppen 5 und 6 anschauen. In
Gruppe 7 ist immer das Jahr zu finden.
In dieser Situation hilft uns nur die .NET-Variante. Hier können verschiedene benannte
einfangende Gruppen (siehe Rezept 2.11) den gleichen Namen tragen und den gleichen
Speicherplatz für ihren Wert verwenden. Nutzen Sie die .NET-Lösung mit benannten
Captures, können Sie den Text, der durch die Gruppen „month“ und „day“ gefunden
wurden, einfach auslesen, ohne sich darum Gedanken machen zu müssen, wie viele Tage
der Monat hat. Alle anderen in diesem Buch behandelten Varianten lassen es nicht zu,
dass zwei Gruppen den gleichen Namen tragen, oder sie geben nur den Text zurück, der
von der letzten einfangenden Gruppe mit einem bestimmten Namen gefunden wurde.
Bei diesen Varianten kann man nur mit nummerierten Captures arbeiten.

248 | Kapitel 4: Validierung und Formatierung


Die Lösung, die mit lediglich einer Regex arbeitet, ist nur dann interessant, wenn Sie
auch nur eine Regex verwenden können – beispielsweise in dem Fall, dass eine Anwen-
dung nur ein Eingabefeld für eine Regex anbietet. Beim Programmieren wird alles viel
einfacher, wenn Sie ein bisschen Code darum herum bauen. Das ist vor allem dann hilf-
reich, wenn Sie die Datumswerte später noch verarbeiten wollen. Hier finden Sie eine
reine Regex-Lösung, die ein Datum zwischen dem 2. Mai 2007 und dem 29. August 2008
im Format d.m.yy oder dd.mm.yyyy findet:
# 2. Mai 2007 bis 29. August 2008
^(?:
# 2. Mai 2007 bis 31. Dezember 2007
(?:
# 2. Mai bis 31. Mai
(?<day>3[01]|[12][0-9]|0?[2-9])\.(?<month>0?5)\.(?<year>2007)
|
# 1. Juni bis 31. Dezember
(?:
# Monate mit 30 Tagen
(?<day>30|[12][0-9]|0?[1-9])\.(?<month>0?[69]|11)
|
# Monate mit 31 Tagen
(?<day>3[01]|[12][0-9]|0?[1-9])\.(?<month>0?[78]|1[02])
)
\.(?<year>2007)
)
|
# 1. Januar 2008 bis 29. August 2008
(?:
# 1. August bis 29. August
(?<day>[12][0-9]|0?[1-9])\.(?<month>0?8)\.(?<year>2008)
|
# 1. Januar bis 31. Juli
(?:
# Februar
(?<day>[12][0-9]|0?[1-9])\.(?<month>0?2)
|
# Monate mit 30 Tagen
(?<day>30|[12][0-9]|0?[1-9])\.(?<month>0?[46])
|
# Monate mit 31 Tagen
(?<day>3[01]|[12][0-9]|0?[1-9])\.(?<month>0?[1357])
)
\.(?<year>2008)
)
)$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Siehe auch
Rezepte 4.4, 4.6 und 4.7.

4.5 Klassische Datumsformate exakt validieren | 249


4.6 Klassische Zeitformate validieren
Problem
Sie wollen Zeitwerte in klassischen Formaten validieren. Dazu gehören hh:mm und
hh:mm:ss – sowohl im 12-Stunden- als auch im 24-Stunden-Format.

Lösung
Stunden und Minuten, 12 Stunden:
^(1[0-2]|0?[1-9]):([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Stunden und Minuten, 24 Stunden:
^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Stunden, Minuten und Sekunden, 12 Stunden:
^(1[0-2]|0?[1-9]):([0-5]?[0-9]):([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Stunden, Minuten und Sekunden, 24 Stunden:
^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Die Fragezeichen in allen angegebenen regulären Ausdrücken sorgen dafür, dass füh-
rende Nullen optional werden. Sollen sie immer angegeben werden, entfernen Sie die
Fragezeichen.

Diskussion
Das Validieren von Uhrzeiten ist deutlich einfacher als das Validieren von Datumswer-
ten. Jede Stunde hat 60 Minuten und jede Minute 60 Sekunden. Somit brauchen wir
keine komplizierten Alternationen in der Regex. Für Minuten und Sekunden sind sogar
überhaupt keine Alternationen notwendig. ‹[0-5]?[0-9]› passt zu einer Ziffer zwischen 0
und 5, gefolgt von einer Ziffer zwischen 0 und 9. Damit werden alle Zahlen zwischen 0
und 59 gefunden. Durch das Fragezeichen nach der ersten Zeichenklasse wird diese opti-
onal. So wird auch eine einzelne Ziffer zwischen 0 und 9 als gültige Minute oder Sekunde

250 | Kapitel 4: Validierung und Formatierung


erkannt. Entfernen Sie das Fragezeichen, wenn die ersten zehn Minuten und Sekunden
als 00 bis 09 geschrieben werden sollen. In Rezept 2.3 und Rezept 2.12 finden Sie Details
zu Zeichenklassen und Quantoren.
Bei den Stunden brauchen wir dann aber eine Alternation (siehe Rezept 2.8). Für die
zweite Ziffer sind abhängig von der ersten Ziffer unterschiedliche Bereiche notwendig.
Bei einer 12-Stunden-Uhr sind für die zweite Ziffer alle 10 Ziffern erlaubt, wenn die erste
Ziffer eine 0 ist. Ist die erste Ziffer dagegen eine 1, muss die zweite Ziffer entweder 0, 1
oder 2 sein. Bei einem regulären Ausdruck schreiben wir das als ‹1[0-2]|0?[1-9]›. Bei
einer 24-Stunden-Uhr sind alle zehn Ziffern für die zweite Ziffer erlaubt, wenn die erste
Ziffer 0 oder 1 ist. Ist diese aber 2, muss die zweite Ziffer zwischen 0 und 3 liegen. In
Regex-Syntax lässt sich das als ‹2[0-3]|[01]?[0-9]› darstellen. Auch hier dient das Frage-
zeichen wieder dazu, dass die ersten zehn Stunden als einzelne Ziffer geschrieben werden
dürfen. Wenn Sie es entfernen, müssen immer zwei Ziffern angegeben werden.
Wir haben die Teile der Regex, mit der die Stunden, Minuten und Sekunden gefunden
werden, mit Klammern versehen. Dadurch ist es einfach, die Ziffern ohne die Trennzei-
chen auszulesen. In Rezept 2.9 ist erklärt, wie Klammern für einfangende Gruppen
genutzt werden. Und in Rezept 3.9 wird beschrieben, wie Sie den von solchen einfangen-
den Gruppen gefundenen Text mithilfe von prozeduralem Code auslesen können.
Die Klammern um den Stundenteil sorgen dafür, dass die zwei Alternativen für die Stun-
den zusammengehalten werden. Entfernen Sie die Klammern, wird die Regex nicht mehr
richtig funktionieren. Entfernen Sie die Klammern um die Minuten und Sekunden, hat
das keine Auswirkungen, außer dass Sie dann die Ziffern nicht mehr einzeln auslesen
können.

Variationen
Wenn Sie in längeren Texten nach Uhrzeiten suchen wollen, statt zu prüfen, ob eine Ein-
gabe nur aus einer Zeitangabe besteht, können Sie die Anker ‹^› und ‹$› nicht nutzen. Es
reicht aber auch nicht aus, die Anker einfach zu entfernen. Damit würden Regexes für
Stunden und Minuten den Wert 12:12 innerhalb von 9912:1299 finden. Statt den Anfang
und das Ende des Texts mit Anfang und Ende der Regex zu verknüpfen, müssen Sie fest-
legen, dass die Uhrzeit kein Teil einer längeren Folge von Ziffern sein kann.
Das lässt sich leicht mit einem Paar Wortgrenzen erreichen. In regulären Ausdrücken
werden Ziffern als Wortzeichen behandelt. Ersetzen Sie also sowohl ‹^› als auch ‹$›
durch ‹\b›, beispielsweise:
\b(2[0-3]|[01]?[0-9]):([0-5]?[0-9])\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Wortgrenzen verhindern nicht alles – nur Buchstaben, Ziffern und den Unterstrich. Die
hier gezeigte Regex passt zu Stunden und Minuten auf einer 24-Stunden-Uhr, findet aber
auch 16:08 im Text Es ist jetzt genau 16:08:42. Das Leerzeichen ist kein Wortbuch-

4.6 Klassische Zeitformate validieren | 251


stabe, die 1 aber schon, daher passt die Wortgrenze dazwischen. Die 8 ist ein Wortzei-
chen, der Doppelpunkt aber nicht, also passt ‹\b› auch hier zwischen.
Wenn Sie sowohl Doppelpunkte als auch Wortzeichen verhindern wollen, müssen Sie
ein Lookaround einsetzen (siehe Rezept 2.16). Die folgende Regex findet den Uhrzeit-
Teil von Es ist jetzt genau 16:08:42 nicht. Allerdings funktioniert sie nur mit Varian-
ten, die Lookbehinds zulassen:
(?<![:\w])(2[0-3]|[01]?[0-9]):([0-5]?[0-9])(?![:\w])
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby 1.9

Siehe auch
Rezepte 4.4, 4.5 und 4.7.

4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren


Problem
Sie wollen Datums- und/oder Uhrzeitwerte im offiziellen Format ISO 8601 finden. Dieses
Format ist die Grundlage vieler standardisierter Datums- und Zeitformate. So basieren
zum Beispiel in XML Schema die eingebauten Typen date, time und dateTime auf ISO
8601.

Lösung
Die folgende Regex findet einen Kalendermonat, zum Beispiel 2008-08. Der Bindestrich
ist dabei verpflichtend:
^([0-9]{4})-(1[0-2]|0[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>[0-9]{4})-(?<month>1[0-2]|0[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
^(?P<year>[0-9]{4})-(?P<month>1[0-2]|0[1-9])$
Regex-Optionen: Keine
Regex-Varianten: PCRE, Python
Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex erlaubt
allerdings YYYY-MMDD und YYYYMM-DD, was nicht ISO 8601 entspricht:
^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[0-1]|0[1-9]|[1-2][0-9])$

252 | Kapitel 4: Validierung und Formatierung


Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>[0-9]{4})-?(?<month>1[0-2]|0[1-9])-?
(?<day>3[0-1]|0[1-9]|[1-2][0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex nutzt eine
Bedingung, um YYYY-MMDD und YYYYMM-DD auszuschließen. Es gibt eine zusätzli-
che einfangende Gruppe für den ersten Bindestrich:
^([0-9]{4})(-)?(1[0-2]|0[1-9])(?(2)-)(3[0-1]|0[1-9]|[1-2][0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE, Perl, Python
Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex nutzt eine
Alternation, um YYYY-MMDD und YYYYMM-DD auszuschließen. Es gibt zwei einfan-
gende Gruppen für den Monat:
^([0-9]{4})(?:(1[0-2]|0[1-9])|-?(1[0-2]|0[1-9])-?)
(3[0-1]|0[1-9]|[1-2][0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Kalenderwoche, zum Beispiel 2008-W35. Der Bindestrich ist optional:
^([0-9]{4})-?W(5[0-3]|[1-4][0-9]|0[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>[0-9]{4})-?W(?<week>5[0-3]|[1-4][0-9]|0[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Tag einer Woche, zum Beispiel 2008-W35-6. Die Bindestriche sind optional:
^([0-9]{4})-?W(5[0-3]|[1-4][0-9]|0[1-9])-?([1-7])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>[0-9]{4})-?W(?<week>5[0-3]|[1-4][0-9]|0[1-9])-?(?<day>[1-7])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Tag eines Jahres, zum Beispiel 2008-243. Der Bindestrich ist optional:
^([0-9]{4})-?(36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren | 253


^(?<year>[0-9]{4})-?
(?<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Stunden und Minuten, zum Beispiel 17:21. Der Doppelpunkt ist optional:
^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<hour>2[0-3]|[01]?[0-9]):?(?<minute>[0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Stunden, Minuten und Sekunden, zum Beispiel 17:21:59. Die Doppelpunkte sind optio-
nal:
^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9]):?([0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<hour>2[0-3]|[01]?[0-9]):?(?<minute>[0-5]?[0-9]):?
(?<second>[0-5]?[0-9])$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Zeitzonenangabe, zum Beispiel Z, +07 oder +07:00. Der Doppelpunkt und die Minuten
sind optional:
^(Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Stunden, Minuten und Sekunden mit Zeitzonenangabe, zum Beispiel 17:21:59+07:00.
Alle Doppelpunkte sind optional. Die Minuten in der Zeitzonenangabe sind ebenfalls
optional:
^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9]):?([0-5]?[0-9])
(Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<hour>2[0-3]|[01]?[0-9]):?(?<minute>[0-5]?[0-9]):?(?<sec>[0-5]?[0-9])
(?<timezone>Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

254 | Kapitel 4: Validierung und Formatierung


Datum mit optionaler Zeitzone, zum Beispiel 2008-08-30 oder 2008-08-30+07:00. Binde-
striche sind erforderlich. Das ist der XML Schema-Typ date:
^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])
(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?<month>1[0-2]|0[1-9])-
(?<day>3[0-1]|0[1-9]|[1-2][0-9])
(?<timezone>Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Uhrzeit mit optionalen Sekundenbruchteilen und Zeitzone, zum Beispiel 01:45:36 oder
01:45:36.123+07:00. Das ist der XML Schema-Typ time:
^(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?
(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<hour>2[0-3]|[0-1][0-9]):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9])
(?<ms>\.[0-9]+)?(?<timezone>Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9
Datum und Uhrzeit mit optionalen Sekundenbruchteilen und Zeitzone, zum Beispiel
2008-08-30T01:45:36 oder 2008-08-30T01:45:36.123Z. Das ist der XML Schema-Typ
dateTime:
^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])
T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?
(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
^(?<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?<month>1[0-2]|0[1-9])-
(?<day>3[0-1]|0[1-9]|[1-2][0-9])T(?<hour>2[0-3]|[0-1][0-9]):
(?<minute>[0-5][0-9]):(?<second>[0-5][0-9])(?<ms>\.[0-9]+)?
(?<timezone>Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

Diskussion
Die ISO 8601 definiert eine große Anzahl von Datums- und Zeitformaten. Die hier vorge-
stellten regulären Ausdrücke kümmern sich um die gebräuchlichsten Formate, aber die
meisten Systeme, die ISO 8601 verwenden, greifen nur auf eine Untermenge zurück. So
sind zum Beispiel bei Datums- und Zeitwerten in XML Schema die Bindestriche und

4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren | 255


Doppelpunkte nicht optional. Um diese Zeichen in den regulären Ausdrücken einzufor-
dern, entfernen Sie einfach die Fragezeichen hinter ihnen. Passen Sie aber auf nicht-ein-
fangende Gruppen auf, die die Syntax ‹(?:Gruppe)› verwenden. Wenn ein Fragezeichen
und ein Doppelpunkt auf eine öffnende Klammer folgen, sind diese drei Zeichen der
Anfang einer nicht-einfangenden Gruppe.
Bei den regulären Ausdrücken sind die einzelnen Bindestriche und Doppelpunkte optio-
nal, was nicht ganz ISO 8601 entspricht. So ist zum Beispiel 1733:26 nach ISO 8601 keine
gültige Zeit, sie wird aber von den Zeit-Regexes akzeptiert. Wenn alle Bindestriche und
Doppelpunkte gleichzeitig vorhanden oder eben nicht vorhanden sein sollen, wird Ihre
Regex ein ganzes Stück komplexer. Wir haben das als Beispiel für die Datums-Regex
gezeigt, aber im Alltag sind die Trennzeichen im Allgemeinen entweder auf jeden Fall
erforderlich (wie bei XML Schema) oder immer verboten und nicht optional.
Wir haben um alle Zahlenelemente der Regex Klammern gelegt. Damit ist es einfach, die
Werte für Jahr, Monat, Tag, Stunden, Minuten, Sekunden und Zeitzonen auszulesen. In
Rezept 2.9 wird beschrieben, wie Klammern einfangende Gruppen definieren. Rezept 3.9
zeigt Ihnen, wie Sie den von diesen einfangenden Gruppen gefundenen Text mithilfe von
prozeduralem Code auslesen können.
Bei den meisten Regexes haben wir auch eine Alternative mit benannten Captures prä-
sentiert. Manche dieser Datums- und Zeitformate sind Ihnen oder Ihren Kollegen viel-
leicht unbekannt. Benannte Captures erleichtern das Verstehen der Regex. .NET, PCRE
7, Perl 5.10 und Ruby 1.9 unterstützen die Syntax ‹(?‹Name›Gruppe)›. Alle Versionen von
PCRE und Python, die in diesem Buch behandelt werden, bieten auch die alternative
Syntax ‹(?P‹Name›Gruppe)› an, in der ein zusätzliches ‹P› enthalten ist. In Rezept 2.11
und Rezept 3.9 finden Sie die Details dazu.
Die Zahlenbereiche in allen Regexes sind sehr strikt. So ist zum Beispiel der Kalendertag
auf Werte zwischen 01 und 31 eingeschränkt. Sie werden nie einen Tag 32 oder einen
Monat 13 erhalten. Allerdings versucht keine der vorgestellten Regexes, ungültige Kom-
binationen aus Tag und Monat auszuschließen, wie zum Beispiel den 31. Februar. In
Rezept 4.5 beschreiben wir, wie Sie dieses Problem angehen können.
Auch wenn manche dieser Regexes ziemlich lang sind, gibt es in ihnen dennoch keine
exotischen Verrenkungen, und alle nutzen die gleichen Techniken, die in Rezept 4.4 und
Rezept 4.6 erläutert wurden.

Siehe auch
Rezepte 4.4, 4.5, 4.6.

256 | Kapitel 4: Validierung und Formatierung


4.8 Eingabe auf alphanumerische Zeichen beschränken
Problem
Bei Ihrer Anwendung soll der Nutzer bei einer Eingabe nur alphanumerische Zeichen aus
dem englischen Alphabet eingeben dürfen.

Lösung
Mit den Ihnen zur Verfügung stehenden regulären Ausdrücken ist die Lösung ganz ein-
fach. Eine Zeichenklasse kann den erlaubten Bereich mit Zeichen festlegen. Mit einem
Quantor, der die Zeichenklasse ein Mal oder mehrfach zulässt, und Ankern, die die
Übereinstimmung mit dem Anfang und dem Ende des Strings verbinden, sind Sie schon
fertig.

Regulärer Ausdruck
^[A-Z0-9]+$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ruby
if subject =~ /^[A-Z0-9]+$/i
puts "Text ist alphanumerisch"
else
puts "Text ist nicht alphanumerisch"
end

Andere Programmiersprachen
In den Rezepten 3.4 und 3.5 finden Sie Informationen über das Implementieren dieses
regulären Ausdrucks in anderen Programmiersprachen.

Diskussion
Lassen Sie uns die vier Teile dieses regulären Ausdrucks nacheinander anschauen:
^ # Sicherstellen, dass die Übereinstimmung am Anfang des Texts beginnt.
[A-Z0-9] # Ein Zeichen zwischen "A" und "Z" oder zwischen "0" und "9" finden...
+ # einmal bis unbegrenzt oft.
$ # Sicherstellen, dass die Übereinstimmung am Ende des Texts endet.
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform-Modus
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

4.8 Eingabe auf alphanumerische Zeichen beschränken | 257


Die Anker ‹^› und ‹$› am Anfang und Ende des regulären Ausdrucks sorgen dafür, dass
die gesamte Eingabe überprüft wird. Ohne sie könnte die Regex auch Teile eines länge-
ren Strings finden, sodass doch ungültige Zeichen übersehen werden. Durch den Quan-
tor ‹+› kann das vorherige Element einmal oder häufiger vorkommen. Wenn Ihre Regex
auch einen vollständig leeren String erkennen soll, können Sie das ‹+› durch ‹*› ersetzen.
Der Stern-Quantor ‹*› erlaubt, dass ein Element null Mal oder häufiger vorkommt,
wodurch dieses Element im Endeffekt optional wird.

Variationen
Eingabe auf ASCII-Zeichen einschränken
Der folgende reguläre Ausdruck beschränkt die Eingabe auf die 128 Zeichen in der 7-Bit-
ASCII-Tabelle. Dazu gehören auch 33 nicht sichtbare Steuerzeichen:
^[\x00-\x7F]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Eingabe auf ASCII-Zeichen ohne Steuerzeichen und auf Zeilenumbrüche einschränken


Mit dem folgenden regulären Ausdruck beschränken Sie die Eingabe auf sichtbare Zei-
chen und Whitespace in der ASCII-Tabelle. Steuerzeichen werden damit ausgeschlossen.
Die Zeichen für Line Feed und Carriage Return (mit den Werten 0x0A und 0x0D) sind
die gebräuchlichsten Steuerzeichen, daher werden sie hier explizit durch ‹\n› (Line Feed)
und ‹\r› (Carriage Return) mit aufgenommen:
^[\n\r\x20-\x7E]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Eingabe auf die Zeichen begrenzen, die sowohl in ISO-8859-1 als auch in Windows-
1252 vorkommen
ISO-8859-1 und Windows-1252 (häufig als ANSI bezeichnet) sind zwei häufig genutzte
Zeichenkodierungen mit 8 Bit Breite, die beide auf dem Latin-1-Standard basieren
(genauer gesagt, auf ISO/IEC 8859-1). Allerdings sind die Zeichen an den Positionen von
0x80 und 0x9F inkompatibel. ISO-8859-1 nutzt diese Positionen für Steuerzeichen, wäh-
rend Windows-1252 dort noch mehr Buchstaben und Satzzeichen abgelegt hat. Diese
Unterschiede führen manchmal zu Problemen beim Anzeigen von Zeichen, insbesondere
bei Dokumenten, die ihre Kodierung nicht angeben, oder bei denen der Empfänger ein
Nicht-Windows-System verwendet. Der folgende reguläre Ausdruck kann dazu verwen-
det werden, die Eingabe auf Zeichen zu beschränken, die in beiden Zeichentabellen ISO-
8859-1 und Windows-1252 vorhanden sind (einschließlich der gemeinsamen Steuer-
zeichen):

258 | Kapitel 4: Validierung und Formatierung


^[\x00-\x7F\xA0-\xFF]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Durch die hexadezimale Schreibweise ist dieser reguläre Ausdruck vielleicht etwas
schlechter zu lesen, aber er arbeitet genau so wie die weiter oben gezeigte Zeichenklasse
‹[A-Z0-9]›. Die Regex passt auf Zeichen in zwei Bereichen: \x00-\x7F und \xA0-\xFF.

Eingabe auf alphanumerische Zeichen in beliebigen Sprachen einschränken


Dieser reguläre Ausdruck beschränkt die Eingabe auf Buchstaben und Ziffern aus beliebi-
gen Sprachen oder Schriften. Er verwendet eine Zeichenklasse, die Eigenschaften für alle
Codepoints in den Buchstaben- und Ziffernkategorien von Unicode enthält:
^[\p{L}\p{N}]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9
Leider werden Unicode-Eigenschaften nicht von allen in diesem Buch behandelten
Regex-Varianten unterstützt. Vor allem funktioniert diese Regex nicht in JavaScript,
Python und Ruby 1.8. Zudem muss die PCRE mit UTF-8-Unterstützung kompiliert wer-
den, will man diese Regex dort nutzen. Unicode-Eigenschaften können in den preg-Funk-
tionen von PHP genutzt werden (die auf der PCRE aufbauen), wenn an die Regex die
Option /u angehängt wird.
Mit der folgenden Regex kann man in Python die fehlende Unterstützung von Unicode-
Eigenschaften umgehen:
^[^\W_]+$
Regex-Optionen: Unicode
Regex-Variante: Python
Hier umgehen wir die in Python fehlenden Unicode-Eigenschaften, indem wir den Schal-
ter UNICODE oder U beim Erstellen des regulären Ausdrucks verwenden. Dadurch wird die
Bedeutung einiger Regex-Tokens so verändert, dass sie auf die Unicode-Zeichentabelle
zugreifen. ‹\w› bringt uns schon recht weit, weil damit alphanumerische Zeichen und der
Unterstrich gefunden werden. Durch die inverse Abkürzung ‹\W› in einer negierten Zei-
chenklasse können wir den Unterstrich aus dieser Menge entfernen. Solche doppelt
negierten Elemente sind in regulären Ausdrücken manchmal recht nützlich, auch wenn
man eventuell erst einmal seinen Grips dafür anstrengen muss.1

1 Wenn Sie noch mehr Spaß haben wollen (für sehr seltsame Definitionen von „Spaß”), können Sie versuchen,
dreifache, vierfache oder noch „höher-fache“ Negierungen zu erzeugen, indem Sie negative Lookarounds
(siehe Rezept 2.16) und Zeichenklassen-Subtraktionen (siehe Rezept 2.3) ins Spiel bringen.

4.8 Eingabe auf alphanumerische Zeichen beschränken | 259


Siehe auch
In Rezept 4.9 wird gezeigt, wie man die Länge des Texts einschränkt.

4.9 Die Länge des Texts begrenzen


Problem
Sie wollen prüfen, ob ein String zwischen einem und zehn Buchstaben von A bis Z ent-
hält.

Lösung
Alle Programmiersprachen, die in diesem Buch behandelt werden, stellen eine einfache
und effiziente Möglichkeit bereit, die Länge eines Texts zu überprüfen. So haben zum
Beispiel JavaScript-Strings eine Eigenschaft length mit einer Integer-Zahl, die die Länge
des Strings angibt. Aber manchmal kann es auch sinnvoll sein, die Länge eines Texts mit
einem regulären Ausdruck zu prüfen, insbesondere wenn die Länge nur eine von mehre-
ren Regeln ist, die bestimmen, ob der Ausgangstext in das gewünschte Muster passt. Der
folgende reguläre Ausdruck stellt sicher, dass der Text zwischen 1 und 10 Zeichen lang
ist. Zudem schränkt er den Text auf die Großbuchstaben A bis Z ein. Sie können die
regulären Ausdrücke so verändern, dass sie eine minimale oder maximale Textlänge defi-
nieren, aber auch andere Zeichen als A bis Z zulassen.

Regulärer Ausdruck
^[A-Z]{1,10}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Perl
if ($ARGV[0] =~ /^[A-Z]{1,10}$/) {
print "Eingabe ist gültig\n";
} else {
print "Eingabe ist ungültig\n";
}

Andere Programmiersprachen
In Rezept 3.5 finden Sie Informationen darüber, wie Sie diesen regulären Ausdruck in
anderen Programmiersprachen implementieren können.

260 | Kapitel 4: Validierung und Formatierung


Diskussion
Hier ist die Aufteilung dieser sehr einfachen Regex in ihre Bestandteile:
^ # Sicherstellen, dass die Regex am Textanfang passt.
[A-Z] # Einen der Buchstaben von "A" bis "Z" finden ...
{1,10} # zwischen 1 und 10 Mal.
$ # Sicherstellen, dass die Regex am Textende passt.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Die Anker ‹^› und ‹$› stellen sicher, dass die Regex den gesamten Ausgangstext umfasst.
Ansonsten könnte es sein, dass sie nur zehn Zeichen innerhalb eines längeren Texts fin-
det. Die Zeichenklasse ‹[A-Z]› passt auf einen einzelnen Großbuchstaben von A bis Z.
Der Intervall-Quantor ‹{1,10}› sorgt dafür, dass die Zeichenklasse zwischen einem und
zehn Mal vorkommen kann. Indem man den Intervall-Quantor mit den Textanfangs-
und -ende-Ankern kombiniert, wird die Regex fehlschlagen, wenn die Länge des Aus-
gangstexts außerhalb des gewünschten Bereichs liegt.
Beachten Sie, dass die Zeichenklasse ‹[A-Z]› explizit nur Großbuchstaben zulässt. Wenn
Sie auch die Kleinbuchstaben a bis z aufnehmen wollen, können Sie entweder als Zei-
chenklasse ‹[A-Za-z]› verwenden oder die Option zum Ignorieren von Groß- und Klein-
schreibung nutzen. In Rezept 3.4 ist nachzulesen, wie man das tut.
Ein von Anfängern häufig gemachter Fehler ist, ein paar Tastendrücke dadurch zu spa-
ren, dass man den Zeichenklassenbereich ‹[A-z]› nutzt. Auf den ersten Blick sieht das
eigentlich ganz praktisch aus. Aber die ASCII-Zeichentabelle enthält zwischen den Berei-
chen von A bis Z und a bis z eine Reihe von Satzzeichen. Daher entspricht ‹[A-z]› in
Wirklichkeit ‹[A-Z[\]^_`a-z]›.

Variationen
Die Länge eines bestimmten Musters begrenzen
Da Quantoren wie ‹{1,10}› nur auf das direkt vor ihnen stehende Element wirken, muss
man etwas anders vorgehen, wenn man die Zeichenzahl von Mustern begrenzen will, die
mehr als ein einzelnes Token enthalten.
Wie in Rezept 2.16 beschrieben, sind Lookaheads (und ihre Gegenstücke, die Look-
behinds) besondere Arten von Zusicherungen, die wie ‹^› und ‹$› auf eine Position
innerhalb des Ausgangstexts passen, aber keine Zeichen konsumieren. Lookaheads kön-
nen entweder positiv oder negativ sein. Das bedeutet, man kann mit ihnen prüfen, ob auf
die aktuelle Position ein Muster folgt – oder eben nicht. Ein positives Lookahead, das die
Syntax ‹(?=...)› hat, kann am Anfang des Musters genutzt werden, um sicherzustellen,
dass die Länge des Strings innerhalb des Zielbereichs liegt. Der Rest der Regex kann dann
das gewünschte Muster prüfen, ohne sich darum kümmern zu müssen, wie lang der Text
ist. Hier ein einfaches Beispiel:

4.9 Die Länge des Texts begrenzen | 261


^(?=.{1,10}$).*
Regex-Optionen: Punkt passt zu Zeilenumbruch
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^(?=[\S\s]{1,10}$)[\S\s]*
Regex-Optionen: Keine
Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Es ist wichtig, dass sich der Anker ‹$› innerhalb des Lookaheads befindet, denn das Prü-
fen der maximalen Länge funktioniert nur, wenn wir sicherstellen, dass es nach dem
Erreichen der Grenze keine weiteren Zeichen gibt. Da das Lookahead die Länge des
Bereichs am Anfang der Regex sicherstellt, kann das folgende Muster dann beliebige
zusätzliche Validierungsregeln umsetzen. In diesem Fall wird das Muster ‹.*› (oder
‹[\S\s]*› für JavaScript) genutzt, um einfach den gesamten Ausgangstext zu finden –
ohne zusätzliche Einschränkungen.
Die erste Regex verwendet die Option Punkt passt auf Zeilenumbruch, damit der Punkt
wirklich alle Zeichen findet – auch Zeilenumbrüche. In Rezept 3.4 finden Sie Details über
das Anwenden dieses Modifikators in Ihrer Programmiersprache. Die Regex für Java-
Script sieht anders aus, da JavaScript die Option Punkt passt auf Zeilenumbruch nicht
besitzt. In „Jedes Zeichen einschließlich Zeilenumbrüchen“ auf Seite 37 in Rezept 2.4 fin-
den Sie weitere Informationen.

Anzahl der Zeichen ohne Whitespace einschränken


Die folgende Regex passt auf Strings, die zwischen 10 und 100 Nicht-Whitespace-Zei-
chen enthalten:
^\s*(?:\S\s*){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In Java, PCRE, Python und Ruby passt ‹\s› nur auf ASCII-Whitespace-Zeichen und ‹\S›
auf alles andere. In Python können Sie durch die Option UNICODE oder U beim Erzeugen
der Regex dafür sorgen, dass ‹\s› jeden Unicode-Whitespace berücksichtigt. Entwickler,
die mit Java, der PCRE oder Ruby 1.9 arbeiten und verhindern wollen, dass Unicode-
Whitespace beim Zählen berücksichtigt wird, können die folgende Version nutzen, die
von den Unicode-Eigenschaften Gebrauch macht (beschrieben in Rezept 2.7):
^[\p{Z}\s]*(?:[^\p{Z}\s][\p{Z}\s]*){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9
Die PCRE muss mit UTF-8-Unterstützung kompiliert werden, damit diese Regex wie
gewünscht funktioniert. In PHP müssen Sie die UTF-8-Unterstützung durch den Modifi-
kator /u aktivieren.

262 | Kapitel 4: Validierung und Formatierung


Diese Regex kombiniert die Unicode-Eigenschaft „Separator“ ‹\p{Z}› mit der Zeichen-
klassenabkürzung ‹\s› für Whitespace. Das liegt daran, dass Zeichen, die von ‹\p{Z}›
und ‹\s› gefunden werden, nicht unbedingt identisch sind. Zu ‹\s› gehören die Zeichen
an den Positionen 0x09 bis 0x0D (Tab, Line Feed, vertikaler Tab, Form Feed und Car-
riage Return), die nicht der Separator-Eigenschaft im Unicode-Standard zugewiesen sind.
Indem Sie ‹\p{Z}› und ‹\s› in einer Zeichenklasse kombinieren, stellen Sie sicher, dass
alle Whitespace-Zeichen gefunden werden.
In beiden Regexes wird der Intervall-Quantor ‹{10,100}› auf die vor ihm stehende nicht-
einfangende Gruppe angewendet und nicht nur auf ein einzelnes Token. Die Gruppe
passt zu jedem einzelnen Nicht-Whitespace-Zeichen, dem null oder mehr Whitespace-
Zeichen folgen. Der Intervall-Quantor kann zuverlässig erkennen, wie viele Nicht-
Whitespace-Zeichen gefunden wurden, da während jeder Iteration nur genau ein Nicht-
Whitespace-Zeichen passt.

Anzahl der Wörter einschränken


Die folgende Regex ähnelt dem vorigen Beispiel, bei dem die Anzahl der Nicht-
Whitespace-Zeichen begrenzt wird. Nur passt hier jede Wiederholung auf ein ganzes
Wort und nicht auf ein einzelnes Nicht-Whitespace-Zeichen. Es passt auf 10 bis 100
Wörter, wobei alle Nicht-Wortzeichen ignoriert werden – auch Satzzeichen und
Whitespace:
^\W*(?:\w+\b\W*){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In Java, JavaScript, PCRE und Ruby passt das Wortzeichen-Token ‹\w› in dieser Regex
nur auf die ASCII-Zeichen A–Z, a–z, 0–9 und _. Daher lassen sich damit Wörter mit
Nicht-ASCII-Buchstaben und -Zahlen nicht korrekt zählen. In .NET und Perl basiert ‹\w›
auf der Unicode-Tabelle (genauso wie das Gegen-Token ‹\W› und die Wortgrenze ‹\b›)
und passt daher auf Buchstaben und Ziffern aus allen Unicode-Schriftsystemen. In
Python können Sie selbst wählen, ob diese Tokens auf Unicode basieren sollen oder
nicht. Das hängt davon ob, ob Sie die Option UNICODE oder U beim Erstellen der Regex mit
angeben.
Wenn Sie Wörter zählen wollen, die Nicht-ASCII-Buchstaben und -Zahlen enthalten,
können die folgenden Regexes dies für weitere Regex-Varianten ermöglichen:
^[^\p{L}\p{N}_]*(?:[\p{L}\p{N}_]+\b[^\p{L}\p{N}_]*){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, Perl
^[^\p{L}\p{N}_]*(?:[\p{L}\p{N}_]+(?:[^\p{L}\p{N}_]+|$)){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

4.9 Die Länge des Texts begrenzen | 263


Die PCRE muss mit UTF-8-Unterstützung kompiliert werden, damit diese Regex dort
funktioniert. In PHP schalten Sie die UTF-8-Unterstützung mit dem Muster-Modifikator
/u ein.
Wie schon in „Wortzeichen“ auf Seite 46 in Rezept 2.6 erwähnt, ist der Grund für diese
verschiedenen (aber gleich funktionierenden) Regexes der unterschiedliche Umgang mit
Wortzeichen und Wortgrenzen.
Die letzten beiden Regexes nutzen Zeichenklassen, die die Unicode-Eigenschaften für
Buchstaben und Zahlen verwenden (‹\p{L}› und ‹\p{N}›). Dazu wurde noch der Unter-
strich mit aufgenommen, damit sich die Zeichenklassen genau so verhalten wie bei den
anderen Regexes, die auf ‹\w› und ‹\W› aufbauen.
Jede Wiederholung der nicht-einfangenden Gruppe in den ersten beiden dieser drei
Regexes passt zu einem vollständigen Wort, auf das null oder mehr Nicht-Wortzeichen
folgen. Das Token ‹\W› (oder ‹[^\p{L}\p{N}_]›) innerhalb der Gruppe ist optional, falls
der String mit einem Wortzeichen endet. Da damit aber die Nicht-Wortzeichen-Folge
während des Suchprozesses optional würde, brauchen wir die Wortgrenzenzusicherung
‹\b› zwischen ‹\w› und ‹\W› (oder ‹[\p{L}\p{N}_]› und ‹[^\p{L}\p{N}_]›). Damit ist
sichergestellt, dass jede Wiederholung der Gruppe wirklich ein vollständiges Wort fin-
det. Ohne die Wortgrenze würde eine einzelne Wiederholung einen beliebigen Teil eines
Worts finden, und die folgenden Wiederholungen könnten dann auf zusätzliche Teile
passen.
Die dritte Version der Regex (durch die auch PCRE und Ruby 1.9 mitmachen können)
funktioniert ein bisschen anders. Sie verwendet einen Plus- (eins oder mehr) statt eines
Stern-Quantors (null oder mehr) und lässt explizit nur dann null Zeichen zu, wenn der
Suchprozess schon am Ende des Strings angelangt ist. Damit wird das Wortgrenzen-
Token vermieden, was für eine genauere Suche wichtig war, da ‹\b› in der PCRE und in
Ruby nicht Unicode-kompatibel ist. In Java ist ‹\b› Unicode-kompatibel, ‹\w› allerdings
nicht.
Leider ist es mit keiner dieser Optionen möglich, in JavaScript oder Ruby 1.8 korrekt mit
Wörtern umzugehen, die Nicht-ASCII-Zeichen verwenden. Eine mögliche Alternative ist,
die Regex so umzubauen, dass sie Whitespace-Sequenzen statt Wörter zählt:
^\s*(?:\S+(?:\s+|$)){10,100}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, Perl, PCRE, Python, Ruby
Das funktioniert in vielen Fällen genau so wie die vorherigen Lösungen, ist aber nicht
genau das Gleiche. So werden hier zum Beispiel Wörter mit Bindestrichen (wie „S-
Bahn“) als ein Wort gezählt und nicht mehr als zwei Wörter.

Siehe auch
Rezepte 4.8 und 4.10.

264 | Kapitel 4: Validierung und Formatierung


4.10 Die Zeilenanzahl eines Texts beschränken
Problem
Sie müssen prüfen, ob ein String aus fünf oder weniger Zeilen besteht, wobei die Gesamt-
länge des Strings unwichtig ist.

Lösung
Die Zeichen oder Zeichenfolgen, die als Zeilentrenner genutzt werden, können sehr stark
von Ihrem Betriebssystem, der Anwendung oder den Einstellungen des Benutzers abhän-
gig sein. Daher stellt sich beim Schaffen einer idealen Lösung die Frage, welche Konven-
tionen unterstützt werden sollen, um den Anfang einer neuen Zeile zu erkennen. Die
folgenden Lösungen unterstützen den Standard von MS-DOS/Windows (‹\r\n›), dem
alten Mac OS (‹\r›) und Unix/Linux/OS X (‹\n›).

Regulärer Ausdruck
Die folgenden drei variantenspezifischen Regexes besitzen zwei Unterschiede. Die erste
Regex verwendet atomare Gruppen, die als ‹(?>...)› geschrieben werden, statt auf
nicht-einfangende Gruppen zurückzugreifen (‹(?:...)›), denn dadurch können die
Regex-Varianten, die sie unterstützen, eventuell einen kleinen Geschwindigkeitsvorteil
erhalten. Python und JavaScript bieten keine atomaren Gruppen, daher werden sie bei
diesen Varianten nicht verwendet. Der andere Unterschied sind die Tokens, die genutzt
werden, um die Position am Anfang und Ende des Strings sicherzustellen (‹\A› oder ‹^›
für den Anfang des Strings und ‹\z›, ‹\Z› oder ‹$› für dessen Ende). Die Gründe für
diese Unterschiede werden später noch genauer beleuchtet. Alle drei variantenspezifi-
schen Regexes passen auf genau die gleichen Strings:
\A(?>(?>\r\n?|\n)?[^\r\n]*){0,5}\z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
\A(?:(?:\r\n?|\n)?[^\r\n]*){0,5}\Z
Regex-Optionen: Keine
Regex-Variante: Python
^(?:(?:\r\n?|\n)?[^\r\n]*){0,5}$
Regex-Optionen: Keine
Regex-Variante: JavaScript

4.10 Die Zeilenanzahl eines Texts beschränken | 265


PHP (PCRE)
if (preg_match('/\A(?>(?>\r\n?|\n)?[^\r\n]*){0,5}\z/', $_POST['subject'])) {
print 'Text enthält fünf oder weniger Zeilen';
} else {
print 'Text enthält mehr als fünf Zeilen';
}

Andere Programmiersprachen
In Rezept 3.5 finden Sie Informationen dazu, wie diese regulären Ausdrücke mit anderen
Programmiersprachen implementiert werden können.

Diskussion
Alle in diesem Rezept bis hierhin gezeigten regulären Ausdrücke nutzen eine Gruppe, die
zu einem Zeilenumbruch in MS-DOS/Windows, dem alten Mac OS oder in
Unix/Linux/OS X passen, gefolgt von einer beliebigen Zahl von Zeichen, die kein Zeilen-
umbruch sind. Diese Gruppe wird zwischen null und fünf Mal wiederholt, da wir bis zu
fünf Zeilen finden wollen.
Im folgenden Beispiel haben wir die JavaScript-Version der Regex in ihre Bestandteile
zerlegt. Die JavaScript-Version haben wir hier deshalb verwendet, weil ihre Elemente ver-
mutlich den meisten Lesern bekannt sein dürften. Wir werden die Versionen für andere
Regex-Varianten im Anschluss behandeln:
^ # Position am Anfang des Strings sicherstellen.
(?: # Gruppieren, aber nicht einfangen ...
(?: # Gruppieren, aber nicht einfangen ...
\r # Ein Carriage Return (CR, ASCII-Position 0x0D) finden.
\n # Ein Line Feed (LF, ASCII-Position 0x0A) finden ...
? # null oder ein Mal.
| # oder ...
\n # Ein Line Feed finden.
) # Ende der nicht-einfangenden Gruppe.
? # Die vorige Gruppe null oder ein Mal wiederholen.
[^\r\n] # Ein beliebiges Zeichen außer CR oder LF finden ...
* # null Mal oder öfter.
) # Ende der nicht-einfangenden Gruppe.
{0,5} # Vorherige Gruppe null bis fünf Mal wiederholen.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Das führende ‹^› stellt sicher, dass sich die Übereinstimmung am Anfang des Strings
befindet. Damit wird dafür gesorgt, dass der gesamte String nicht mehr als fünf Zeilen
enthält, denn so können sich nicht schon vor dem gefundenen Bereich Zeilen befinden.
Als Nächstes umschließt eine nicht-einfangende Gruppe die Kombination einer Zeilen-
umbruchfolge und einer beliebigen Zahl von Zeichen, die gerade kein Zeilenumbruch

266 | Kapitel 4: Validierung und Formatierung


sind. Der direkt darauffolgende Quantor erlaubt, diese Gruppe zwischen null und fünf
Mal zu finden (null Wiederholungen passen zu einem vollständig leeren String). Inner-
halb der äußeren Gruppe passt eine optionale Untergruppe zu einer Zeilenumbruchfolge.
Danach kommt die Zeichenklasse, die zu einer beliebigen Zahl von Zeichen passt, die
kein Zeilenumbruch sind.
Schauen Sie sich die Reihenfolge der Elemente der äußeren Gruppe genauer an (zuerst
ein Zeilenumbruch, dann anderer Text). Wenn wir die Reihenfolge umkehren, sodass die
Gruppe stattdessen als ‹(?:[^\r\n]*(?:\r\n?|\n)?)› geschrieben würde, ließe eine fünfte
Wiederholung einen abschließenden Zeilenumbruch zu. Somit würde man eine leere
sechste Zeile zulassen.
Die Untergruppe erlaubt eine von drei Zeilenumbruchfolgen:
• Ein Carriage Return, gefolgt von einem Line Feed (‹\r\n›, der normale Zeilenum-
bruch im MS-DOS-/Windows-Umfeld).
• Ein einzelnes Carriage Return (‹\r›, der Zeilenumbruch im alten Mac OS).
• Ein einzelnes Line Feed (‹\n›, der klassische Zeilenumbruch unter Unix/Linux/OS
X).
Lassen Sie uns nun die Unterschiede bei den Varianten anschauen.
Die erste Version der Regex (die für alle Varianten außer Python und JavaScript genutzt
werden kann) verwendet atomare Gruppen statt einfache nicht-einfangende Gruppen.
Auch wenn die Verwendung von atomaren Gruppen einen deutlich größeren Einfluss auf
die Performance haben kann, wird die Regex-Engine in diesem Fall nur vor ein bisschen
unnötigem Backtracking bewahrt, das bei einer fehlschlagenden Suche auftreten kann (in
Rezept 2.15 erhalten Sie mehr Informationen über atomare Gruppen).
Die anderen Unterschiede zwischen den Varianten sind die Tokens, die genutzt werden,
um die Position am Anfang und Ende des Strings sicherzustellen. Die auseinandergenom-
mene Regex weiter oben hat dafür ‹^› und ‹$› genutzt. Diese Anker werden zwar von
allen hier behandelten Regex-Varianten unterstützt, die anderen Regexes in diesem
Abschnitt nutzen aber stattdessen ‹\A›, ‹\Z› und ‹\z›. Kurz gesagt, unterscheidet sich die
Bedeutung dieser Metazeichen ein wenig bei den verschiedenen Regex-Varianten. Eine
ausführlichere Erklärung lässt uns ein wenig in die Geschichte von Regexes eintauchen …
Wenn man Perl nutzt, um eine Zeile aus einer Datei einzulesen, endet der sich so erge-
bende String mit einem Zeilenumbruch. Daher hat Perl eine „Verbesserung“ für die klas-
sische Bedeutung von ‹$› eingeführt, die seitdem von den meisten Regex-Varianten
übernommen wurde. So findet ‹$› nicht nur das absolute Ende des Strings, sondern auch
einen Zeilenumbruch direkt vor dem String-Ende. Perl hat zudem zwei weitere Zusiche-
rungen für das Ende eines Strings eingeführt: ‹\Z› und ‹\z›. Der Anker ‹\Z› hat die glei-
che eigenartige Bedeutung wie ‹$›, nur dass sich diese nicht ändert, wenn die Option
Zirkumflex und Dollar passen auf Zeilenumbruch für ‹^› und ‹$› eingeschaltet wird. ‹\z›
passt immer nur auf das absolute Ende eines Strings – ohne Ausnahme. Da dieses Rezept
explizit mit Zeilenumbrüchen hantiert, um die Zeilen in einem String zu zählen, nutzt es

4.10 Die Zeilenanzahl eines Texts beschränken | 267


die Zusicherung ‹\z› für die Regex-Varianten, in denen sie angeboten wird. Damit kann
sichergestellt werden, dass es keine sechste, leere Zeile gibt.
Die meisten anderen Regex-Varianten haben die Zeilenende-/String-Ende-Anker von Perl
übernommen. .NET, Java, PCRE und Ruby unterstützen alle sowohl ‹\Z› als auch ‹\z›
mit der gleichen Bedeutung wie in Perl. Python bietet nur das ‹\Z› (als Großbuchstabe),
wobei es aber verwirrenderweise die Bedeutung ändert. Es passt nur auf das absolute
Ende des Strings, so wie das kleine ‹\z› von Perl. JavaScript unterstützt überhaupt keine
„z“-Anker, aber anders als die anderen Varianten passt sein Anker ‹$› nur auf das abso-
lute Ende des Strings (wenn die Option Zirkumflex und Dollar passen auf Zeilenumbruch
nicht aktiv ist).
Bei ‹\A› ist das Ganze etwas übersichtlicher. Dieser Anker passt immer nur auf den
Anfang eines Strings und hat überall die gleiche Bedeutung – außer in JavaScript, das ihn
nicht unterstützt.
Es ist zwar nicht sehr schön, dass es diese verwirrenden Inkonsistenzen gibt, aber einer
der Vorteile beim Verwenden regulärer Ausdrücke in diesem Buch ist, dass Sie sich nor-
malerweise keine Sorgen darum machen müssen. Solche unschönen Details werden nur
dann relevant, wenn Sie sich doch intensiver mit den Regexes befassen wollen.

Variationen
Umgang mit esoterischen Zeilentrennern
Die oben gezeigten Regexes unterstützen nur die klassischen Zeilenumbrüche von MS-
DOS/Windows, Unix/Linux/OS X und dem alten Mac OS. Aber es gibt noch eine Reihe
selten genutzter vertikaler Whitespace-Zeichen, die Ihnen gelegentlich über den Weg lau-
fen. Die folgenden Regexes berücksichtigen diese zusätzlichen Zeichen, und schränken
dabei die Übereinstimmungen auf maximal fünf Zeilen Text ein.
\A(?>\R?\V*){0,5}\z
Regex-Optionen: Keine
Regex-Varianten: PCRE 7 (mit der Option PCRE_BSR_UNICODE), Perl 5.10
\A(?>(?>\r\n?|[\n-\f\x85\x{2028}\x{2029}])?
[^\n-\r\x85\x{2028}\x{2029}]*){0,5}\z
Regex-Optionen: Keine
Regex-Varianten: PCRE, Perl
\A(?>(?>\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}\z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, Ruby
\A(?:(?:\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}\Z
Regex-Optionen: Keine
Regex-Variante: Python

268 | Kapitel 4: Validierung und Formatierung


^(?:(?:\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}$
Regex-Optionen: Keine
Regex-Variante: JavaScript
Alle diese Regexes kümmern sich um die Zeilentrenner aus Tabelle 4-1, die zusammen
mit ihren Unicode-Positionen und Namen aufgeführt sind.

Tabelle 4-1: Zeilentrenner


Unicode-Folge Regex-Äquivalent Name Verwendung
U+000D U+000A ‹\r\n› Carriage Return und Line Feed (CRLF) Textdateien unter Windows
und MS-DOS
U+000A ‹\n› Line Feed (LF) Textdateien unter Unix, Linux
und OS X
U+000B ‹\v› Line Tabulation (auch vertikaler Tab oder VT) (selten)
U+000C ‹\f› Form Feed (FF) (selten)
U+000D ‹\r› Carriage Return (CR) Textdateien unter Mac OS
U+0085 ‹\x85› Next Line (NEL) Textdateien auf IBM
Mainframes (selten)
U+2028 ‹\u2028› oder Zeilentrenner (Line Separator) (selten)
‹\x{2028}›
U+2029 ‹\u2029› oder Absatztrenner (Paragraph Separator) (selten)
‹\x{2029}›

Siehe auch
Rezept 4.9.

4.11 Antworten auswerten


Problem
Sie müssen eine Konfigurationsoption oder eine Eingabe an der Befehlszeile auf einen
positiven Wert überprüfen. Sie wollen bei den möglichen Antworten flexibel sein, sodass
true, t, yes, y, ja, j, okay, ok und 1 in beliebiger Groß- und Kleinschreibung akzeptiert
werden.

Lösung
Mit einer Regex, die alle akzeptablen Antworten kombiniert, können Sie die Überprü-
fung mit einem einfachen Test durchführen.

4.11 Antworten auswerten | 269


Regulärer Ausdruck
^(?:1|t(?:rue)?|y(?:es)?|ja?|ok(?:ay)?)$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

JavaScript
var yes = /^(?:1|t(?:rue)?|y(?:es)?|ja?|ok(?:ay)?)$/i;

if (yes.test(subject)) {
alert("Ja");
} else {
alert("Nein");
}

Andere Programmiersprachen
In den Rezepten 3.4 und 3.5 erhalten Sie Hinweise dazu, wie dieser reguläre Ausdruck in
anderen Programmiersprachen implementiert werden kann.

Diskussion
Die folgende Regex zeigt die einzelnen Elemente im Detail. Kombinationen von Tokens,
die leicht zusammen lesbar sind, werden in einer Zeile aufgeführt:
^ # Position am Anfang des Strings sicherstellen.
(?: # Gruppieren, aber nicht einfangen ...
1 # Eine literale "1" finden.
| # oder ...
t(?:rue)? # Finde "t", optional gefolgt von "rue".
| # oder ...
y(?:es)? # Finde "y", optional gefolgt von "es".
| # oder ...
ja? # Finde "j", optional gefolgt von "a".
| # oder ...
ok(?:ay)? # Finde "ok", optional gefolgt von "ay".
) # Ende der nicht-einfangenden Gruppe.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Diese Regex ist im Prinzip nur ein einfacher Test auf einen von neun literalen Werten,
wobei die Groß-/Kleinschreibung ignoriert wird. Sie könnte auch anders geschrieben wer-
den. So ist zum Beispiel ‹^(?:[1tyj]|true|yes|ja|ok(?:ay)?)$› ein ebenso guter Ansatz.
Man könnte auch einfach eine Alternation mit allen neun Werten nutzen, wie zum Beispiel
‹^(?:1|t|true|y|yes|j|ja|ok|okay)$›, aber aus Performancegründen ist es im Allgemei-
nen besser, die Anzahl der Alternativen mit dem Pipe-Operator ‹|› zu reduzieren und Zei-
chenklassen und optionale Endungen (mit dem Quantor ‹?›) vorzuziehen. In diesem Fall

270 | Kapitel 4: Validierung und Formatierung


ist der Performanceunterschied vermutlich nur minimal, aber es ist nicht verkehrt, die Per-
formance von Regexes immer im Hinterkopf zu behalten. Manchmal kann der Unterschied
zwischen den verschiedenen Vorgehensweisen erstaunlich groß sein.
Alle diese Beispiele umgeben die möglichen Werte mit einer nicht-einfangenden Gruppe,
um die Reichweite des Alternationsoperators zu begrenzen. Würden wir die Gruppe
weglassen und stattdessen so etwas wie ‹^true|yes$› verwenden, würde die Regex-
Engine nach „dem Anfang des Strings, gefolgt von true’, oder yes’, gefolgt vom Ende des
Strings“ suchen. ‹^(?:true|yes)$› weist die Regex-Engine an, den Anfang des Strings zu
finden, dann entweder „true“ oder „yes“ und dann das Ende des Strings.

Siehe auch
Rezepte 5.2 und 5.3.

4.12 US-Sozialversicherungsnummern validieren


Problem
Sie müssen prüfen, ob jemand eine gültige US-Sozialversicherungsnummer eingegeben hat.

Lösung
Wenn Sie nur sicherstellen wollen, dass sich ein String an das grundlegende Sozialversi-
cherungsnummerformat hält und keine offensichtlich ungültigen Zahlen enthalten sind,
stellt die folgende Regex eine einfache Lösung bereit. Brauchen Sie eine strengere Prü-
fung, die auch bei der Social Security Administration prüft, ob die Nummer zu einer
lebenden Person gehört, werfen Sie einen Blick auf die Links im Abschnitt „Siehe auch“
dieses Rezepts.

Regulärer Ausdruck
^(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))-
(?!00)[0-9]{2}-(?!0000)[0-9]{4}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Python
if re.match(r"^(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))-
(?!00)[0-9]{2}-(?!0000)[0-9]{4}$", sys.argv[1]):
print "SSN ist gültig"
else:
print "SSN ist ungültig"

4.12 US-Sozialversicherungsnummern validieren | 271


Andere Programmiersprachen
In Rezept 3.5 finden Sie Informationen über das Implementieren dieses regulären Aus-
drucks in anderen Programmiersprachen.

Diskussion
Sozialversicherungsnummern in den USA sind neunstellige Nummern im Format AAA-GG-
SSSS:
Die ersten drei Ziffern werden anhand der geografischen Region zugewiesen. Dies
ist die Area Number. Die Area Number kann nicht den Wert 000 oder 666 haben,
zudem gibt es aktuell keine Sozialversicherungsnummer mit einer Area Number grö-
ßer als 772.
Die Ziffern vier und fünf bilden die Group Number und liegen im Bereich 01 bis 99.
Die letzten vier Ziffern sind Serial Numbers von 0001 bis 9999.
Dieses Rezept nutzt alle diese Regeln. Hier noch einmal der reguläre Ausdruck, dieses
Mal Stück für Stück erläutert:
^ # Position am Anfang des Strings sicherstellen.
(?!000|666) # Weder "000" noch "666" dürfen hier vorkommen.
(?: # Gruppieren, aber nicht einfangen ...
[0-6] # Ein Zeichen im Bereich von "0" bis "6" finden.
[0-9]{2} # Zwei Ziffern finden.
| # oder ...
7 # Eine literale "7" finden.
(?: # Gruppieren, aber nicht einfangen ...
[0-6] # Eine Ziffer im Bereich von "0" bis "6" finden.
[0-9] # Eine Ziffer finden.
| # oder ...
7 # Eine literale "7" finden.
[0-2] # Eine Ziffer im Bereich von "0" bis "2" finden.
) # Ende der nicht-einfangenden Gruppe.
) # Ende der nicht-einfangenden Gruppe.
- # Einen literalen "-" finden.
(?!00) # Sicherstellen, dass "00" hier nicht vorkommt.
[0-9]{2} # Zwei Ziffern finden.
- # Einen literalen "-" finden.
(?!0000) # Sicherstellen, dass "0000" hier nicht vorkommt.
[0-9]{4} # Vier Ziffern finden.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Abgesehen von den Tokens ‹^› und ‹$›, die sicherstellen, dass die Position am Anfang
und Ende des Texts gefunden wird, kann diese Regex in drei Zifferngruppen aufgeteilt
werden, die durch Bindestriche getrennt sind. Die erste Gruppe ist die komplexeste. Die
zweite und die dritte Gruppe passen einfach auf zwei beziehungsweise vier Ziffern. Dabei

272 | Kapitel 4: Validierung und Formatierung


wird aber vorher ein negatives Lookahead genutzt, um zu verhindern, dass alle Ziffern
solch einer Gruppe 0 sind.
Die erste Zifferngruppe ist viel komplexer und auch schlechter lesbar, denn sie passt auf
einen ganzen Bereich. Zunächst wird das negative Lookahead ‹(?!000|666)› verwendet,
um die Werte „000“ und „666“ auszuschließen. Als Nächstes geht es darum, alle Zahlen
größer als 772 auszuschließen.
Da reguläre Ausdrücke mit Text arbeiten und nicht mit Zahlen, müssen wir den Bereich
Zeichen für Zeichen unterteilen. Zunächst einmal wissen wir, dass jede dreistellige Zahl
gültig ist, deren erste Ziffern im Bereich von 0 bis 6 liegt. Durch das vorherige negative
Lookahead sind die ungültigen Zahlen 000 und 666 schon ausgeschlossen. Dieser erste
Teil wird recht einfach durch ein paar Zeichenklassen und einen Quantor umgesetzt:
‹[0-6][0-9]{2}›. Da wir eine Alternative für Zahlen benötigen, die mit 7 beginnen, nut-
zen wir eine Gruppe wie in ‹(?:[0-6][0-9]{2}|7)›, um die Reichweite des Alternations-
operators einzuschränken.
Nummern, die mit 7 beginnen, sind nur zulässig, wenn sie im Bereich von 700 bis 772
liegen, daher müssen wir nun die Nummern in Abhängigkeit von der zweiten Ziffern
unterteilen. Liegt sie zwischen 0 und 6, ist eine beliebige dritte Ziffer erlaubt. Ist die
zweite Ziffer 7, muss die dritte Ziffer zwischen 0 und 2 liegen. Bringen wir alle diese
Regeln zusammen, erhalten wir ‹7(?:[0-6][0-9]|7[0-2])›.
Fügen Sie das schließlich in die äußere Gruppe für die restlichen gültigen Nummern ein,
erhalten Sie ‹(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))›. Das ist alles. Sie haben erfolg-
reich eine Regex erstellt, die eine dreistellige Nummer zwischen 000 und 772 findet.

Variationen
Sozialversicherungsnummern in Dokumenten finden
Wenn Sie in einem größeren Dokument nach Sozialversicherungsnummern suchen,
ersetzen Sie die Anker ‹^› und ‹$› durch Wortgrenzen. Regex-Engines betrachten alle
alphanumerischen Zeichen und den Unterstrich als Wortzeichen.
\b(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))-
(?!00)[0-9]{2}-(?!0000)[0-9]{4}\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Siehe auch
Die Website der Social Security Administration (http://www.socialsecurity.gov) beantwor-
tet die am häufigsten gestellten Fragen und führt auch aktuelle Listen mit bisher zugewie-
senen Area und Group Numbers.

4.12 US-Sozialversicherungsnummern validieren | 273


Der Social Security Number Verification Service (SSNVS) unter http://www.socialsecu-
rity.gov/employer/ssnv.htm stellt zwei Wege bereit, um zu überprüfen, ob Namen und
Sozialversicherungsnummern den Daten der Social Security Administration entsprechen.
Der Umgang mit Zahlenbereichen, einschließlich Beispielen für das Finden solcher Berei-
che, wird in Rezept 6.5 detaillierter erläutert.

4.13 ISBN validieren


Problem
Sie müssen die Gültigkeit einer International Standard Book Number (ISBN) prüfen.
Diese kann entweder im älteren ISBN-10- oder im aktuellen ISBN-13-Format vorliegen.
Am Anfang soll optional eine ISBN-Kennung stehen, zudem können die Teile der ISBN
optional durch Bindestriche oder Leerzeichen getrennt sein. ISBN 978-0-596-52068-7,
ISBN-13: 978-0-596-52068-7, 978 0 596 52068 7, 9780596520687, ISBN-10 0-596-52068-9
und 0-596-52068-9 sind allesamt Beispiele für gültige Eingaben.

Lösung
Sie können eine ISBN nicht allein mit einer Regex validieren, da die letzte Ziffer mit
einem Prüfsummenalgorithmus berechnet wird. Die regulären Ausdrücke in diesem
Abschnitt überprüfen das Format einer ISBN, während die folgenden Codebeispiele auch
prüfen, ob die letzte Ziffer korrekt ist.

Reguläre Ausdrücke
ISBN-10:
^(?:ISBN(?:-10)?:?z)?(?=[-0-9Xz]{13}$|[0-9X]{10}$)[0-9]{1,5}[-z]?
(?:[0-9]+[-z]?){2}[0-9X]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
ISBN-13:
^(?:ISBN(?:-13)?:?z)?(?=[-0-9z]{17}$|[0-9]{13}$)97[89][-z]?[0-9]{1,5}
[-z]?(?:[0-9]+[-z]?){2}[0-9]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
ISBN-10 oder ISBN-13:
^(?:ISBN(?:-1[03])?:?z)?(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[0-9X]{10}$)
(?:97[89][-z]?)?[0-9]{1,5}[-z]?(?:[0-9]+[-z]?){2}[0-9X]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

274 | Kapitel 4: Validierung und Formatierung


JavaScript
// `regex` prüft auf die Formate ISBN-10 oder ISBN-13
var regex = /^(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}$|[-0-9X ]{13}$|
[0-9X]{10}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$/;

if (regex.test(subject)) {
// Nicht zugehörige ISBN-Elemente entfernen, dann in ein Array aufteilen
var chars = subject.replace(/[^0-9X]/g, "").split("");
// Letzte ISBN-Ziffer aus `chars` entfernen und `last` zuweisen
var last = chars.pop();
var sum = 0;
var digit = 10;
var check;

if (chars.length == 9) {
// ISBN-10-Prüfziffer berechnen
for (var i = 0; i < chars.length; i++) {
sum += digit * parseInt(chars[i], 10);
digit -= 1;
}
check = 11 - (sum % 11);
if (check == 10) {
check = "X";
} else if (check == 11) {
check = "0";
}
} else {
// ISBN-13-Prüfziffer berechnen
for (var i = 0; i < chars.length; i++) {
sum += (i % 2 * 2 + 1) * parseInt(chars[i], 10);
}
check = 10 - (sum % 10);
if (check == 10) {
check = "0";
}
}

if (check == last) {
alert("Gültige ISBN");
} else {
alert("Ungültige ISBN-Prüfziffer");
}
} else {
alert("Ungültige ISBN");
}

Python
import re
import sys

# `regex` prüft auf die Formate ISBN-10 oder ISBN-13


regex = re.compile("^(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}$|
[-0-9X ]{13}$|[0-9X]{10}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?

4.13 ISBN validieren | 275


(?:[0-9]+[- ]?){2}[0-9X]$")

subject = sys.argv[1]

if regex.search(subject):
# Nicht zugehörige ISBN-Elemente entfernen, dann in ein Array aufteilen
chars = re.sub("[^0-9X]", "", subject).split("")
# Letzte ISBN-Ziffer aus `chars` entfernen und `last` zuweisen
last = chars.pop()

if len(chars) == 9:
# ISBN-10-Prüfziffer berechnen
val = sum((x + 2) * int(y) for x,y in enumerate(reversed(chars)))
check = 11 - (val % 11)
if check == 10:
check = "X"
elif check == 11:
check = "0"
else:
# ISBN-13-Prüfziffer berechnen
val = sum((x % 2 * 2 + 1) * int(y) for x,y in enumerate(chars))
check = 10 - (val % 10)
if check == 10:
check = "0"

if (str(check) == last):
print "Gültige ISBN"
else:
print "Ungültige ISBN-Prüfziffer"
else:
print "Ungültige ISBN"

Andere Programmiersprachen
In Rezept 3.5 wird beschrieben, wie Sie diesen regulären Ausdruck in anderen Program-
miersprachen implementieren können.

Diskussion
Eine ISBN ist eine eindeutige Kennung für Bücher und bücherähnliche Produkte. Das
zehnstellige ISBN-Format wurde als internationaler Standard ISO 2108 im Jahr 1970 ver-
öffentlicht. Alle ISBN, die seit dem 1. Januar 2007 zugewiesen werden, sind 13-stellig.
ISBN-10- und ISBN-13-Nummern werden in vier beziehungsweise fünf Elemente aufge-
teilt. Drei der Elemente haben eine variable, die verbleibenden ein oder zwei Elemente
haben eine feste Länge. Alle fünf Teile werden normalerweise durch Bindestriche oder
Leerzeichen getrennt. Dabei haben die Elemente folgende Bedeutung:
• 13-stellige ISBN beginnen mit dem Präfix 978 oder 979.
• Die Gruppennummer steht für einen geografisch oder sprachlich zusammenhängen-
den Raum. Sie kann eine bis fünf Ziffern lang sein.

276 | Kapitel 4: Validierung und Formatierung


• Die Verlagsnummer kann unterschiedlich lang sein und wird von der nationalen
ISBN-Agentur vergeben.
• Die Titelnummer kann auch unterschiedlich lang sein und wird vom Verlag festgelegt.
• Das letzte Zeichen ist die Prüfziffer. Sie wird mit einem Prüfsummenalgorithmus
ermittelt. Eine ISBN-10-Prüfziffer kann entweder die Werte 0 bis 9 oder den Buch-
staben X (für die römische 10) enthalten. Eine ISBN-13-Prüfziffer liegt im Bereich
von 0 bis 9. Die hier verwendeten Zeichen sind unterschiedlich, weil auch unter-
schiedliche Prüfsummenalgorithmen genutzt werden.
Die Regex für ISBN-10- und ISBN-13-Nummern wird im folgenden Beispiel in ihre
Bestandteile zerlegt. Da sie hier im Freiform-Modus genutzt wird, wurden die literalen
Leerzeichen in der Regex durch Backslashs maskiert. Bei Java müssen im Freiform-
Modus Leerzeichen selbst in Zeichenklassen maskiert werden:
^ # Position am Anfang des Strings sicherstellen.
(?: # Gruppieren, aber nicht einfangen ...
ISBN # Den Text "ISBN" finden.
(?:-1[03])? # Optional den Text "-10" oder "-13" finden.
:? # Optional ein literales ":" finden.
\ # Ein (maskiertes) Leerzeichen finden.
)? # Die Gruppe null oder ein Mal finden.
(?= # Sicherstellen, dass das Folgende passt ...
[-0-9\ ]{17}$ # 17 Bindestriche, Ziffern und Leerzeichen bis zum Ende
| # des Strings finden. Oder ...
[-0-9X\ ]{13}$ # 13 Bindestriche, Ziffern, X und Leerzeichen bis zum Ende
| # des Strings finden. Oder ...
[0-9X]{10}$ # 10 Ziffern und X bis zum Ende finden.
) # Ende des positiven Lookahead.
(?: # Gruppieren, aber nicht einfangen ...
97[89] # Den Text "978" oder "979" finden.
[-\ ]? # Optional einen Bindestrich oder ein Leerzeichen finden.
)? # Die Gruppe null oder ein Mal finden.
[0-9]{1,5} # Eine Ziffer ein bis vier Mal finden.
[-\ ]? # Optional einen Bindestrich oder ein Leerzeichen finden.
(?: # Gruppieren, aber nicht einfangen ...
[0-9]+ # Eine Ziffer ein Mal oder häufiger finden.
[-\ ]? # Optional einen Bindestrich oder ein Leerzeichen finden.
){2} # Die Gruppe genau zwei Mal finden.
[0-9X] # Eine Ziffer oder ein "X" finden.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Der erste Teil ‹(?:ISBN(?:-1[03])?:?z)?› hat drei optionale Elemente, durch die einer
der folgenden sieben Strings passt (mit Ausnahme des leeren Strings enthalten alle ein
Leerzeichen am Ende):
• ISBNz
• ISBN-10z
• ISBN-13z

4.13 ISBN validieren | 277


• ISBN:z
• ISBN-10:z
• ISBN-13:z
• Ein leerer String (kein Präfix)
Als Nächstes sorgt das positive Lookahead ‹(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[0-
9X]{10}$)› dafür, dass eine der drei Optionen (getrennt durch den Alternationsoperator
‹|›) bezüglich der Länge und erlaubten Zeichen für den Rest der Übereinstimmung gül-
tig ist. Alle drei Optionen enden mit dem Anker ‹$›. Dadurch ist sichergestellt, dass es
keinen nachfolgenden Text gibt, der nicht zu einem der Muster passt:
‹[-0-9z]{17}$›
Erlaubt eine ISBN-13 mit vier Trennzeichen (insgesamt 17 Zeichen).
‹[-0-9Xz]{13}$›
Erlaubt eine ISBN-13 ohne Trennzeichen oder eine ISBN-10 mit drei Trennzeichen
(insgesamt 13 Zeichen).
‹[0-9X]{10}$›
Erlaubt eine ISBN-10 ohne Trennzeichen (insgesamt 10 Zeichen).
Nachdem das positive Lookahead die Länge und die Zeichen geprüft hat, können wir die
einzelnen Elemente der ISBN auswerten, ohne uns über ihre Gesamtlänge Gedanken
machen zu müssen. ‹(?:97[89][-z]?)?› passt zum Präfix „978“ oder „979“, das von
einer ISBN-13 gefordert wird. Die nicht-einfangende Gruppe ist optional, weil sie bei
einer ISBN-10 nicht vorkommt. ‹[0-9]{1,5}[-z]?› passt zu einer Zifferngruppe mit ein
bis fünf Ziffern, denen optional ein Trennzeichen folgt. ‹(?:[0-9]+[-z]?){2}› passt zur
Verlags- und Titelnummer und deren optionalen Separatoren. Schließlich passt ‹[0-
9X]$› auf die Prüfziffer am Ende des Strings.
Ein regulärer Ausdruck kann zwar prüfen, ob die letzte Ziffer ein gültiges Zeichen nutzt
(eine Ziffer oder ein X), aber nicht, ob es sich dabei um die korrekte Prüfziffer handelt.
Einer der beiden Prüfsummenalgorithmen (abhängig davon, ob Sie mit einer ISBN-10-
oder einer ISBN-13-Nummer arbeiten) wird verwendet, um wenigstens halbwegs sicher
zu sein, dass die ISBN-Ziffern nicht unabsichtlich vertauscht oder anders falsch eingege-
ben wurden. Der weiter oben gezeigte Beispielcode für JavaScript und Python implemen-
tiert beide Algorithmen. Der folgende Abschnitt beschreibt die Prüfsummenregeln, damit
Sie diese Algorithmen auch in anderen Programmiersprachen implementieren können.

ISBN-10-Prüfsumme
Die Prüfziffer einer ISBN-10-Nummer kann den Wert 0 bis 10 haben (wobei die römi-
sche Zahl X statt der 10 verwendet wird). Sie wird wie folgt ermittelt:
1. Multipliziere jede der ersten 9 Ziffern mit einer Zahl in der absteigenden Folge von
10 bis 2 und addiere die Ergebnisse.
2. Teile die Summe durch 11.

278 | Kapitel 4: Validierung und Formatierung


3. Ziehe den Rest (nicht den Quotienten) von 11 ab.
4. Wenn das Ergebnis 11 ist, verwende die Ziffer 0; ist es 10, verwende den Buchsta-
ben X.
Hier ein Beispiel, wie man die ISBN-10-Prüfziffer für 3-89721-957-? ermittelt:
Schritt 1:
sum = 10×3 + 9×8 + 8×9 + 7×7 + 6×2 + 5×1 + 4×9 + 3×5 + 2×7
= 30 + 72 + 72 + 49 + 18 + 5 + 36 + 15 + 14
= 305
Schritt 2:
305 ÷ 11 = 27, Rest 8
Schritt 3:
11 - 8 = 3
Schritt 4:
3 [keine Ersetzung notwendig]

Die Prüfziffer ist 3, daher ist die komplette ISBN ISBN 3-89721-957-3.

ISBN-13-Prüfsumme
Eine ISBN-13-Prüfziffer liegt im Bereich von 0 bis 9 und wird in ähnlichen Schritten
ermittelt.
Multipliziere jede der ersten 12 Ziffern mit 1 oder 3 – immer abwechselnd von links
nach rechts – und addiere die Ergebnisse.
Teile die Summe durch 10.
Ziehe den Rest (nicht den Quotienten) von 10 ab.
Ist das Ergebnis 10, verwende die Ziffer 0.
So wird zum Beispiel die ISBN-13-Prüfziffer für 978-3-89721-957-? wie folgt berechnet:
Schritt 1:
sum = 1×9 + 3×7 + 1×8 + 3×3 + 1×8 + 3×9 + 1×7 + 3×2 + 1×1 + 3×9 + 1×5 + 3×7
= 9 + 21 + 8 + 9 + 8 + 27 + 7 + 6 + 1 + 27 + 5 + 21
= 149
Schritt 2:
149 ÷ 10 = 14, remainder 9
Schritt 3:
10 - 9 = 1
Schritt 4:
1 [keine Ersetzung notwendig]

Die Prüfziffer ist 1, und die komplette ISBN hat den Wert ISBN 978-3-89721-957-1.

Variationen
ISBNs in Dokumenten finden
Diese Version der Regex für ISBN-10 und ISBN-13 nutzt statt der Anker Wortgrenzen,
um ISBN in längeren Texten zu finden, dabei aber sicherzustellen, dass sie für sich ste-

4.13 ISBN validieren | 279


hen. Der Text „ISBN“ ist in dieser Regex immer erforderlich. Das hat zwei Gründe. Zum
einen verhindert man so fälschlicherweise als ISBN erkannte Zahlenfolgen (denn die
Regex könnte potenziell beliebige 10- oder 13-stellige Zahlen finden), und zum anderen
sollen ISBN diesen Text offiziell enthalten, wenn sie ausgegeben werden:
\bISBN(?:-1[03])?:?z(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[0-9X]{10}$)
(?:97[89][-z]?)?[0-9]{1,5}[-z]?(?:[0-9]+[-z]?){2}[0-9X]\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Falsche ISBN-Kennungen finden


Die vorigen Regexes haben ein Problem: Man kann den Text „ISBN-13“ haben, auf den
dann aber eine ISBN-10-Nummer folgt und umgekehrt. Die folgende Regex nutzt Regex-
Bedingungen (siehe Rezept 2.17), um sicherzustellen, dass eine Kennung „ISBN-10“ oder
„ISBN-13“ immer vom passenden ISBN-Typ begleitet wird. Wenn der Typ nicht explizit
angegeben ist, sind beide Nummernarten möglich. Diese Regex ist in den meisten Fällen
doch etwas übertrieben, da man das gleiche Ergebnis auch einfacher erreichen kann,
wenn man die getrennten ISBN-10- und ISBN-13-Regexes nutzt. Sie soll hier eher gezeigt
werden, um eine interessante Anwendung von regulären Ausdrücken zu demonstrieren:
^
(?:ISBN(-1(?:(0)|3))?:?\ )?
(?(1)
(?(2)
(?=[-0-9X ]{13}$|[0-9X]{10}$)
[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$
|
(?=[-0-9 ]{17}$|[0-9]{13}$)
97[89][- ]?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9]$
)
|
(?=[-0-9 ]{17}$|[-0-9X ]{13}$|[0-9X]{10}$)
(?:97[89][- ]?)?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$
)
$
Regex-Optionen: Freiform
Regex-Varianten: .NET, PCRE, Perl, Python

Siehe auch
Die aktuellste Version der ISBN-Dokumente lassen sich auf der Website der Internatio-
nal ISBN Agency unter http://www.isbn-international.org finden.
Die offizielle Liste mit Gruppennummern findet sich ebenfalls auf der Website der Inter-
national ISBN Agency. Anhand dieser Liste können Sie das Ursprungsland eines Buchs
mithilfe der ersten 1 bis 5 Ziffern der ISBN ermitteln.

280 | Kapitel 4: Validierung und Formatierung


4.14 ZIP-Codes validieren
Problem
Sie müssen einen ZIP-Code (eine US-Postleitzahl) validieren, wobei sowohl das fünfstel-
lige als auch das neunstellige Format (ZIP + 4) zu erkennen ist. Die Regex sollte auf 12345
und 12345-6789 passen, aber nicht auf 1234, 123456, 123456789 oder 1234-56789.

Lösung
Regulärer Ausdruck
^[0-9]{5}(?:-[0-9]{4})?$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

VB.NET
If Regex.IsMatch(subjectString, "^[0-9]{5}(?:-[0-9]{4})?$") Then
Console.WriteLine("Gültiger ZIP-Code")
Else
Console.WriteLine("Ungültiger ZIP-Code")
End If

Andere Programmiersprachen
In Rezept 3.5 finden Sie Informationen über das Implementieren dieses regulären Aus-
drucks in anderen Programmiersprachen.

Diskussion
Der reguläre Ausdruck für den ZIP-Code sieht im Freiform-Modus so aus:
^ # Position am Anfang des Strings sicherstellen.
[0-9]{5} # Fünf Ziffern finden.
(?: # Gruppieren, aber nicht einfangen ...
- # Einen literalen "-" finden.
[0-9]{4} # Vier Ziffern finden.
) # Ende der nicht-einfangenden Gruppe.
? # Die vorige Gruppe null oder ein Mal finden.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Diese Regex ist ziemlich einfach, daher ist nicht sehr viel dazu zu sagen. Eine simple
Änderung ermöglicht es Ihnen, ZIP-Codes in einem längeren String zu finden: Ersetzen
Sie die Anker ‹^› und ‹$› durch Wortgrenzen: ‹\b[0-9]{5}(?:-[0-9]{4})?\b›.

4.14 ZIP-Codes validieren | 281


Siehe auch
Rezepte 4.15, 4.16 und 4.17.

4.15 Kanadische Postleitzahlen validieren


Problem
Sie wollen prüfen, ob ein String eine kanadische Postleitzahl enthält.

Lösung
^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]z[0-9][A-Z][0-9]$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Das negative Lookahead am Anfang dieses regulären Ausdrucks verhindert, dass sich
irgendwo im Ausgangstext die Buchstaben D, F, I, O, Q oder U befinden. Die Zeichen-
klasse ‹[A-VXY]› verhindert darüber hinaus, dass W oder Z das erste Zeichen ist. Neben
diesen beiden Ausnahmen werden für kanadische Postleitzahlen einfach abwechselnde
Folgen von sechs alphanumerischen Zeichen genutzt, wobei in der Mitte ein Leerzeichen
steht. So passt diese Regex zum Beispiel auf K1A 0B1. Dabei handelt es sich um die Post-
leitzahl für die Zentrale der kanadischen Post in Ottawa.

Siehe auch
Rezepte 4.14, 4.16 und 4.17.

4.16 Britische Postleitzahlen validieren


Problem
Sie benötigen einen regulären Ausdruck, der britische Postleitzahlen erkennt.

Lösung
^[A-Z]{1,2}[0-9R][0-9A-Z]?z[0-9][ABD-HJLNP-UW-Z]{2}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

282 | Kapitel 4: Validierung und Formatierung


Diskussion
Postleitzahlen in Großbritannien (oder auch Postcodes, wie sie dort genannt werden)
bestehen aus fünf bis sieben alphanumerischen Zeichen, die durch ein Leerzeichen unter-
teilt sind. Die Regeln legen fest, welche Zeichen an welcher Position stehen dürfen. Lei-
der sind sie ziemlich kompliziert und voller Ausnahmen. Daher kümmert sich dieser
reguläre Ausdruck nur um die grundlegenden Regeln.

Siehe auch
British Standard BS7666, verfügbar unter http://www.govtalk.gov.uk/gdsc/html/frames/
PostCode.htm. Hier werden die Regeln für britische Postleitzahlen beschrieben.
Rezepte 4.14, 4.15 und 4.17.

4.17 Deutsche Postleitzahlen validieren


Problem
Sie benötigen einen regulären Ausdruck, der deutsche Postleitzahlen erkennt.

Lösung
^[0-9]{5}$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Deutsche Postleitzahlen bestehen einfach aus fünf Ziffern ohne weitere Unterteilung. Beach-
ten Sie, dass Postleitzahlen bei einer weiteren Verarbeitung nicht als Zahlen angesehen wer-
den sollten, sondern eher als Zeichenkette. In Deutschland gibt es eine Reihe von Orten,
deren Postleitzahl mit einer 0 beginnt. Speichert man Postleitzahlen als Zahl, verschwindet
diese 0, was verwirrt und eventuell dafür sorgt, dass die Post nicht (direkt) ankommt.

Variationen
Postleitzahlen in anderen europäischen Ländern
Belgien
‹^[1-9][0-9]{3}$›
Bulgarien
‹^[1-9][0-9]{3}$›
Dänemark
‹^[1-9][0-9]{3}$›

4.17 Deutsche Postleitzahlen validieren | 283


Finnland
‹^[0-9]{5}$›
Frankreich und Monaco
‹^[0-9]{5}$›
Griechenland
‹^[1-8][0-9]{4}$›
Italien, San Marino und Vatikanstadt
‹^[0-9]{5}$›
Kroatien
‹^(?:[1-4][0-9]|5[1-3])[0-9]{3}$›
Montenegro
‹^8[145][0-9]{3}$›
Niederlande
‹^[1-9][0-9]{3}z?[A-Z]{2}$›
Norwegen
‹^[0-9]{4}$›
Österreich
‹^[1-9][0-9]{3}$›
Polen
‹^[0-9]{2}-[0-9]{3}$›
Portugal
‹^[1-9][0-9]{3}-?[0-9]{2}$›
Rumänien
‹^[0-9]{6}$›
Schweden
‹^[1-9][0-9]{2}z?[0-9]{2}$›
Schweiz und Liechtenstein
‹^[1-9][0-9]{3}$›
Slowakei
‹^[890][0-9]{2}z?[0-9]{2}$›
Spanien
‹^(?:0[1-9]|[1-4][0-9]|5[12])[0-9]{3}$›
Tschechien
‹^[1-7][0-9]{2}z?[0-9]{2}$›
Ungarn
‹^[1-9][0-9]{3}$›
Zypern
‹^[1-9][0-9]{3}$›

Siehe auch
Rezepte 4.14, 4.15 und 4.16.

284 | Kapitel 4: Validierung und Formatierung


4.18 Namen von „Vorname Nachname“ nach „Nachname,
Vorname“ umwandeln
Problem
Sie wollen Personennamen von „Vorname Nachname“ umwandeln in „Nachname, Vor-
name“, um so alphabetisch sortieren zu können. Zudem wollen Sie zusätzlich auf andere
Namensbestandteile Rücksicht nehmen, zum Beispiel auf einen zweiten Vornamen.

Lösung
Leider ist es nicht möglich, Namen mit einem regulären Ausdruck zuverlässig zu parsen.
Reguläre Ausdrücke sind strikt, während Namen so flexibel gehandhabt werden, dass
selbst Menschen durcheinanderkommen. Das Bestimmen der Struktur eines Namens
und die richtige Einordnung in eine alphabetisch sortierte Liste erfordern häufig das Ein-
beziehen traditioneller und landesspezifischer Konventionen, und selbst persönliche Vor-
lieben können eine Rolle spielen. Wenn Sie aber dazu bereit sind, gewissen Annahmen
über Ihre Daten zu treffen und auch dann und wann Fehler akzeptieren können, kann ein
regulärer Ausdruck eine schnelle Lösung bieten.
Der folgende reguläre Ausdruck wird eher einfach gehalten und soll nicht unbedingt alle
möglichen Grenzfälle abdecken.

Regulärer Ausdruck
^(.+?)z([^\s]+)$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung
$2,z$1
Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP
\2,z\1
Ersetzungstextvarianten: Python, Ruby

JavaScript
function formatName (name) {
return name.replace(/^(.+?)z([^\s,]+)$/i,
"$2, $1");
}

4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln | 285
Andere Programmiersprachen
In Rezept 3.15 finden Sie Informationen über das Implementieren dieses regulären Aus-
drucks in anderen Programmiersprachen.

Diskussion
Lassen Sie uns diesen regulären Ausdruck erst mal Stück für Stück betrachten. Danach
erklären wir Ihnen, welche Teile eines Namens von welchen Regex-Elementen gefunden
werden. Da die Regex hier im Freiform-Modus geschrieben ist, wurden die literalen Leer-
zeichen durch Backslashs maskiert:
^ # Position am Anfang des Strings sicherstellen.
( # Gruppe für Rückwärtsreferenz 1 ...
.+? # Eines oder mehrere Zeichen finden, aber so wenig wie möglich.
) # Ende der einfangenden Gruppe.
\ # Ein Leerzeichen finden.
( # Gruppe für Rückwärtsreferenz 2 ...
[^\s]+ # Eines oder mehrere Zeichen finden, die keine Leerzeichen sind.
) # Ende der einfangenden Gruppe.
$ # Position am Ende des Strings sicherstellen.
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck geht von folgenden Annahmen aus:
• Der Ausgangstext enthält mindestens einen Vornamen und einen Nachnamen (wei-
tere Bestandteile sind optional).
• Der Vorname steht vor dem Nachnamen.
Ein paar Probleme gibt es aber:
• Der reguläre Ausdruck kann keine mehrteiligen Nachnamen erkennen, die nicht per
Bindestrich verbunden sind. So würde Sacha Baron Cohen zum Beispiel durch Cohen,
Sacha Baron ersetzt werden und nicht durch die korrekte Version Baron Cohen,
Sacha.
• Namensbestandteile vor dem Familiennamen werden auch nicht dem Nachnamen
zugeordnet, obwohl dies aufgrund von Konventionen oder persönlichen Vorlieben
teilweise gewünscht wird (so kann „Charles de Gaulle“ entweder als „de Gaulle,
Charles“ oder als „Gaulle, Charles de“ aufgeführt sein).
• Aufgrund der Anker ‹^› und ‹$›, die die Übereinstimmung mit dem Anfang und
Ende des Strings verbinden, kann keine Ersetzung vorgenommen werden, wenn
nicht der gesamte Ausgangstext zum Muster passt. Wird keine passende Überein-
stimmung gefunden (weil zum Beispiel der Ausgangstext nur einen Namen enthält),
bleibt der Name so bestehen.
Der reguläre Ausdruck nutzt zwei einfangende Gruppen, um den Namen aufzuteilen.
Diese Elemente werden dann über Rückwärtsreferenzen in der gewünschten Reihenfolge

286 | Kapitel 4: Validierung und Formatierung


wieder zusammengesetzt. Die erste einfangende Gruppe nutzt das ausgesprochen flexible
Muster ‹.+?›, um den ersten Vornamen zusammen mit allen weiteren Vornamen und
den zusätzlichen Bestandteilen des Nachnamens einzufangen, wie zum Beispiel das deut-
sche „von“ oder das französische, portugiesische und spanische „de“. Diese Namensele-
mente werden zusammen bearbeitet, da sie in der Ausgabe auch nacheinander
erscheinen sollen.
Die zweite einfangende Gruppe passt durch ‹[^\s]+› auf den Nachnamen. Wie beim
Punkt in der ersten einfangenden Gruppe ermöglicht die Flexibilität dieser Zeichenklasse
auch die Verwendung von Umlauten und anderen exotischen Zeichen.
In Tabelle 4-2 werden ein paar Beispiele für mit dieser Regex und dem entsprechenden
Ersetzungstext umgestellte Namen aufgeführt.

Tabelle 4-2: Formatierte Namen


Eingabe Ausgabe
Robert Downey Downey, Robert
John F. Kennedy Kennedy, John F.
Scarlett O’Hara O’Hara, Scarlett
Pepé Le Pew Pew, Pepé Le
J.R.R. Tolkien Tolkien, J.R.R.
Catherine Zeta-Jones Zeta-Jones, Catherine

Variationen
Nachnamensbestandteile am Anfang des Namens aufführen
Im folgenden regulären Ausdruck haben wir ein Element ergänzt, durch das zusätzliche
Bestandteile des Nachnamens bei ihm verbleiben. Diese Regex berücksichtigt „de“, „du“,
„la“, „le“, „St“, „St.“, „Ste“, „Ste.“, „van“ und „von“. Dabei sind auch mehrere solcher
Bestandteile möglich (zum Beispiel „de la”):
^(.+?)z((?:(?:D[eu]|L[ae]|Ste?\.?|V[ao]n)z)*[^\s]+)$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
$2,z$1
Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP
\2,z\1
Ersetzungstextvarianten: Python, Ruby

4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln | 287
4.19 Kreditkartennummern validieren
Problem
Sie sollen für eine Firma ein Bestellformular bauen, das auch eine Bezahlung per Kredit-
karte zulässt. Da die Karten-Servicegesellschaft für jeden Transaktionsversuch eine
Gebühr erhebt – auch für fehlgeschlagene Versuche –, wollen Sie mit einem regulären
Ausdruck die offensichtlich ungültigen Kreditkartennummern ausfiltern.
Nebenbei verbessert das auch die Bedienungsfreundlichkeit. Ein regulärer Ausdruck
kann offensichtliche Tippfehler sofort erkennen, sobald der Anwender mit dem Ausfül-
len der Felder auf der Webseite fertig ist. Eine Anfrage bei der Karten-Servicegesellschaft
dauert dagegen leicht einmal 10 bis 30 Sekunden.

Lösung
Leerzeichen und Bindestriche entfernen
Lesen Sie die vom Kunden eingegebene Kreditkartennummer aus und speichern Sie sie in
einer Variablen. Bevor Sie die Gültigkeit der Nummer überprüfen, suchen Sie nach Leer-
zeichen und Bindestrichen und entfernen sie aus der Nummer. Ersetzen Sie diesen regu-
lären Ausdruck global durch einen leeren Ersetzungstext:
[z-]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In Rezept 3.14 ist beschrieben, wie Sie diese erste Ersetzung vornehmen.

Validieren der Nummer


Nachdem Leerzeichen und Bindestriche aus der Eingabe entfernt wurden, prüft dieser
reguläre Ausdruck, ob die Kreditkartennummer dem Format einer der sechs großen Kre-
ditkartenfirmen entspricht. Dabei nutzt die Regex benannte Captures, um herauszufin-
den, was für eine Kreditkarte der Kunde hat:
^(?:
(?<visa>4[0-9]{12}(?:[0-9]{3})?) |
(?<mastercard>5[1-5][0-9]{14}) |
(?<discover>6(?:011|5[0-9][0-9])[0-9]{12}) |
(?<amex>3[47][0-9]{13}) |
(?<diners>3(?:0[0-5]|[68][0-9])[0-9]{11}) |
(?<jcb>(?:2131|1800|35\d{3})\d{11})
)$
Regex-Optionen: Freiform
Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

288 | Kapitel 4: Validierung und Formatierung


^(?:
(?P<visa>4[0-9]{12}(?:[0-9]{3})?) |
(?P<mastercard>5[1-5][0-9]{14}) |
(?P<discover>6(?:011|5[0-9][0-9])[0-9]{12}) |
(?P<amex>3[47][0-9]{13}) |
(?P<diners>3(?:0[0-5]|[68][0-9])[0-9]{11}) |
(?P<jcb>(?:2131|1800|35\d{3})\d{11})
)$
Regex-Optionen: Freiform
Regex-Varianten: PCRE, Python
Java, Perl 5.6, Perl 5.8 und Ruby 1.8 unterstützen keine benannten Captures. Hier kön-
nen Sie nummerierte Captures verwenden. Gruppe 1 fängt die Visa-Karten, Gruppe 2 die
MasterCards und so weiter bis zur Gruppe 6 für JCB:
^(?:
(4[0-9]{12}(?:[0-9]{3})?) | # Visa
(5[1-5][0-9]{14}) | # MasterCard
(6(?:011|5[0-9][0-9])[0-9]{12}) | # Discover
(3[47][0-9]{13}) | # American Express
(3(?:0[0-5]|[68][0-9])[0-9]{11}) | # Diners Club
((?:2131|1800|35\d{3})\d{11}) # JCB
)$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
JavaScript unterstützt keinen Freiform-Modus. Entfernen wir den Leerraum und die
Kommentare, erhalten wir:
^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|
(6(?:011|5[0-9][0-9])[0-9]{12})|(3[47][0-9]{13})|
(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35\d{3})\d{11}))$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Wenn Sie nicht wissen müssen, um welchen Kartentyp es geht, können Sie die unnötigen
einfangenden Gruppen entfernen:
^(?:
4[0-9]{12}(?:[0-9]{3})? | # Visa
5[1-5][0-9]{14} | # MasterCard
6(?:011|5[0-9][0-9])[0-9]{12} | # Discover
3[47][0-9]{13} | # American Express
3(?:0[0-5]|[68][0-9])[0-9]{11} | # Diners Club
(?:2131|1800|35\d{3})\d{11} # JCB
)$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

4.19 Kreditkartennummern validieren | 289


Für JavaScript:
^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|
3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Folgen Sie der Anleitung in Rezept 3.6, um Ihrem Bestellformular diesen regulären Aus-
druck hinzuzufügen und die Kreditkartennummer zu überprüfen. Wenn Sie unterschied-
liche Serviceunternehmen für verschiedene Karten nutzen oder einfach selbst eine
Statistik führen wollen, können Sie wie in Rezept 3.9 prüfen, welche benannten oder
nummerierten einfangenden Gruppen die Übereinstimmung enthalten. Damit erfahren
Sie, von welcher Firma die Karte Ihres Kunden ist.

Beispiel-Webseite mit JavaScript


<html>
<head>
<title>Kreditkartentest</title>
</head>

<body>
<h1>Kreditkartentest</h1>

<form>
<p>Bitte geben Sie Ihre Kreditkartennummer ein:</p>

<p><input type="text" size="20" name="cardnumber"


onkeyup="validatecardnumber(this.value)"></p>

<p id="notice">(keine Kartennummer eingegeben)</p>


</form>

<script>
function validatecardnumber(cardnumber) {
// Leerzeichen und Bindestriche entfernen
cardnumber = cardnumber.replace(/[ -]/g, '');
// Prüfen, ob die Karte gültig ist
// Die Regex fängt die Nummer in einer der einfangenden Gruppen
var match = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|
(6(?:011|5[0-9][0-9])[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])
[0-9]{11})|((?:2131|1800|35\d{3})\d{11}))$/.exec(cardnumber);
if (match) {
// Liste der Kartentypen in der gleichen Reihenfolge wie die einfangenden Gruppen
var types = ['Visa', 'MasterCard', 'Discover', 'American Express',
'Diners Club', 'JCB'];
// Einfangende Gruppe finden, die passt
// Das nullte Element des Match-Arrays überspringen (das Gesamtsuchergebnis)
for (var i = 1; i < match.length; i++) {
if (match[i]) {
// Kartentyp für diese Gruppe anzeigen
document.getElementById('notice').innerHTML = types[i - 1];

290 | Kapitel 4: Validierung und Formatierung


break;
}
}
} else {
document.getElementById('notice').innerHTML = '(Ungültige Kartennummer)';
}
}
</script>
</body>
</html>

Diskussion
Leerzeichen und Bindestriche entfernen
Auf Kreditkarten sind die in die Karte eingestanzten Ziffern meist in Vierergruppen
unterteilt. So lässt sich die Kartennummer leichter lesen. Natürlich werden viele Leute
versuchen, ihre Kartennummer auch genau so auf einer Webseite einzugeben – ein-
schließlich der Leerzeichen.
Schreibt man einen regulären Ausdruck, der eine Kartennummer validieren soll und
dabei Leerzeichen, Bindestriche und was auch immer berücksichtigen will, ist das deut-
lich schwieriger als einer, der nur Ziffern zulässt. Um daher den Kunden nicht damit zu
nerven, dass er die Kartennummer nochmals ohne Leerzeichen oder Bindestriche einge-
ben soll, entfernen Sie sie einfach vor dem Überprüfen der Nummer und der Übermitt-
lung an die Karten-Servicegesellschaft.
Der reguläre Ausdruck ‹[z-]› passt auf ein Leerzeichen oder einen Bindestrich. Ersetzen
Sie alle Übereinstimmungen dieses regulären Ausdrucks durch einen leeren String, wer-
den damit alle Leerzeichen und Bindestriche entfernt.
Kreditkartennummern können nur aus Ziffern bestehen. Statt mit ‹[z-]› lediglich Leer-
zeichen und Bindestriche zu entfernen, können Sie auch die Zeichenklassenabkürzung
‹\D› nutzen, um alles zu entfernen, was keine Ziffer ist.

Validieren der Nummer


Jede Kreditkartenfirma verwendet ein anderes Nummernformat. Wir nutzen diese Unter-
schiede, damit der Anwender eine Nummer eingeben kann, ohne die Kartenfirma ange-
ben zu müssen. Die Firma kann dann aus der Nummer ermittelt werden. Die Formate
sind:
Visa
13 oder 16 Ziffern, beginnend mit einer 4.
MasterCard
16 Ziffern, beginnend mit 51 bis 55.
Discover
16 Ziffern, beginnend mit 6011 oder 65.

4.19 Kreditkartennummern validieren | 291


American Express
15 Ziffern, beginnend mit 34 oder 37.
Diners Club
14 Ziffern, beginnend mit 300 bis 305, 36 oder 38.
JCB
15 Ziffern, beginnend mit 2131 oder 1800, oder 16 Ziffern, beginnend mit 35.
Wenn Sie nur bestimmte Kartenfirmen akzeptieren, können Sie die Karten aus der Regex
entfernen, die Sie nicht haben wollen. Beim Entfernen von JCB achten Sie darauf, auch
das letzte ‹|› zu entfernen. Endet Ihr regulärer Ausdruck mit ‹||› oder ‹|)›, werden
auch leere Strings als gültige Kartennummer akzeptiert.
Um zum Beispiel nur Visa, MasterCard und American Express zu akzeptieren, nutzen
Sie:
^(?:
4[0-9]{12}(?:[0-9]{3})? | # Visa
5[1-5][0-9]{14} | # MasterCard
3[47][0-9]{13} # AMEX
)$
Regex-Optionen: Freiform
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Oder alternativ:
^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Suchen Sie in einem längeren Text nach Kreditkartennummern, ersetzen Sie die Anker
durch Wortgrenzen (‹\b›).

Einbau der Lösung in eine Webseite


Das Beispiel in „Beispiel-Webseite mit JavaScript“ auf Seite 290 zeigt, wie Sie diese bei-
den regulären Ausdrücke in Ihr Bestellformular einbauen können. Das Eingabefeld für
die Kreditkartennummer hat einen Event-Handler onkeyup, der die Funktion validate-
cardnumber() aufruft. Diese Funktion liest die Kartennummer aus dem Eingabefeld aus,
entfernt Leerzeichen und Bindestriche und führt dann mithilfe des regulären Ausdrucks
mit nummerierten einfangenden Gruppen eine Validierung durch. Das Ergebnis dieser
Überprüfung wird angezeigt, indem der Text im letzten Absatz auf der Seite ersetzt wird.
Hat der reguläre Ausdruck keinen Erfolg, liefert regexp.exec() den Wert null zurück, und
es wird (Ungültige Kartennummer) angezeigt. Passt die Regex, liefert regexp.exec() ein
String-Array zurück. Das nullte Element dieses Arrays enthält das vollständige Suchergeb-
nis. In den Elementen 1 bis 6 finden sich die Ergebnisse der sechs einfangenden Gruppen.

292 | Kapitel 4: Validierung und Formatierung


Unser regulärer Ausdruck hat sechs einfangende Gruppen, die in den Alternativen einer
Alternation untergebracht sind. Das bedeutet, dass immer nur genau eine Gruppe an der
Übereinstimmung beteiligt ist und die Kartennummer enthält. Die anderen Gruppen
sind dann leer (entweder undefined, oder sie enthalten einen leeren String – das hängt
von Ihrem Browser ab). Die Funktion prüft nacheinander die sechs einfangenden Grup-
pen. Findet sie eine, die nicht leer ist, wird die Kartenfirma erkannt und ausgegeben.

Zusätzliche Validierung mit dem Luhn-Algorithmus


Es gibt eine zusätzliche Validierungsmöglichkeit für Kreditkartennummern, bevor die
Bestellung wirklich durchgeführt wird. Die letzte Ziffer in der Kartennummer ist eine
Prüfsumme, die nach dem Luhn-Algorithmus berechnet wird. Da für diesen Algorithmus
ein paar (wenn auch einfache) Kalkulationen notwendig sind, können Sie ihn nicht mit
einem regulären Ausdruck implementieren.
Sie können Ihr Webseitenbeispiel für dieses Rezept mit dem Luhn-Algorithmus ergän-
zen, indem Sie vor der else-Zeile in der Funktion validatecardnumber() den Aufruf
luhn(cardnumber); einfügen. So wird die Luhn-Prüfung nur dann durchgeführt, wenn der
reguläre Ausdruck eine Übereinstimmung gefunden hat und nachdem die Kartenart
ermittelt wurde. Allerdings ist das Bestimmen der Kartenart für die Luhn-Prüfung nicht
notwendig. Alle Kreditkarten nutzen die gleiche Methode.
In JavaScript können Sie den Luhn-Algorithmus wie folgt implementieren:
function luhn(cardnumber) {
// Aufbau eines Arrays mit den Ziffern der Kartennummer
var getdigits = /\d/g;
var digits = [];
while (match = getdigits.exec(cardnumber)) {
digits.push(parseInt(match[0], 10));
}
// Luhn-Algorithmus für die Ziffern ausführen
var sum = 0;
var alt = false;
for (var i = digits.length - 1; i >= 0; i--) {
if (alt) {
digits[i] *= 2;
if (digits[i] > 9) {
digits[i] -= 9;
}
}
sum += digits[i];
alt = !alt;
}
// Prüfung der Kartennummer
if (sum % 10 == 0) {
document.getElementById("notice").innerHTML += '; Luhn-Prüfung erfolgreich';
} else {
document.getElementById("notice").innerHTML += '; Luhn-Prüfung nicht erfolgreich';
}
}

4.19 Kreditkartennummern validieren | 293


Diese Funktion übernimmt einen String mit der Kreditkartennummer als Parameter. Die
Kartennummer sollte nur aus Ziffern bestehen. In unserem Beispiel hat validatecardnum-
ber() schon Leerzeichen und Bindestriche entfernt und ermittelt, ob die Kartennummer
die richtige Anzahl an Ziffern besitzt.
In der Funktion wird zunächst der reguläre Ausdruck ‹\d› verwendet, um über alle Zif-
fern im String iterieren zu können. Beachten Sie dabei den Modifikator /g. Innerhalb der
Schleife findet sich in match[0] die Ziffer. Da reguläre Ausdrücke nur mit Texten arbeiten,
rufen wir parseInt() auf, um sicherzustellen, dass die Variable als Integer und nicht als
String gespeichert wird. Tun wir das nicht, findet sich später in der Variablen sum eine
String-Verkettung von Ziffern und nicht die aufsummierten Werte.
Der eigentliche Algorithmus läuft über das Array und berechnet eine Prüfsumme. Lässt
sich diese Summe ohne Rest durch 10 teilen, ist die Kartennummer gültig.

4.20 Europäische Umsatzsteuer-Identifikationsnummern


Problem
Sie sollen ein Onlinebestellformular für eine Firma in der Europäischen Union erstellen.
Kauft eine für die Umsatzsteuer registrierte Firma (Ihr Kunde), die in einem EU-Land
sitzt, von einem Verkäufer (Ihre Firma) in einem anderen EU-Land etwas, muss der Ver-
käufer nach EU-Steuerrecht keine Umsatzsteuer berechnen. Hat der Käufer keine
Umsatzsteuer-Identifikationsnummer (USt-IdNr.) angegeben, muss der Verkäufer die
Mehrwertsteuer berechnen und sie an das lokale Finanzamt abführen. Um das zu vermei-
den, nutzt der Verkäufer die USt-IdNr. des Käufers als Beweis, dass keine Steuer fällig ist.
Für den Verkäufer ist es also sehr wichtig, die USt-IdNr. des Käufers zu überprüfen,
bevor er die Bestellung ohne Umsatzsteuer durchführt.
Die häufigste Ursache für ungültige Umsatzsteuer-Identifikationsnummern sind einfache
Tippfehler vom Kunden. Um den Bestellprozess schneller und einfacher zu gestalten,
sollten Sie die USt-IdNr. mit einer Regex überprüfen, während der Kunde das Bestellfor-
mular ausfüllt. Das lässt sich mit etwas JavaScript-Code auf Clientseite oder im CGI-
Skript auf Ihrem Webserver erreichen. Passt die Nummer nicht zum regulären Ausdruck,
kann der Kunde den Tippfehler direkt korrigieren.

Lösung
Leerzeichen, Bindestriche und Punkte entfernen
Lesen Sie die vom Kunden eingegebene USt-IdNr. aus und speichern Sie sie in einer Vari-
ablen. Bevor Sie die Gültigkeit der Nummer prüfen, ersetzen Sie die durch folgenden
regulären Ausdruck gefundenen Übereinstimmungen mit einem leeren String:

294 | Kapitel 4: Validierung und Formatierung


[-.z]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In Rezept 3.14 wird gezeigt, wie Sie diese Ersetzung durchführen können. Wir sind
davon ausgegangen, dass der Kunde abgesehen von Bindestrichen, Punkten und Leerzei-
chen keine anderen Satzzeichen eingegeben hat. Jegliches weitere „falsche“ Zeichen wird
von der nächsten Prüfung abgefangen.

Überprüfen der Nummer


Nachdem Leerzeichen, Punkte und Bindestriche entfernt wurden, prüft dieser reguläre
Ausdruck, ob die USt-IdNr. für einen der 27 EU-Staaten gültig ist:
^(
(AT)?U[0-9]{8} | # Österreich
(BE)?0?[0-9]{9} | # Belgien
(BG)?[0-9]{9,10} | # Bulgarien
(CY)?[0-9]{8}L | # Zypern
(CZ)?[0-9]{8,10} | # Tschechische Republik
(DE)?[0-9]{9} | # Deutschland
(DK)?[0-9]{8} | # Dänemark
(EE)?[0-9]{9} | # Estland
(EL|GR)?[0-9]{9} | # Griechenland
(ES)?[0-9A-Z][0-9]{7}[0-9A-Z] | # Spanien
(FI)?[0-9]{8} | # Finnland
(FR)?[0-9A-Z]{2}[0-9]{9} | # Frankreich
(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3}) | # Großbritannien
(HU)?[0-9]{8} | # Ungarn
(IE)?[0-9]S[0-9]{5}L | # Irland
(IT)?[0-9]{11} | # Italien
(LT)?([0-9]{9}|[0-9]{12}) | # Litauen
(LU)?[0-9]{8} | # Luxemburg
(LV)?[0-9]{11} | # Lettland
(MT)?[0-9]{8} | # Malta
(NL)?[0-9]{9}B[0-9]{2} | # Niederlande
(PL)?[0-9]{10} | # Polen
(PT)?[0-9]{9} | # Portugal
(RO)?[0-9]{2,10} | # Rumänien
(SE)?[0-9]{12} | # Schweden
(SI)?[0-9]{8} | # Slowenien
(SK)?[0-9]{10} # Slowakei
)$
Regex-Optionen: Freiform, Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck verwendet den Freiform-Modus, um ein späteres Bearbeiten
der Regex zu vereinfachen. Schließlich nimmt die EU gelegentlich auch neue Länder auf,
oder die Staaten passen ihre Regeln für die USt-IdNr. an. Leider ist in JavaScript kein
Freiform-Modus möglich. Dort müssen Sie alles in einer Zeile unterbringen:

4.20 Europäische Umsatzsteuer-Identifikationsnummern | 295


^((AT)?U[0-9]{8}|(BE)?0?[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|
(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|
(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|
(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|
(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|
(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|
(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|
(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In Rezept 3.6 wird beschrieben, wie Sie diesen regulären Ausdruck auf Ihrem Bestellfor-
mular unterbringen können.

Diskussion
Leerzeichen, Punkte und Bindestriche entfernen
Damit die Umsatzsteuer-Identifikationsnummern für Menschen leichter lesbar sind, wer-
den sie häufig mit zusätzlichen Trennzeichen eingegeben. So könnte ein deutscher Kunde
seine USt-IdNr. DE123456789 zum Beispiel als DE 123.456.789 eingeben.
Ein einzelner regulärer Ausdruck, der die Nummern aus 27 Ländern in allen möglichen
Schreibweisen erkennt, ist unmöglich zu realisieren. Da die Trennzeichen nur der Lesbar-
keit dienen, ist es viel einfacher, zunächst alle diese Zeichen zu entfernen und dann die
reine USt-IdNr. zu analysieren.
Der reguläre Ausdruck ‹[-.z]› passt zu einem Zeichen, das ein Bindestrich, ein Punkt
oder ein Leerzeichen ist. Ersetzt man alle Übereinstimmungen dieses regulären Aus-
drucks durch einen leeren String, werden diese Zeichen aus den Nummern entfernt.
Umsatzsteuer-Identifikationsnummern bestehen nur aus Buchstaben und Ziffern. Statt
lediglich die am häufigsten eingegebenen Trennzeichen mit ‹[-.z]› zu entfernen, kön-
nen Sie auch alle ungültigen Zeichen mit ‹[^A-Z0-9]› eliminieren.

Validieren der Nummer


Die zwei regulären Ausdrücke für das Validieren der Nummer sind identisch. Nur nutzt
der erste den Freiform-Modus, damit er leichter lesbar ist. JavaScript unterstützt diesen
Modus nicht, aber bei den anderen Varianten haben die Sie freie Wahl.
Die Regex nutzt eine große Alternation, um die Umsatzsteuer-Identifikationsnummern
aller 27 EU-Staaten berücksichtigen zu können. Die Formate sehen so aus:
Belgien
999999999 oder 0999999999
Bulgarien
999999999 oder 9999999999

296 | Kapitel 4: Validierung und Formatierung


Dänemark
99999999
Deutschland
999999999
Estland
999999999
Finnland
99999999
Frankreich
XX999999999
Griechenland
999999999
Großbritannien
999999999, 999999999999 oder XX999
Irland
9S99999L
Italien
99999999999
Lettland
99999999999
Litauen
999999999 oder 99999999999
Luxemburg
99999999
Malta
99999999
Niederlande
999999999B99
Österreich
U99999999
Polen
999999999
Portugal
999999999
Rumänien
99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999 oder 9999999999
Schweden
99999999999
Slowakei
999999999

4.20 Europäische Umsatzsteuer-Identifikationsnummern | 297


Slowenien
99999999
Spanien
X9999999X
Tschechische Republik
99999999, 999999999 oder 9999999999
Ungarn
99999999
Zypern
99999999L
Streng genommen ist der zweistellige Ländercode Teil der USt-IdNr. Aber viele lassen ihn
häufig weg, da die Rechnungsanschrift schon den Staat angibt. Der reguläre Ausdruck
akzeptiert daher die Nummern mit und ohne Ländercode. Wenn Sie wollen, dass er ein-
gegeben werden muss, entfernen Sie alle Fragezeichen aus dem regulären Ausdruck.
Dann sollten Sie aber auch in der Fehlermeldung, die den Anwender auf eine ungültige
USt-IdNr. hinweist, erwähnen, dass der Ländercode eingegeben werden muss.
Akzeptieren Sie Bestellungen nur aus bestimmten Ländern, können Sie die Länder aus
der Regex weglassen, die in der Länderauswahl auf Ihrem Bestellformular vorhanden
sind. Löschen Sie eine der Alternativen, müssen Sie auch den Operator ‹|› löschen, der
die Alternativen untereinander trennt. Tun Sie das nicht, steht in Ihrem regulären Aus-
druck ‹||›. Das führt aber zu einer Alternative, die einen leeren String akzeptiert, sodass
Ihr Bestellformular letztendlich auch ohne USt-IdNr. fertiggestellt werden kann.
Die 27 Alternativen sind in einer Gruppe zusammengefasst. Diese Gruppe umfasst den
gesamten Bereich zwischen einem Zirkumflex und einem Dollar, wodurch der reguläre
Ausdruck mit Anfang und Ende des zu überprüfenden Strings verbunden ist. Die gesamte
Eingabe muss also eine gültige USt-IdNr. sein.
Suchen Sie in einem längeren Text nach Umsatzsteuer-Identifikationsnummern, ersetzen
Sie die Anker durch die Wortgrenzen ‹\b›.

Variationen
Der Vorteil eines regulären Ausdrucks für alle 27 Staaten liegt darin, dass Sie in Ihrem
Formular nur eine Regex-Überprüfung benötigen. Sie könnten aber auch 27 getrennte
Regexes nutzen. Zuerst prüfen Sie das Land, das der Kunde in der Rechnungsanschrift
angegeben hat. Dann validieren Sie die USt-IdNr. abhängig vom Land:
Belgien
‹^(BE)?0?[0-9]{9}$›
Bulgarien
‹^(BG)?[0-9]{9,10}$›
Dänemark
‹^(DK)?[0-9]{8}$›

298 | Kapitel 4: Validierung und Formatierung


Deutschland
‹^(DE)?[0-9]{9}$›
Estland
‹^(EE)?[0-9]{9}$›
Finnland
‹^(FI)?[0-9]{8}$›
Frankreich
‹^(FR)?[0-9A-Z]{2}[0-9]{9}$›
Griechenland
‹^(EL|GR)?[0-9]{9}$›
Großbritannien
‹^(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})$›
Irland
‹^(IE)?[0-9]S[0-9]{5}L$›
Italien
‹^(IT)?[0-9]{11}$›
Lettland
‹^(LV)?[0-9]{11}$›
Litauen
‹^(LT)?([0-9]{9}|[0-9]{12})$›
Luxemburg
‹^(LU)?[0-9]{8}$›
Malta
‹^(MT)?[0-9]{8}$›
Niederlande
‹^(NL)?[0-9]{9}B[0-9]{2}$›
Österreich
‹^(AT)?U[0-9]{8}$›
Polen
‹^(PL)?[0-9]{10}$›
Portugal
‹^(PT)?[0-9]{9}$›
Rumänien
‹^(RO)?[0-9]{2,10}$›
Schweden
‹^(SE)?[0-9]{12}$›
Slowakei
‹^(SK)?[0-9]{10}$›
Slowenien
‹^(SI)?[0-9]{8}$›
Spanien
‹^(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]$›

4.20 Europäische Umsatzsteuer-Identifikationsnummern | 299


Tschechische Republik
‹^(CZ)?[0-9]{8,10}$›
Ungarn
‹^(HU)?[0-9]{8}$›
Zypern
‹^(CY)?[0-9]{8}L$›
Implementieren Sie Rezept 3.6, um die USt-IdNr. mit der ausgewählten Regex zu validie-
ren. Damit erfahren Sie, ob die Nummer für das Land, das der Kunde angegeben hat gül-
tig ist.
Der Hauptvorteil der getrennten regulären Ausdrücke ist, dass die USt-IdNr. auf jeden
Fall mit der korrekten Länderkennung beginnen kann, ohne dass der Kunde sie angeben
muss. Passt der reguläre Ausdruck auf die angegebene Nummer, prüfen Sie den Inhalt
der ersten einfangenden Gruppe. In Rezept 3.9 wird beschrieben, wie Sie das machen
können. Ist die erste einfangende Gruppe leer, hat der Kunde den Ländercode nicht mit
eingegeben. Sie können ihn dann selbst ergänzen, bevor Sie die überprüfte Nummer in
Ihrer Bestelldatenbank ablegen.
Griechische Umsatzsteuer-Identifikationsnummern können zwei verschiedene Länder-
codes haben. EL wird traditionell für griechische Nummern verwendet, aber GR ist der
ISO-Ländercode für Griechenland.

Siehe auch
Der reguläre Ausdruck prüft nur, ob die Nummer wie eine gültige USt-IdNr. aussieht.
Das reicht aus, um echte Fehler auszuschließen. Aber ein regulärer Ausdruck kann offen-
sichtlich nicht prüfen, ob die Nummer der Firma zugeordnet ist, die die Bestellung auf-
gibt. Die Europäische Union hat eine Website (http://ec.europa.eu/taxation_customs/vies/
vieshome.do), auf der Sie prüfen können, zu welcher Firma eine bestimmte USt-IdNr.
gehört – wenn sie denn überhaupt zugewiesen ist. Die Nummern werden mit der Daten-
bank des entsprechenden Staats verglichen. Manche Staaten bestätigen dabei allerdings
nur eine Gültigkeit, ohne weitere Informationen über die entsprechende Firma herauszu-
geben.
Die in diesem regulären Ausdruck genutzten Techniken werden in den Rezepten 2.3, 2.5
und 2.8 besprochen.

300 | Kapitel 4: Validierung und Formatierung


KAPITEL 5
Wörter, Zeilen und Sonderzeichen

Dieses Kapitel enthält Rezepte für das Finden und Bearbeiten von Texten. Mit einigen
dieser Rezepte erreichen Sie Dinge, die Sie vielleicht von einer ausgefuchsten Such-
Engine erwarten – so zum Beispiel das Finden eines von mehreren Wörtern oder das Fin-
den von Wörtern, die nahe beieinanderstehen. Andere Beispiele helfen Ihnen dabei,
ganze Zeilen zu finden, die bestimmte Wörter enthalten, Wortwiederholungen zu entfer-
nen oder Meta-zeichen in regulären Ausdrücken zu maskieren.
Vor allem geht es in diesem Kapitel aber darum, Regex-Konstrukte und -Techniken im
echten Einsatz zu präsentieren. Lesen Sie sich die Rezepte durch, ist das wie ein Training
für einen ganzen Reigen von Regex-Features. Damit fällt es Ihnen in Zukunft leichter,
reguläre Ausdrücke auf eigene Probleme anzuwenden. In vielen Fällen ist das, wonach
wir suchen, einfach, aber die Vorlagen, die wir in den Lösungen bereitstellen, ermögli-
chen es Ihnen, sie an Ihre eigenen Probleme anzupassen.

5.1 Ein bestimmtes Wort finden


Problem
Sie haben die einfache Aufgabe, alle Vorkommen des Worts „rot“ zu finden, unabhängig
von Groß- oder Kleinschreibung. Entscheidend ist, dass es ein vollständiges Wort sein
muss. Sie wollen keine Teile längerer Wörter finden, wie zum Beispiel Brot, Karotte oder
rotieren.

Lösung
Tokens für Wortgrenzen sorgen für eine ganze einfache Lösung:
\brot\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

| 301
In Rezept 3.7 wird gezeigt, wie Sie mit diesem regulären Ausdruck alle Übereinstimmun-
gen finden können. In Rezept 3.14 ist beschrieben, wie Sie die Übereinstimmungen
durch anderen Text ersetzen können.

Diskussion
Die Wortgrenzen an beiden Enden des regulären Ausdrucks stellen sicher, dass rot nur
dann gefunden wird, wenn es als eigenständiges Wort auftaucht. Genauer gesagt,
erzwingen die Wortgrenzen, dass rot von anderem Text durch den Anfang oder das Ende
des Strings, durch Whitespace, Satzzeichen oder andere Nicht-Wortzeichen getrennt ist.
Regex-Engines betrachten Buchstaben, Ziffern und Unterstriche als Wortzeichen. Wort-
grenzen werden detaillierter in Rezept 2.6 behandelt.
Es kann aber ein Problem geben, wenn man in JavaScript, in der PCRE und in Ruby mit
internationalen Texten arbeitet, da diese Regex-Varianten nur Buchstaben aus der ASCII-
Tabelle zum Erstellen der Wortgrenzen in Betracht ziehen. Die Wortgrenzen werden
also nur an Positionen zwischen ‹^|[^A-Za-z0-9_]› und ‹[A-Za-z0-9_]› oder zwischen
‹[A-Za-z0-9_]› und ‹[^A-Za-z0-9_]|$› gefunden. Das Gleiche gilt für Python, wenn die
Option UNICODE oder U nicht gesetzt ist. Damit wird leider verhindert, dass man ‹\b› in
Sprachen für Suchen nach ganzen Wörtern einsetzen kann, die akzentuierte Buchstaben
oder Wörter mit nicht lateinischen Schriftsystemen enthalten. So wird zum Beispiel in
JavaScript, in der PCRE und in Ruby durch ‹\büber\b› eine Übereinstimmung in darüber
gefunden, aber nicht in dar über. In den meisten Fällen ist das genau das Gegenteil von
dem, was Sie wollen. Das Problem ist, dass ü als Nicht-Wortzeichen angesehen und
daher eine Wortgrenze zwischen den beiden Zeichen rü gefunden wird. Dagegen gibt es
dann keine Wortgrenze zwischen einem Leerzeichen und ü, da beide als Nicht-Wortzei-
chen betrachtet werden.
Sie können dieses Problem durch die Verwendung von Lookaheads und Lookbehinds
(gemeinsam als Lookarounds bezeichnet) statt von Wortgrenzen umgehen. Wie Wort-
grenzen passen Lookarounds auf Übereinstimmungen der Länge null. In der PCRE
(wenn sie mit UTF-8-Unterstützung kompiliert wurde) und Ruby 1.9 können Sie auf
Unicode basierende Wortgrenzen zum Beispiel durch ‹(?<=\P{L}|^)rot(?=\P{L}|$)›
emulieren. Dieser reguläre Ausdruck verwendet zudem die Tokens für negierte Unicode-
Eigenschaften, die in Rezept 2.7 besprochen werden. Lookarounds werden in
Rezept 2.16 erläutert. Wenn Sie bei den Lookarounds auch Ziffern und den Unterstrich
als Wortzeichen behandelt haben wollen (wie dies ‹\b› ebenfalls tut), ersetzen Sie die
beiden Instanzen von ‹\P{L}› durch die Zeichenklasse ‹[^\p{L}\p{N}_]›.
JavaScript und Ruby 1.8 unterstützen weder Lookbehinds noch Unicode-Eigenschaften.
Sie können die fehlende Unterstützung eines Lookbehinds umgehen, indem Sie das
Nicht-Wortzeichen vor jeder Übereinstimmung mitsuchen und es dann entweder aus
dem Suchergebnis entfernen oder es in den String zurückstecken, wenn Sie die Überein-
stimmungen ersetzen (siehe dazu auch die Beispiele für die Verwendung von Teilen einer
Übereinstimmung in Rezept 3.15). Die zusätzlich fehlende Unterstützung von Unicode-

302 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Eigenschaften (zusammen mit der Tatsache, dass die Tokens ‹\w› und ‹\W› in beiden
Programmiersprachen nur ASCII-Zeichen finden) bedeutet, dass Sie eventuell eine res-
triktivere Lösung nutzen müssen. Die Codepoints der Buchstabenkategorie sind über den
gesamten Unicode-Zeichensatz verteilt, sodass man Tausende von Zeichen bräuchte, um
‹\p{L}› mit Unicode-Maskierungsfolgen und Zeichenklassenbereichen zu emulieren. Ein
guter Kompromiss könnte ‹[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\xFF]› sein, der
alle Unicode-Buchstaben im achtbittigen Adressraum findet – also in den ersten 256 Uni-
code-Codepoints von 0x00 bis 0xFF (in Abbildung 5-1 finden Sie eine Liste der gefunde-
nen Zeichen). Mit dieser Zeichenklasse finden Sie (oder schließen mit der negierten Form
aus) eine Reihe von häufig genutzten akzentuierten Zeichen, die sich außerhalb des
adressierbaren Bereichs von 7-Bit-ASCII befinden.

0 1 2 3 4 5 6 7 8 9 A B C D E F

4 A B C D E F G H I J K L M N O
5 P Q R S T U V W X Y Z
6 a b c d e f g h i j k l m n o
7 p q r s t u v w x y z

A ª
B µ º
C À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï
D Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß
E à á â ã ä å æ ç è é ê ë ì í î ï
F ð ñ ò ó ô õ ö ø ù ú û ü ý þ ÿ

Abbildung 5-1: Unicode-Buchstaben im 8-Bit-Adressraum

Im Folgenden finden Sie ein Beispiel dafür, wie Sie in JavaScript alle Vorkommen des
Worts „rot“ durch „grün“ ersetzen können. Es achtet korrekt auf häufiger genutzte
Akzentzeichen, sodass ërot nicht verändert wird. Dazu müssen Sie Ihre eigene Zeichen-
klasse erstellen, statt sich auf die eingebauten Tokens ‹\b› oder ‹\w› verlassen zu können:
// 8-Bit-Buchstaben
var L = 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\xFF';
var pattern = '([^{L}]|^)rot([^{L}]|$)'.replace(/{L}/g, L);
var regex = new RegExp(pattern, 'gi');

// rot durch grün ersetzen und alle zusätzlich gefundenen Zeichen


// wieder einfügen
subject = subject.replace(regex, '$1grün$2');

Beachten Sie, dass String-Literale in JavaScript \xHH verwenden (wobei HH eine zweistel-
lige Hexadezimalzahl ist), um Sonderzeichen einzufügen. Daher enthält die Variable L,
die an den regulären Ausdruck übergeben wird, direkt die literalen Versionen der Zei-
chen. Wenn Sie wollen, dass die Metafolgen \xHH direkt an die Regex übergeben werden,
müssen Sie den Backslash im String-Literal maskieren (zum Beispiel durch "\\xHH").

5.1 Ein bestimmtes Wort finden | 303


Allerdings macht das in diesem Fall keinen Unterschied und wird auch die Suchergeb-
nisse nicht verändern.

Siehe auch
Rezepte 5.2, 5.3 und 5.4.

5.2 Eines von mehreren Wörtern finden


Problem
Sie wollen ein Wort aus einer Liste von Wörtern finden, ohne den Ausgangstext mehr-
fach zu durchsuchen.

Lösung
Alternation
Die einfache Lösung ist die Verwendung einer Alternation, um aus den fraglichen Wör-
tern auswählen zu können:
\b(?:eins|zwei|drei)\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Kompliziertere Beispiele für das Finden ähnlicher Wörter werden in Rezept 5.3 vorge-
stellt.

Beispiellösung in JavaScript
var subject = 'Zwei mal eins plus eins ergibt drei.';

var regex = /\b(?:eins|zwei|drei)\b/gi;

subject.match(regex);
// Gibt Array mit vier Treffern zurück: ['Zwei','eins','eins','drei']

// Diese Funktion macht das Gleiche, akzeptiert aber ein Wort-Array mit
// zu findenden Texten. Jegliche Regex-Metazeichen in den Wörtern werden
// vor der Suche mit einem Backslash maskiert.

function match_words (subject, words) {


var regex_metachars = /[(){}[\]*+?.\\^$|,\-]/g;

for (var i = 0; i < words.length; i++) {


words[i] = words[i].replace(regex_metachars, '\\$&');
}

304 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


var regex = new RegExp('\\b(?:' + words.join('|') + ')\\b', 'gi');

return subject.match(regex) || [];


}

match_words(subject, ['eins','zwei','drei']);
// Gibt Array mit vier Treffern zurück: ['Zwei','eins','eins','drei']

Diskussion
Alternation
Es gibt drei Elemente in diesem regulären Ausdruck: die Wortgrenzen auf beiden Seiten,
die nicht-einfangende Gruppe und die Liste mit Wörtern (die durch den Alternationsope-
rator ‹|› voneinander getrennt sind). Die Wortgrenzen stellen sicher, dass die Regex-
Übereinstimmung nicht Teil eines längeren Worts ist. Die nicht-einfangende Gruppe
schränkt die Reichweite des Alternationsoperators ein. Würde sie nicht genutzt werden,
müssten Sie ‹\beins\b|\bzwei\b|\bdrei\b› schreiben, um das gleiche Ergebnis zu erzie-
len. Jedes der Wörter passt einfach auf sich selbst.
Da die Regex-Engine von links nach rechts versucht, ein Wort in der Liste zu finden, errei-
chen Sie vielleicht einen kleinen Performancegewinn, wenn Sie die am wahrscheinlichsten
zu findenden Wörter an den Anfang der Liste stellen. Weil die Wörter auf beiden Seiten
durch Wortgrenzen umschlossen sind, können sie in beliebiger Reihenfolge angegeben
werden. Ohne die Wortgrenzen kann es wichtig sein, längere Wörter an den Anfang zu
stellen. Ansonsten würden Sie „Juliane“ niemals finden, wenn Sie nach ‹Julia|Juliane›
suchen. Die Regex würde immer auf „Julia“ am Anfang des Worts passen.
Beachten Sie, dass dieser reguläre Ausdruck dazu dient, zu zeigen, wie man etwas mit
einer Wortliste findet. Da in diesem Beispiel ‹zwei› und ‹drei› mit den gleichen beiden
Buchstaben enden, können Sie die Regex-Engine unterstützen, indem Sie sie in
‹\b(?:eins|(?:zw|dr)ei)\b› umschreiben. In Rezept 5.3 finden Sie noch mehr Beispiele,
wie Sie ein Wort effizient aus einer Liste mit ähnlichen Wörtern finden können.

Beispiellösung in JavaScript
Das JavaScript-Beispiel findet Wörter mit der gleichen Wortliste – auf zwei verschiede-
nen Wegen. Beim ersten Ansatz erstellen Sie einfach die Regex und suchen dann im Aus-
gangstext mit der für JavaScript-Strings verfügbaren Methode match. Wenn dieser
Methode ein regulärer Ausdruck mit der Option /g („global“) übergeben wird, liefert sie
ein Array mit allen im String gefundenen Übereinstimmungen zurück. Findet sie nichts,
ist das Ergebnis null.
Der zweite Ansatz nutzt eine Funktion (match_words), der der zu durchsuchende Aus-
gangstext und ein Array mit Wörtern übergeben wird, nach denen gesucht werden soll.
Die Funktion maskiert zunächst alle Regex-Metazeichen, die eventuell in den übergebe-
nen Wörtern vorhanden sind, und verkettet dann die Wortliste in einen neuen regulären

5.2 Eines von mehreren Wörtern finden | 305


Ausdruck, der zum Durchsuchen des Strings genutzt wird. Die Funktion liefert ein Array
mit Übereinstimmungen zurück. Wenn die erzeugte Regex nichts findet, wird ein leeres
Array geliefert. Die gewünschten Wörter können beliebig groß- oder kleingeschrieben
werden, da die Option zum Ignorieren von Groß- und Kleinschreibung (/i) genutzt wird.

Siehe auch
Rezepte 5.1, 5.3 und 5.4.

5.3 Ähnliche Wörter finden


Problem
Hier haben Sie mehrere Probleme:
Sie wollen alle Vorkommen von Jogurt und Joghurt in einem String finden.
Sie wollen eines von drei Wörtern finden, das mit „ot“ endet: rot, tot oder Not.
Sie wollen Wörter finden, die mit phobie enden.
Sie wollen verschiedene Formen des Namens „Martin“ finden: Martin, Marten und
Maarten.
Sie wollen verschiedene Formen des Begriffs „regulärer Ausdruck“ finden.

Lösung
Die regulären Ausdrücke zum Lösen jedes dieser Probleme werden im Folgenden gezeigt.
Alle Lösungen nutzen die Option, Groß- und Kleinschreibung zu ignorieren.

Jogurt oder Joghurt


\bJog?hurt\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

rot, tot oder Not


\b[rtn]ot\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Wörter, die auf „phobie“ enden


\b\w*phobie\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

306 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Martin, Marten oder Maarten
\bMa(?:rtin|a?rten)\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Varianten von „regulärer Ausdruck”


\breg(?:uläre(?:rzAusdruck|zAusdrücke)|ex(?:ps?|e[sn])?)\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Verwenden von Wortgrenzen, um vollständige Wörter zu finden
Alle fünf regulären Ausdrücke nutzen Wortgrenzen (‹\b›), um sicherzustellen, dass sie
immer nur vollständige Wörter finden. Die Muster verwenden eine Reihe von unter-
schiedlichen Vorgehensweisen, um in den zu findenden Wörtern eine gewissen Variabili-
tät zuzulassen.
Lassen Sie uns jeden Ausdruck einmal genauer anschauen.

Jogurt oder Joghurt


Dieser reguläre Ausdruck passt entweder auf Jogurt oder auf Joghurt, aber nicht auf Erd-
beerjoghurt. Er nutzt den Quantor ‹?›, um das davor befindliche „h“ optional zu
machen. Quantoren wie ‹?› funktionieren nicht wie die Platzhalter, die allgemein
bekannt sind. Stattdessen binden sie sich an das direkt davorliegende Element, das ent-
weder ein einzelnes Token (in diesem Fall das literale Zeichen „h“) oder eine Gruppe von
Tokens sein kann, die von Klammern umschlossen sind. Der Quantor ‹?› lässt das vor-
herige Element null oder ein Mal zu. Die Regex-Engine versucht zunächst, das Element
zu finden, an das der Quantor gebunden ist. Funktioniert das nicht, macht sie weiter,
ohne es zu berücksichtigen. Ein Quantor, der auch ein nullmaliges Vorkommen zulässt,
macht das vorherige Element damit optional – und genau das wollen wir ja auch hier.

Rot, tot oder Not


Dieser reguläre Ausdruck verwendet eine Zeichenklasse, um „r“, „t“ oder „n“ zu finden,
gefolgt von den literalen Zeichen „ot“. Sie könnten das Gleiche auch mit
‹\b(?:r|t|n)ot\b›, ‹\b(?:rot|tot|not)\b› oder ‹\brot\b|\btot\b|\bnot\b› erreichen.
Aber immer dann, wenn der Unterschied zwischen den verschiedenen zulässigen Über-
einstimmungen nur in der Wahl aus einer Liste mit Zeichen besteht, nutzen Sie besser
eine Zeichenklasse. Diese bietet eine kürzere und besser lesbare Syntax (denn man kann
Bereiche wie A–Z nutzen und die ganzen vertikalen Striche weglassen), zudem sind die

5.3 Ähnliche Wörter finden | 307


meisten Regex-Engines auch noch auf Zeichenklassen optimiert. Bei einer Alternation
mit vertikalen Strichen muss die Engine den rechenaufwendigen Backtracking-Algorith-
mus nutzen, während Zeichenklassen ein einfacheres Vorgehen ermöglichen.
Eine Warnung müssen wir aber trotzdem aussprechen. Zeichenklassen gehören zu den
am häufigsten missbrauchten Regex-Features. Möglicherweise sind sie nicht immer gut
dokumentiert oder vielleicht hat auch nicht jeder genau auf die Details geachtet. Was
auch immer der Grund ist – machen Sie nicht die gleichen Anfängerfehler. Zeichenklas-
sen können aus ihrer Menge immer nur ein Zeichen gleichzeitig finden – ohne Aus-
nahme.
Im Folgenden stellen wir zwei der häufigsten Fehlanwendungen von Zeichenklassen vor:
Wörter in Zeichenklassen setzen
Klar, ‹[rot]{3}› passt auf rot, aber eben auch auf ort, ttt und alle anderen Kombi-
nationen aus den aufgeführten Zeichen. Das Gleiche gilt für negierte Zeichenklas-
sen wie ‹[^rot]›, die auf jedes Zeichen passen, das nicht r, o oder t ist.
Verwenden des Alternationsoperators innerhalb von Zeichenklassen
Per definitionem ermöglichen Zeichenklassen eine Auswahl zwischen den in ihnen
definierten Zeichen. ‹[a|b|c]› passt auf ein einzelnes Zeichen aus der Menge
„abc|“, was vermutlich nicht das ist, was Sie haben wollen. Und selbst dann enthält
die Klasse einen redundanten vertikalen Balken.
In Rezept 2.3 erfahren Sie alle notwendigen Details, um Zeichenklassen korrekt und effi-
zient anzuwenden.

Wörter, die auf „phobie“ enden


Wie beim vorherigen regulären Ausdruck wird auch hier ein Quantor verwendet, um
unterschiedliche Strings finden zu können. Diese Regex passt zum Beispiel auf Arachno-
phobie und Hexakosioihexekontahexaphobie, und weil ‹*› null Wiederholungen ebenfalls
zulässt, passt auch Phobie allein. Wenn Sie wollen, dass vor „phobie“ mindestens noch
ein Zeichen steht, ändern Sie das ‹*› zu ‹+›.

Martin, Marten oder Maarten


Diese Regex kombiniert eine Reihe von Features, die wir im vorhergehenden Beispiel ver-
wendet haben. Eine nicht-einfangende Gruppe ‹(?:...)› begrenzt die Reichweite des
Alternationsoperators ‹|›. Der innerhalb der Gruppe in der zweiten Alternationsoption
verwendete Quantor ‹?› macht das vorherige Zeichen ‹a› optional. Damit wird die Effi-
zienz (und Kürze) gegenüber dem gleichwertigen ‹\bMa(?:rtin|rten|arten)\b› verbes-
sert. Dasselbe Prinzip ist der Grund dafür, dass der literale String ‹Ma› am Anfang des
regulären Ausdrucks steht und nicht drei Mal in ‹\b(?:Martin|Marten|Maarten)\b› oder
‹\bMartin\b|\bMarten\b|\bMaarten\b› erscheint. Ein paar Regex-Engines, die Back-
tracking nutzen, sind nicht pfiffig genug, zu bemerken, dass jeder Text, der von diesen
letzten beiden Regexes gefunden werden kann, mit Ma beginnen muss. Stattdessen wird

308 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


die Engine durch den String laufen und zunächst versuchen, eine Wortgrenze zu finden;
dann wird sie das folgende Zeichen überprüfen, um herauszufinden, ob es ein M ist.
Wenn nicht, muss die Engine alle alternativen Pfade durch den regulären Ausdruck prü-
fen, bevor sie an der nächsten Position im String erneut ihre Suche beginnen kann. Es ist
für Menschen zwar einfach, zu erkennen, dass das nur unnötige Arbeit ist (da die alterna-
tiven Pfade durch die Regex alle mit „Ma“ beginnen), aber die Engine weiß das nicht.
Wenn Sie die Regex stattdessen als ‹\bMa(?:rtin|a?rten)\b› schreiben, erkennt die
Engine direkt, dass sie keinen String finden kann, der nicht mit diesen Zeichen beginnt.
Eine detailliertere Beschreibung einer Regex-Engine mit Backtracking finden Sie in
Rezept 2.13.

Varianten von „regulärer Ausdruck”


Das letzte Beispiel dieses Rezepts kombiniert Alternation, Zeichenklassen und Quanto-
ren, um eine Reihe gebräuchlicher Varianten des Begriffs „regulärer Ausdruck“ zu finden.
Da der reguläre Ausdruck auf den ersten Blick ein wenig kompliziert aussieht, wollen wir
ihn aufteilen und die Einzelteile genauer anschauen.
Die folgende Regex ist im Freiform-Modus geschrieben, der in JavaScript nicht zur Verfü-
gung steht. Da Whitespace in diesem Modus ignoriert wird, wurde das literale Leerzei-
chen mit einem Backslash maskiert.
\b # Position an einer Wortgrenze sicherstellen.
reg # "reg" finden.
(?: # Gruppieren, aber nicht einfangen ...
uläre # "uläre".
(?: # Gruppieren, aber nicht einfangen ....
r\ Ausdruck # "r Ausdruck" finden.
| # oder ...
\ Ausdrücke # " Ausdrücke" finden.
) # Ende der nicht-einfangenden Gruppe.
| # oder ...
ex # "ex" finden.
(?: # Gruppieren, aber nicht einfangen ...
ps? # "p" oder "ps" finden.
| # oder ...
e # "e" finden.
[sn] # Eines der Zeichen aus der Menge "sn" finden.
) # Ende der nicht-einfangenden Gruppe.
? # Vorige Gruppe null oder ein Mal finden.
) # Ende der nicht-einfangenden Gruppe.
\b # Position an einer Wortgrenze sicherstellen.
Regex-Optionen: Freiform, Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

5.3 Ähnliche Wörter finden | 309


Dieses Muster passt auf einen der folgenden Strings:
• regulärer Ausdruck
• reguläre Ausdrücke
• Regexps
• Regexp
• Regexes
• Regexen
• Regex

Siehe auch
Rezepte 5.1, 5.2 und 5.4.

5.4 Alle Wörter außer einem bestimmten finden


Problem
Sie wollen einen regulären Ausdruck verwenden, um ein beliebiges vollständiges Wort zu
finden – außer rot. Rotieren und andere Wörter, die die Buchstabenkombination „rot“
enthalten, sollen gefunden werden – eben nur nicht rot.

Lösung
Ein negatives Lookahead kann dabei helfen, bestimmte Wörter auszuschließen. In dieser
Regex ist es das Schlüsselelement:
\b(?!rot\b)\w+
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Auch wenn es mit einer negierten Zeichenklasse (geschrieben als ‹[^...]›) leicht ist, alles
bis auf ein bestimmtes Zeichen zu finden, können Sie nicht einfach ‹[^rot]› schreiben,
um alles außer dem Wort rot zu finden. ‹[^rot]› ist eine gültige Regex, aber sie findet
jedes Zeichen außer r, o oder t. Also würde ‹\b[^rot]+\b› das Wort rot erwartungsge-
mäß nicht finden, zusätzlich würde es aber auch rau nicht finden, weil es den verbotenen
Buchstaben r enthält. Der reguläre Ausdruck ‹\b[^r][^o][^t]\w*› ist ebenfalls nicht gut,
da jedes Wort abgewiesen würde, das r als ersten, o als zweiten oder t als dritten Buch-
staben enthält. Zudem werden damit die ersten drei Buchstaben nicht auf Wortzeichen
eingeschränkt, und die Regex passt nur auf Wörter, die mindestens drei Buchstaben ent-
halten, da keine der negierten Zeichenklassen optional ist.

310 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Mit all dem im Hinterkopf wollen wir uns nun anschauen, wie der reguläre Ausdruck,
der dieses Problem lösen soll, aussieht:
\b # Position an einer Wortgrenze sicherstellen.
(?! # Sicherstellen, dass die folgende Regex nicht hier gefunden werden kann ...
rot # "rot" finden.
\b # Position an einer Wortgrenze sicherstellen.
) # Ende des negativen Lookahead.
\w+ # Ein oder mehrere Wortzeichen finden.
Regex-Optionen: Freiform, Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Der Schlüssel zu diesem Muster ist ein negatives Lookahead, das die Syntax ‹(?!...)›
besitzt. Dieses verbietet die Sequenz rot, gefolgt von einer Wortgrenze, ohne zu verhin-
dern, dass diese Buchstaben im Folgenden in einer anderen Reihenfolge oder in einem
längeren oder kürzeren Wort genutzt werden. Es gibt keine Wortgrenze am Ende des
regulären Ausdrucks, da die Regex-Übereinstimmung damit nicht anders aussehen
würde. Der Quantor ‹+› in ‹\w+› wiederholt das Wortzeichen-Token so oft wie möglich,
daher wird die nächste Wortgrenze sowieso erreicht.
Wenn Sie die Regex auf den Ausgangstext Ich rotiere um das Wort rot anwenden, wer-
den fünf Übereinstimmungen geliefert: Ich, rotiere, um, das und Wort.

Variationen
Wörter finden, die ein anderes Wort nicht enthalten
Statt zu versuchen, ein Wort zu finden, das nicht rot ist, können Sie auch versuchen, ein
Wort zu finden, dass das Wort rot nicht enthält, was einen etwas anderen Ansatz erfor-
dert:
\b(?:(?!rot)\w)+\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Im vorigen Abschnitt war die Wortgrenze am Anfang des regulären Ausdrucks ein prakti-
scher Anker, mit dem wir das negative Lookahead am Anfang des Worts fixieren konn-
ten. Die hier verwendete Lösung ist nicht so effizient, aber sie ist doch ein häufig
verwendetes Konstrukt, mit dem man etwas finden kann, was nicht ein bestimmtes Wort
oder Muster enthält. Das wird erreicht durch das Wiederholen einer Gruppe, die ein
negatives Lookahead und ein einzelnes Wortzeichen enthält. Bevor jedes Zeichen gefun-
den wird, stellt die Regex-Engine sicher, dass das Wort rot an der aktuellen Position
nicht passt.
Anders als beim vorherigen regulären Ausdruck wird hier eine abschließende Wortgrenze
benötigt. Ansonsten könnte der erste Teil eines Worts gefunden werden, bevor dort rot
auftaucht.

5.4 Alle Wörter außer einem bestimmten finden | 311


Siehe auch
In Rezept 2.16 finden Sie eine ausführlichere Beschreibung zu Lookarounds (dem Sam-
melbegriff für positive und negative Lookaheads und Lookbehinds).
Rezepte 5.1, 5.5, 5.6 und 5.11.

5.5 Ein beliebiges Wort finden, auf das ein bestimmtes Wort
nicht folgt
Problem
Sie wollen ein beliebiges Wort finden, auf das nicht direkt das Wort rot folgt, wobei
Whitespace, Satzzeichen oder andere Nicht-Wortzeichen dazwischen ignoriert werden.

Lösung
Das Geheimnis dieses regulären Ausdrucks ist ein negatives Lookahead:
\b\w+\b(?!\W+rot\b)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
In den Rezepten 3.7 und 3.14 finden Sie Beispiele dafür, wie Sie diesen regulären Aus-
druck im Code implementieren können.

Diskussion
Wie bei vielen anderen Rezepten in diesem Kapitel arbeiten hier Wortgrenzen (‹\b›) und
das Wortzeichen-Token (‹\w›) Hand in Hand, um ein vollständiges Wort zu finden.
Detailliertere Beschreibungen dieser Features finden Sie in Rezept 2.6.
Das den zweiten Teil dieser Regex umschließende ‹(?!...)› ist ein negatives Lookahead.
Ein Lookahead weist die Regex-Engine an, im String vorläufig weiterzusuchen, um zu
prüfen, ob das Muster innerhalb des Lookahead an der aktuellen Position gefunden wer-
den kann. Dabei werden aber keine Zeichen genutzt, die innerhalb des Lookahead gefun-
den werden. Stattdessen wird nur sichergestellt, dass eine Übereinstimmung möglich ist.
Da wir einen negativen Lookahead verwenden, wird das Ergebnis dieser Zusicherung
umgekehrt. Kann also das Muster innerhalb des Lookahead an der aktuellen Position
gefunden werden, schlägt die Übereinstimmung fehl, und die Regex-Engine geht im Aus-
gangs-String ein Zeichen weiter, um dort mit der Suche fortzufahren. Mehr Infor-
mationen zu Lookaheads (und ihrem Gegenstück, den Lookbehinds) finden Sie in
Rezept 2.16.

312 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Das Muster innerhalb des Lookahead findet zunächst über ‹\W+› eines oder mehrere
Nicht-Wortzeichen, die vor ‹rot› vorhanden sein können. Die Wortgrenze am Ende
stellt sicher, dass wir nur Wörter überspringen, auf die rot als vollständiges Wort folgt
und nicht nur ein längeres Wort, das mit rot beginnt.
Beachten Sie, dass dieser reguläre Ausdruck auch auf das Wort rot passt, solange das fol-
gende Wort nicht ebenfalls rot ist. Wollen Sie rot gar nicht finden, können Sie diese
Regex mit der aus Rezept 5.4 kombinieren und erhalten ‹\b(?!rot\b)\w+\b(?!\W+rot\b)›.

Variationen
Wenn Sie nur Wörter finden wollen, auf die rot folgt (ohne rot und die davorliegenden
Nicht-Wortzeichen mit in die Übereinstimmung aufzunehmen), ändern Sie das negative
Lookahead in ein positives Lookahead und Ihr Stirnrunzeln in ein Lächeln:
\b\w+\b(?=\W+rot\b)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Siehe auch
Rezept 2.16 geht detaillierter auf Lookarounds ein (den Oberbegriff für positive und
negative Lookaheads und Lookbehinds).
Rezepte 5.4 und 5.6.

5.6 Ein beliebiges Wort finden, das nicht hinter


einem bestimmten Wort steht
Problem
Sie wollen ein beliebiges Wort finden, vor dem nicht direkt das Wort rot steht, wobei
Whitespace, Satzzeichen oder anderen Nicht-Wortzeichen dazwischen ignoriert werden.

Lösung
Lookbehind
Mit Lookbehinds können Sie prüfen, ob sich ein bestimmter Text vor einer Position
befindet. Dabei wird die Regex-Engine angewiesen, zwischenzeitlich im String rückwärts
zu springen und zu prüfen, ob dort etwas gefunden werden kann, das genau an der Posi-
tion endet, an der Sie das Lookbehind platziert haben. In Rezept 2.16 finden Sie mehr
Informationen zu Lookbehinds.

5.6 Ein beliebiges Wort finden, das nicht hinter einem bestimmten Wort steht | 313
Die folgenden drei Regexes verwenden negative Lookbehinds, die die Syntax ‹(?<!...)›
haben. Leider haben die in diesem Buch behandelten Regex-Varianten recht unterschied-
liche Vorstellungen davon, was Sie in einem Lookbehind unterbringen können. Daher
sehen die Lösungen alle ein bisschen unterschiedlich aus. Mehr Details finden Sie in
„Diskussion“ auf Seite 315.

Wörter, vor denen nicht „rot“ steht


(?<!\brot\W+)\b\w+
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Variante: .NET
(?<!\brot\W{1,9})\b\w+
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE
(?<!\brot)(?:\W+|^)(\w+)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby 1.9

Lookbehinds simulieren
JavaScript und Ruby 1.8 haben gar kein Lookbehind-Feature, auch wenn sie Lookaheads
unterstützen. Aber da das Lookbehind in dieser Lösung ganz am Anfang der Regex steht,
kann man es wunderbar simulieren, indem man die Regex in zwei Teile aufteilt. Hier ein
JavaScript-Beispiel:
var subject = 'Mein Auto ist rot und gelb.',
main_regex = /\b\w+/g,
lookbehind = /\brot\W+$/i,
lookbehind_type = false, // Negatives Lookbehind
matches = [],
match,
left_context;

while (match = main_regex.exec(subject)) {


left_context = subject.substring(0, match.index);

if (lookbehind_type == lookbehind.test(left_context)) {
matches.push(match[0]);
} else {
main_regex.lastIndex = match.index + 1;
}
}

// Passt auf: ['Mein','Auto','ist','rot','gelb']

314 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Diskussion
Lookbehinds mit fester, endlicher und unendlicher Länge
Der erste reguläre Ausdruck nutzt das negative Lookbehind ‹(?<!\brot\W+)›. Da der
Quantor ‹+› innerhalb des Lookbehind bezüglich der passenden Zeichen keine Ober-
grenze hat, funktioniert diese Version nur mit der .NET-Regex-Variante. Alle anderen in
diesem Buch behandelten Regex-Varianten benötigen eine feste oder zumindest endliche
Länge für Lookbehind-Muster.
Der zweite reguläre Ausdruck ersetzt das ‹+› innerhalb des Lookbehind durch ‹{1,9}›.
Damit lässt er sich mit .NET, Java und der PCRE nutzen, die alle Lookbehinds mit einer
variablen Länge unterstützen, solange es eine bekannte Obergrenze gibt. Ich habe hier
eine maximale Länge von neun Nicht-Wortzeichen genutzt, um die Wörter zu trennen.
Damit kann man ein paar Satzzeichen und leere Zeilen zwischen den Wörtern finden.
Sofern Sie nicht mit sehr ungewöhnlichen Ausgangstexten arbeiten, wird das vermutlich
zu genau den gleichen Ergebnissen führen wie in der davor angegebenen .NET-Lösung.
Aber selbst in .NET ist die Angabe einer Obergrenze für Quantoren in einem Look-
behind nicht verkehrt, um die Regex effizienter zu machen. Denn ansonsten kann ein
Amok laufendes Backtracking im Lookbehind viel Zeit kosten.
Der dritte reguläre Ausdruck wurde so umgebaut, dass das Lookbehind einen String mit
fester Länge enthält, wodurch noch mehr reguläre Ausdrücke unterstützt werden. Dazu
wurde das Nicht-Wortzeichen-Token (‹\W›) aus dem Lookbehind herausgenommen.
Somit werden die vor den Wörtern liegenden Nicht-Wortzeichen (wie zum Beispiel Satz-
zeichen und Whitespace) Teil der Übereinstimmung sein und vom regulären Ausdruck
zurückgegeben werden. Um diesen Teil nicht immer berücksichtigen zu müssen, wurde
die abschließende Wortzeichenfolge in eine einfangende Gruppe gesteckt. Mit etwas
zusätzlichem Code können Sie auf den Wert der Rückwärtsreferenz 1 zugreifen und das
Gesamtsuchergebnis verwerfen. So erhalten Sie das gleiche Ergebnis wie in den vorigen
regulären Ausdrücken. In Rezept 3.9 wird der Code gezeigt, der für den Zugriff auf die
Rückwärtsreferenzen benötigt wird.

Lookbehinds simulieren
In JavaScript gibt es keine Lookbehinds, aber der Beispielcode zeigt, wie Sie Look-
behinds, die am Anfang einer Regex stehen, simulieren können, indem Sie zwei reguläre
Ausdrücke kombinieren. Damit gibt es auch keine Einschränkungen bezüglich der Text-
länge, die durch das (simulierte) Lookbehind gefunden werden kann.
Zunächst teilen wir den regulären Ausdruck ‹(?<!\brot\W+)\b\w+› aus der ursprüngli-
chen Lösung in zwei Blöcke auf: das Muster innerhalb des Lookbehind (‹\brot\W+›) und
das Muster, das danach folgt (‹\b\w+›). Fügen Sie an das Ende der Lookbehind-Regex ein
‹$› an. Müssen Sie die Option Zirkumflex und Dollar passen auf Zeilenumbrüche (/m) für
die lookbehind-Regex verwenden, nutzen Sie ‹$(?!\s)› statt ‹$›, um sicherzustellen, dass

5.6 Ein beliebiges Wort finden, das nicht hinter einem bestimmten Wort steht | 315
sie nur ganz am Ende des Ausgangstexts passt. Die Variable lookbehind_type gibt an, ob
wir einen positiven (true) oder einen negativen (false) Lookbehind emulieren.
Nachdem die Variablen eingerichtet sind, verwenden wir main_regex und die Methode
exec, um über den Ausgangstext zu iterieren (in Rezept 3.11 finden Sie eine Beschreibung
dieses Prozesses). Wird eine Übereinstimmung gefunden, wird der Teil des Ausgangs-
texts vor der Übereinstimmung in eine neue String-Variable kopiert (left_context), und
wir prüfen, ob die Regex lookbehind auf diesen String passt. Durch den Anker am Ende
der Regex lookbehind wird diese zweite Übereinstimmung immer direkt links von der ers-
ten Übereinstimmung liegen. Durch einen Vergleich des Ergebnisses des Lookbehind-
Tests mit lookbehind_type können wir herausfinden, ob die Übereinstimmung alle Krite-
rien für eine erfolgreiche Suche erfüllt.
Schließlich führen wir einen von zwei Schritten durch. Gibt es eine erfolgreiche Überein-
stimmung, fügen wir den gefundenen Text an das Array matches an. Wenn nicht, passen
wir die Position an, an der wir mit der Suche fortfahren wollen (mit main_regex.last-
Index). Wir fahren ein Zeichen nach der Ausgangsposition der letzten Übereinstimmung
des Objekts main_regex fort, statt die nächste Iteration der Methode exec am Ende der
aktuellen Übereinstimmung beginnen zu lassen.
Puh! Fertig.
Das ist ein fortgeschrittener Trick, der sich die Eigenschaft lastIndex zunutze macht.
Diese Eigenschaft wird bei JavaScript-Regexes dynamisch aktualisiert, wenn diese die
Option /g („global“) verwenden. Normalerweise wird lastIndex irgendwie automatisch
angepasst oder zurückgesetzt. Hier verwenden wir sie, um das Vorangehen der Regex-
Engine im Ausgangstext zu beeinflussen. Damit können Sie aber nur Lookbehinds emu-
lieren, die am Anfang einer Regex stehen. Mit ein paar Änderungen könnte dieser Code
auch verwendet werden, um Lookbehinds am Ende einer Regex nachzubilden. Das ist
natürlich kein vollständiger Ersatz für eine echte Lookbehind-Unterstützung. Aufgrund
des Zusammenspiels zwischen Lookbehinds und Backtracking kann dieser Ansatz das
Verhalten eines Lookbehind in der Mitte einer Regex nicht vollständig emulieren.

Variationen
Wenn Sie nur Wörter finden wollen, vor denen rot steht (ohne rot und die folgenden
Nicht-Wortzeichen in den gefundenen Text mit aufzunehmen), ändern Sie das negative
Lookbehind in ein positives:
(?<=\brot\W+)\b\w+
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Variante: .NET
(?<=\brot\W{1,9})\b\w+
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE

316 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


(?<=\brot)(?:\W+|^)(\w+)
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby 1.9

Siehe auch
Rezept 2.16 geht detaillierter auf Lookarounds ein (den Oberbegriff von positiven und
negativen Lookaheads und Lookbehinds).
Rezepte 5.4 und 5.5.

5.7 Wörter finden, die nahe beieinanderstehen


Problem
Sie wollen eine NEAR-Suche durch einen regulären Ausdruck emulieren. Wenn Sie die-
sen Begriff nicht kennen, sei Ihnen gesagt, dass es Suchtools gibt, die Boolesche Operato-
ren wie NOT und OR verwenden, aber auch einen speziellen Operator NEAR besitzen.
Sucht man nach „wort1 NEAR wort2“, findet man wort1 und wort2 in beliebiger Reihen-
folge, solange sie voneinander nur einen bestimmten maximalen Abstand haben.

Lösung
Wenn Sie nur nach zwei unterschiedlichen Wörtern suchen, können Sie zwei verschie-
dene reguläre Ausdrücke kombinieren – einen, der wort1 vor wort2 findet, und einen wei-
teren, der die Wörter genau umgekehrt findet. Die folgende Regex ermöglicht bis zu fünf
Wörter Abstand zwischen den beiden Wörtern, die Sie suchen:
\b(?:wort1\W+(?:\w+\W+){0,5}?wort2|wort2\W+(?:\w+\W+){0,5}?wort1)\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
\b(?:
wort1 # erster Begriff
\W+ (?:\w+\W+){0,5}? # bis zu fünf Wörter
wort2 # zweiter Begriff
| # oder das gleiche Muster umgekehrt ...
wort2 # zweiter Begriff
\W+ (?:\w+\W+){0,5}? # bis zu fünf Wörter
wort1 # erster Begriff
)\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Der zweite reguläre Ausdruck nutzt den Freiform-Modus, in dem er zur besseren Lesbar-
keit Leerzeichen und Kommentare einfügt. Abgesehen davon sind die beiden regulären

5.7 Wörter finden, die nahe beieinanderstehen | 317


Ausdrücke identisch. JavaScript unterstützt keinen Freiform-Modus, aber in den anderen
aufgeführten Regex-Varianten können Sie selbst entscheiden, welche Version Sie nutzen.
In den Rezepten 3.5 und 3.7 finden Sie Beispiele dafür, wie Sie diese regulären Ausdrücke
in Ihrem Suchformular oder in anderem Code einsetzen können.

Diskussion
Dieser reguläre Ausdruck stellt zwei invertierte Kopien des gleichen Musters nebeneinan-
der und umgibt sie mit Wortgrenzen. Das erste Untermuster passt auf wort1, gefolgt von
null bis fünf Wörtern und dann wort2. Das zweite Untermuster passt auf das Gleiche, nur
dass die Reihenfolge von wort1 und wort2 vertauscht sind.
Der genügsame Quantor ‹{0,5}?› taucht in beiden Untermustern auf. Er sorgt dafür,
dass der reguläre Ausdruck zwischen den beiden gesuchten Begriffen so wenige Wörter
wie möglich findet. Lassen Sie den regulären Ausdruck über den Ausgangstext wort1
wort2 wort2 laufen, passt er auf wort1 wort2, da er weniger Wörter (nämlich null) zwi-
schen Anfangs- und Endpunkt enthält. Um den zulässigen Abstand zwischen den beiden
gewünschten Wörtern festzulegen, ändern Sie 0 und 5 in den beiden Quantoren auf die
von Ihnen gewünschten Werte. Entscheiden Sie sich zum Beispiel für ‹{1,15}?›, dürfen
bis zu 15 Wörter zwischen den beiden gesuchten Wörtern stehen, aber es muss sich auch
mindestens ein anderes Wort dazwischen befinden.
Die Zeichenklassenabkürzungen zum Finden der Wortzeichen und Nicht-Wortzeichen
(‹\w› und ‹\W›) orientieren sich an den seltsamen Regex-Definitionen von Wortzeichen
(Buchstaben, Ziffern und Unterstriche).

Variationen
Verwenden einer Bedingung
Häufig gibt es viele Möglichkeiten, mit einem regulären Ausdruck das gleiche Ergebnis
zu erreichen. In diesem Buch haben wir uns bemüht, einen Ausgleich zwischen Portabili-
tät, Kürze, Effizienz und anderen Überlegungen zu finden. Aber manchmal können
Lösungen, die überhaupt nicht ideal sind, trotzdem lehrreich sein. Die nächsten zwei
regulären Ausdrücke zeigen alternative Vorgehensweisen, um Wörter zu finden, die nahe
beieinanderliegen. Wir empfehlen nicht, sie zu verwenden, da sie zwar den gleichen Text
finden, aber meist ein bisschen länger dafür brauchen. Zudem lassen sie sich in nicht so
vielen Regex-Varianten nutzen.
Der folgende erste reguläre Ausdruck verwendet eine Bedingung, um zu ermitteln, ob
wort1 oder wort2 am Ende der Regex passt, anstatt einfach die beiden „inversen“ Muster
zu verketten. Die Bedingung prüft, ob die einfangende Gruppe 1 an der Übereinstim-
mung beteiligt war. Denn das würde bedeuten, dass die Übereinstimmung mit wort2
begann:

318 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


\b(?:wort1|(wort2))\W+(?:\w+\W+){0,5}?(?(1)wort1|wort2)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, PCRE, Perl, Python
Die nächste Version greift ebenfalls auf eine Bedingung zurück, um herauszufinden, wel-
ches Wort am Ende gefunden werden sollte, aber sie nutzt noch zwei weitere Regex-
Features:
\b(?:(?<w1>wort1)|(?<w2>wort2))\W+(?:\w+\W+){0,5}?(?(w2)(?&w1)|(?&w2))\b
Regex-Optionen: Keine
Regex-Varianten: PCRE 7, Perl 5.10
Hier umgeben benannte einfangende Gruppen, die die Syntax ‹(?<name>...)› haben, die
ersten Vorkommen von ‹wort1› und ‹wort2›. Damit können Sie mit der Subrouti-
nensyntax ‹(?&name)› ein Submuster durch seinen Namen wiederverwenden. Das funk-
tioniert jedoch anders als eine Rückwärtsreferenz einer benannten Gruppe. Mit einer
benannten Rückwärtsreferenz, wie zum Beispiel ‹\k<name>› (.NET, PCRE 7, Perl 5.10)
oder ‹(?P=name)› (PCRE 4 und neuer, Perl 5.10, Python), können Sie Text erneut suchen,
der schon von einer benannten einfangenden Gruppe gefunden wurde. Eine Subroutine
wie ‹(?&name)› ermöglicht die Wiederverwendung des eigentlichen Musters, das sich in
der entsprechenden Gruppe befindet. Sie können hier keine Rückwärtsreferenz nutzen,
weil damit nur Wörter gefunden werden können, die schon gefunden wurden. Die Sub-
routinen innerhalb der Bedingung am Ende der Regex passen zu dem Wort aus den bei-
den angegebenen Optionen, das noch nicht gefunden wurde, ohne es erneut hinschreiben
zu müssen. Somit gibt es in der Regex nur eine Stelle, die Sie anpassen müssen, wenn Sie
sie mit anderen Wörtern verwenden wollen.

Drei oder mehr Wörter finden, die nahe beieinanderliegen


Exponentiell wachsende Permutationen: Zwei Wörter zu finden, die nahebeieinander liegen, ist
eine recht übersichtliche Aufgabe. Schließlich gibt es nur zwei Möglichkeiten, sie anzu-
ordnen. Aber was, wenn Sie drei Wörter in beliebiger Reihenfolge finden wollen? Jetzt
gibt es sechs mögliche Anordnungen (siehe Abbildung 5-2). Die Anzahl an Möglichkei-
ten, eine gegebene Menge an Wörtern zu ordnen, ist n! oder das Produkt der ganzen Zah-
len von 1 bis n („n Fakultät“). Bei vier Wörtern gibt es 24 Möglichkeiten, sie anzuordnen.
Geht es um zehn Wörter, wächst die Anzahl an Möglichkeiten in die Millionen. Es ist
schlicht nicht machbar, mehr als ein paar beieinanderliegende Wörter mithilfe der bisher
genutzten Regex-Techniken finden.

Die hässliche Lösung: Eine Möglichkeit, dieses Problem zu lösen, ist das Wiederholen einer
Gruppe, die die notwendigen Wörter oder ein beliebiges anderes Wort (nachdem vorher
ein notwendiges Wort gefunden wurde) findet. Dann greift man auf Bedingungen
zurück, die dafür sorgen, dass die Übereinstimmung erst dann erfolgreich ist, wenn alle
anderen notwendigen Wörter gefunden wurden. Dies ist ein Beispiel, mit dem drei Wör-

5.7 Wörter finden, die nahe beieinanderstehen | 319


ter in beliebiger Reihenfolge gefunden werden, wobei höchstens fünf andere Wörter
dazwischenliegen dürfen:
\b(?:(?>(wort1)|(wort2)|(wort3)|(?(1)|(?(2)|(?(3)|(?!))))\w+)\b\W*?){3,8}
(?(1)(?(2)(?(3)|(?!))|(?!))|(?!))
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, PCRE, Perl

Zwei Werte:
[ 12, 21 ]
= 2 mögliche Anordnungen

Drei Werte:
[ 123, 132,
213, 231,
312, 321 ]
= 6 mögliche Anordnungen

Vier Werte:
[ 1234, 1243, 1324, 1342, 1423, 1432,
2134, 2143, 2314, 2341, 2413, 2432,
3124, 3142, 3214, 3241, 3412, 3421,
4123, 4132, 4213, 4231, 4312, 4321 ]
= 24 mögliche Anordnungen

Faktorieren:
2! = 2 × 1 = 2
3! = 3 × 2 × 1 = 6
4! = 4 × 3 × 2 × 1 = 24
5! = 5 × 4 × 3 × 2 × 1 = 120
...
10! = 10 × 9 × 8 × 7 × 6 × 5 × 4 × 3 × 2 × 1 = 3628800

Abbildung 5-2: Viele Möglichkeiten, eine Menge zu ordnen

Die gleiche Regex, diesmal ohne atomare Gruppen (siehe Rezept 2.14), dafür mit einer
normalen nicht-einfangenden Gruppe, damit sie auch in Python genutzt werden kann:
\b(?:(?:(wort1)|(wort2)|(wort3)|(?(1)|(?(2)|(?(3)|(?!))))\w+)\b\W*?){3,8}
(?(1)(?(2)(?(3)|(?!))|(?!))|(?!))
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, PCRE, Perl, Python
Die Quantoren ‹{3,8}› in den regulären Ausdrücken berücksichtigen die drei erforderli-
chen Wörter und ermöglichen null bis fünf andere Wörter dazwischen. Die leeren negati-
ven Lookaheads ‹(?!)› passen niemals und werden daher genutzt, um bestimmte Pfade
durch die Regex zu blockieren, bis eines oder mehrere der notwendigen Wörter gefunden
wurden. Die Logik, die diese Pfade kontrolliert, ist durch zwei Sets an verschachtelten
Bedingungen implementiert. Das erste Set verhindert, dass ein schon gefundenes Wort
‹\w+› gefunden wird, bis mindestens eines der notwendigen Wörter passt. Das zweite Set

320 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


an Bedingungen am Ende zwingt die Regex-Engine dazu, per Backtracking zurückzuge-
hen oder einen Misserfolg zu vermelden, bis nicht alle notwendigen Wörter gefunden
wurden.
Das ist nur eine kurze Beschreibung der Funktionsweise, aber statt hier noch tiefer einzu-
steigen und zu erklären, wie man zusätzliche notwendige Wörter ergänzt, wollen wir uns
lieber eine verbesserte Implementierung anschauen, die mehr Regex-Varianten unter-
stützt und ein bisschen trickreicher vorgeht.

Leere Rückwärtsreferenzen ausnutzen: Die hässliche Lösung funktioniert, ist aber eher ein
Anwärter auf den Preis für die verwirrendste Regex, da sie sich nur schlecht lesen und
handhaben lässt. Und mit jedem weiteren Wort würde es nur noch schlimmer werden.
Glücklicherweise gibt es einen Regex-Hack, der deutlich einfacher zu verstehen ist und
der zudem auch unter Java und Ruby läuft (die beide keine Bedingungen unterstützen).

Das in diesem Abschnitt beschriebene Verhalten sollte in produktiven


Anwendungen nur mit Vorsicht eingesetzt werden. Wir nutzen dabei
Regex-Verhaltensweisen aus, die in den meisten Regex-Bibliotheken nicht
dokumentiert sind.

\b(?:(?>wort1()|wort2()|wort3()|(?>\1|\2|\3)\w+)\b\W*?){3,8}\1\2\3
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
\b(?:(?:wort1()|wort2()|wort3()|(?:\1|\2|\3)\w+)\b\W*?){3,8}\1\2\3
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Mit diesem Konstrukt kann man auch sehr einfach weitere notwendige Wörter hinzufü-
gen. Hier ein Beispiel mit vier erforderlichen Wörtern in beliebiger Reihenfolge, wobei
höchstens fünf Wörter zwischen ihnen liegen dürfen:
\b(?:(?>wort1()|wort2()|wort3()|wort4()|
(?>\1|\2|\3|\4)\w+)\b\W*?){4,9}\1\2\3\4
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Ruby
\b(?:(?:wort1()|wort2()|wort3()|wort4()|
(?:\1|\2|\3|\4)\w+)\b\W*?){4,9}\1\2\3\4
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Diese regulären Ausdrücke nutzen mit Absicht leere einfangende Gruppe hinter jedem
erforderlichen Wort. Da jeder Versuch, eine Rückwärtsreferenz wie ‹\1› zu finden, fehl-
schlägt, wenn die entsprechende einfangende Gruppe noch nicht an der Übereinstim-
mung beteiligt war, können Rückwärtsreferenzen auf leere Gruppen genutzt werden, um
den Pfad der Regex-Engine durch ein Muster so zu kontrollieren, wie es mit den weiter

5.7 Wörter finden, die nahe beieinanderstehen | 321


oben vorgestellten umfangreicheren Bedingungen möglich war. War die entsprechende
Gruppe schon an der Übereinstimmung beteiligt, wenn die Engine die Rückwärtsrefe-
renz erreicht, wird sie einfach einen leere String finden und mit der Arbeit fortfahren.
Hier verhindert die Gruppe ‹(?>\1|\2|\3)›, dass mit ‹\w+› ein Wort gefunden wird,
bevor nicht mindestens eines der notwendigen Wörter passt. Die Rückwärtsreferenzen
werden am Ende des Musters wiederholt, um zu verhindern, dass erfolgreich eine Über-
einstimmung abgeschlossen werden kann, bevor nicht alle notwendigen Wörter gefun-
den wurden.
Python unterstützt keine atomaren Gruppen, daher werden auch hier in dem Beispiel,
das für Python genutzt werden kann, die atomaren Gruppen durch nicht-einfangende
Gruppen ersetzt. Dadurch werden die Regexes zwar weniger effizient, aber die gefunde-
nen Inhalte unterscheiden sich nicht. Die äußere Gruppe kann in keiner Variante atomar
sein, denn die Regex-Engine muss innerhalb der äußeren Gruppe per Backtracking arbei-
ten können, wenn die Rückwärtsreferenzen am Ende des Musters keinen Erfolg haben.

JavaScript-Rückwärtsreferenzen mit eigenen Regeln: Obwohl JavaScript die in der Python-Version


dieses Musters genutzte Syntax vollständig unterstützt, gibt es dort zwei Unterschiede,
die dafür sorgen, dass dieser Trick hier nicht funktioniert. Beim ersten geht es darum,
was durch Rückwärtsreferenzen auf einfangende Gruppen gefunden wird, die noch nicht
Teil einer Übereinstimmung sind. In der JavaScript-Spezifikation steht, dass solche Rück-
wärtsreferenzen einen leeren String finden, also immer erfolgreich sind. In so gut wie
jeder anderen Regex-Variante gilt das Gegenteil: Sie passen niemals und sorgen damit
dafür, dass die Regex-Engine per Backtracking zurückspringen muss, bis entweder die
gesamte Suche ein Fehlschlag war oder bis die Gruppe, auf die sie verweisen, Teil der
Übereinstimmung ist. Dann passt auch wieder die Rückwärtsreferenz.
Der zweite Unterschied bei der JavaScript-Variante dreht sich um den Wert, der von
einer einfangenden Gruppe in einer wiederholten äußeren Gruppe gefunden wird, zum
Beispiel ‹((a)|(b))+›. Bei den meisten Regex-Varianten entspricht der Wert, den sich
eine einfangende Gruppe innerhalb einer wiederholten Gruppe merkt, dem, was als Letz-
tes gefunden wurde. Wenn zum Beispiel mit ‹(?:(a)|(b))+› der Text ab gefunden wurde,
wird der Wert der Rückwärtsreferenz 1 auf a gesetzt sein. Nach der JavaScript-Spezifika-
tion wird aber der Wert von Rückwärtsreferenzen in verschachtelten Gruppen jedes Mal
zurückgesetzt, wenn die äußere Gruppe wiederholt wird. Findet also ‹(?:(a)|(b))+› wie-
der den Text ab, würde die Rückwärtsreferenz 1 hier auf eine nicht beteiligte einfangende
Gruppe verweisen, was in JavaScript innerhalb der Regex selbst einem leeren String ent-
spricht. Das von RegExp.prototype.exec zurückgegebene Array würde hier den Wert
undefined liefern.
Beide Verhaltensunterschiede führen in der JavaScript-Regex-Variante dazu, dass Bedin-
gungen in einer Regex auf dem oben vorgestellten Weg nicht emuliert werden können.

322 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Mehrere Wörter, die einen beliebigen Abstand voneinander haben können
Wenn Sie einfach nur prüfen wollen, ob in einem Text eine Liste von Wörtern gefunden
werden kann, wobei der Abstand zwischen den Wörtern beliebig sein kann, bieten posi-
tive Lookaheads eine Möglichkeit, das in einer Suchoperation zu erledigen.

In vielen Fällen ist es einfacher und effizienter, jeden Term einzeln zu


suchen und zu prüfen, ob alle Tests erfolgreich waren.

\A(?=.*?\bwort1\b)(?=.*?\bwort2\b).*\Z
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt auf Zeilenumbruch
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^(?=[\s\S]*?\bwort1\b)(?=[\s\S]*?\bwort2\b)[\s\S]*$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert (Zirkumflex und Dollar-
zeichen passen auf Zeilenumbruch darf nicht gesetzt sein)
Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Diese regulären Ausdrücke passen auf den gesamten String, auf den sie angewendet wer-
den, wenn alle Ihre gesuchten Wörter darin enthalten sind. Ansonsten gibt es keine
Übereinstimmung. JavaScript-Programmierer können die erste Version nicht nutzen, da
JavaScript die Anker ‹\A› und ‹\Z› oder die Option Punkt passt auf Zeilenumbruch nicht
unterstützt.
Sie können diese regulären Ausdrücke wie in Rezept 3.6 implementieren. Ändern Sie ein-
fach die Platzhalter ‹wort1› und ‹wort2› in die gesuchten Begriffe. Wenn Sie nach mehr
als zwei Wörtern suchen, können Sie so viele Lookaheads wie nötig in den regulären Aus-
druck einfügen. Mit ‹\A(?=.*?\bwort1\b)(?=.*?\bwort2\b)(?=.*?\bwort3\b).*\Z› suchen
Sie zum Beispiel nach drei Wörtern.

Siehe auch
Rezepte 5.5 und 5.6.

5.8 Wortwiederholungen finden


Problem
Sie bearbeiten ein Dokument und würden gern prüfen, ob Sie unabsichtlich Wörter wie-
derholt haben. Diese doppelten Wörter sollen auch dann gefunden werden, wenn sie in
unterschiedlicher Groß- und Kleinschreibung eingetippt wurden, wie zum Beispiel bei
„Wer wer“. Der Whitespace zwischen den Wörtern ist Ihnen ebenfalls egal. Er kann
beliebig groß sein, auch wenn die Wörter damit auf unterschiedlichen Zeilen gelandet
sind.

5.8 Wortwiederholungen finden | 323


Lösung
Eine Rückwärtsreferenz passt auf etwas, das vorher gefunden wurde. Damit ist sie die
wichtigste Zutat für dieses Rezept:
\b([A-Z]+)\s+\1\b
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Wenn Sie diesen regulären Ausdruck verwenden wollen, um das erste Wort zu behalten,
die Wiederholung aber zu entfernen, ersetzen Sie alle Übereinstimmungen durch die
Rückwärtsreferenz 1. Sie können die Übereinstimmungen auch mit anderen Zeichen
umschließen (zum Beispiel durch ein HTML-Tag), um sie bei einer späteren Überarbei-
tung schneller zu erkennen. In Rezept 3.15 erfahren Sie, wie Sie Rückwärtsreferenzen in
Ihrem Ersetzungstext verwenden können.
Wenn Sie nur doppelte Wörter finden wollen, um manuell zu prüfen, ob sie korrigiert
werden müssen, finden Sie in Rezept 3.7 den notwendigen Code. Ein Texteditor oder ein
grep-ähnliches Tool – wie die in „Tools für das Arbeiten mit regulären Ausdrücken“ in
Kapitel 1 erwähnten – kann Ihnen dabei helfen, doppelte Wörter zu finden, während Sie
gleichzeitig den Kontext sehen, in dem die fraglichen Wörter stehen.

Diskussion
Man braucht zwei Dinge, um etwas zu finden, was schon vorher gefunden wurde: eine
einfangende Gruppe und eine Rückwärtsreferenz. Stecken Sie das, was Sie mehr als ein-
mal finden wollen, in eine einfangende Gruppe und suchen Sie es dann erneut mithilfe
einer Rückwärtsreferenz. Das ist etwas anderes, als ein Token oder eine Gruppe mit
einem Quantor zu wiederholen. Schauen Sie sich den Unterschied zwischen den beiden
vereinfachten regulären Ausdrücken ‹(\w)\1› und ‹\w{2}› an. Die erste Regex nutzt eine
einfangende Gruppe und eine Rückwärtsreferenz, mit der das gleiche Wortzeichen dop-
pelt gefunden werden kann, während die zweite Regex einen Quantor nutzt, um zwei
beliebige Wortzeichen zu finden. In Rezept 2.10 wird die Faszination von Rückwärtsrefe-
renzen im Detail beschrieben.
Aber zurück zum eigentlichen Problem. Dieses Rezept findet nur doppelte Wörter, die
aus den Buchstaben A bis Z und a bis z bestehen (da die Option zum Ignorieren von
Groß- und Kleinschreibung aktiv ist). Um auch akzentuierte Zeichen und Zeichen aus
anderen Schriftsystemen zuzulassen, können Sie die Buchstabeneigenschaft für Unicode-
Zeichen verwenden (‹\p{L}›), wenn Ihre Regex-Variante das unterstützt (siehe „Uni-
code-Eigenschaften oder -Kategorien“ auf Seite 49).
Zwischen der einfangenden Gruppe und der Rückwärtsreferenz passt ‹\s+› auf beliebig
viele Whitespace-Zeichen, also auf Leerzeichen, Tabs und Zeilenumbrüche. Wollen Sie
die Zeichen auf solche einschränken, die horizontale Abstände darstellen (also keine Zei-
lenumbrüche), ersetzen Sie ‹\s› durch ‹[^\S\r\n]›. Damit wird verhindert, dass Sie dop-

324 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


pelte Wörter finden, die sich nicht gemeinsam in einer Zeile befinden. Bei der PCRE 7
und in Perl 5.10 gibt es die Zeichenklassenabkürzung ‹\h›, die Sie hier vielleicht lieber
einsetzen wollen, weil damit nur horizontaler Whitespace gefunden wird.
Schließlich stellen die Wortgrenzen am Anfang und am Ende des regulären Ausdrucks
sicher, dass die Übereinstimmungen nicht innerhalb anderer Wörter liegen, wie zum Bei-
spiel bei „die Diebe“.
Beachten Sie, dass doppelte Wörter nicht immer falsch sind, daher ist es zu gefährlich, sie
einfach ohne Kontrolle zu löschen. Die Konstrukte „die die“ oder „das das“ sind durch-
aus korrekt. Homonyme, Namen, lautmalerische Wörter (wie zum Beispiel „oink oink“
oder „ha ha“) und einige andere Konstrukte führen auch durchaus zu bewusst wiederhol-
ten Wörtern. Daher werden Sie in den meisten Fällen jede Übereinstimmung in ihrem
Kontext überprüfen müssen.

Siehe auch
Rezept 2.10 behandelt Rückwärtsreferenzen im Detail.
Rezept 5.9 zeigt, wie man doppelte Textzeilen findet.

5.9 Doppelte Zeilen entfernen


Problem
Sie haben eine Logdatei, die Ausgabe einer Datenbankabfrage oder eine andere Art von
Datei oder String mit doppelten Zeilen. Sie müssen diese doppelten Einträge mit einem
Texteditor oder einem ähnlichen Tool entfernen.

Lösung
Es gibt eine Reihe von Softwaretools (wie zum Beispiel das Befehlszeilentool uniq unter
Unix und das Windows PowerShell Cmdlet Get-Unique), mit denen Sie doppelte Zeilen in
einer Datei oder einem String entfernen können. Die folgenden Abschnitte enthalten drei
Regex-basierte Ansätze, die besonders dann hilfreich sein können, wenn man versucht,
diese Aufgabe in einem nicht skriptbaren Texteditor umzusetzen, der aber immerhin mit-
hilfe von regulären Ausdrücken suchen und ersetzen kann.
Beim Programmieren sollten die Optionen 2 und 3 vermieden werden, da sie im Ver-
gleich zu anderen verfügbaren Vorgehensweisen ineffizient sind. Hier sollte man zum
Beispiel eher auf ein Hash-Objekt zurückgreifen, um die Eindeutigkeit von Zeilen zu
gewährleisten. Aber die erste Option (bei der Sie die Zeilen vorher sortieren müssen,
sofern Sie nicht nur direkt hintereinanderliegende doppelte Zeilen entfernen wollen)
kann auch hier durchaus akzeptabel sein, da sie sich schnell und einfach implementieren
lässt.

5.9 Doppelte Zeilen entfernen | 325


Option 1: Zeilen sortieren und hintereinanderliegende Duplikate entfernen
Wenn Sie die Zeilen in der Datei oder dem String sortieren können, sodass doppelte Zei-
len direkt hintereinanderliegen, sollten Sie das tun, sofern die Reihenfolge der Zeilen
nicht beibehalten werden muss. Denn damit wird das Suchen und Ersetzen der doppel-
ten Einträge viel einfacher und effizienter.
Nach dem Sortieren der Zeilen verwenden Sie die folgende Regex mit dem Ersetzungs-
String, um die Duplikate loszuwerden:
^(.*)(?:(?:\r?\n|\r)\1)+$
Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenum-
bruch darf nicht gesetzt sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Zu ersetzen durch:
$1
Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP
\1
Ersetzungstextvarianten: Python, Ruby
Dieser reguläre Ausdruck nutzt eine einfangende Gruppe und eine Rückwärtsreferenz
(neben anderen Bestandteilen), um zwei oder mehr aufeinanderfolgende doppelte Zeilen
zu finden. Mit einer Rückwärtsreferenz im Ersetzungstext fügen Sie nur die erste Zeile
wieder ein. In Rezept 3.15 finden Sie Beispielcode, den Sie auch hier verwenden können.

Option 2: Das letzte Vorkommen jeder doppelten Zeile in einer unsortierten Datei
behalten
Wenn Sie einen Texteditor nutzen, mit dem Sie Zeilen nicht sortieren können, oder wenn
es wichtig ist, die ursprüngliche Reihenfolge beizubehalten, können Sie mit der folgenden
Lösung doppelte Zeilen entfernen, auch wenn es zwischen ihnen andere Zeilen gibt:
^([^\r\n]*)(?:\r?\n|\r)(?=.*^\1$)
Regex-Optionen: Punkt passt auf Zeilenumbruch, ^ und $ passen auf Zeilenumbruch
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Das Gleiche noch einmal als JavaScript-kompatible Regex, bei der die Option Punkt passt
auf Zeilenumbruch nicht notwendig ist:
^(.*)(?:\r?\n|\r)(?=[\s\S]*^\1$)
Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch
darf nicht gesetzt sein)
Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Zu ersetzen durch:
(Einen leeren String, also nichts.)
Ersetzungstextvarianten: nicht notwendig

326 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Option 3: Das erste Vorkommen jeder doppelten Zeile in einer unsortierten Datei
behalten
Wenn Sie das erste Vorkommen jeder doppelten Zeile behalten wollen, brauchen Sie
einen etwas anderen Ansatz. Dies sind der reguläre Ausdruck und der Ersetzungstext, die
wir verwenden werden:
^([^\r\n]*)$(.*?)(?:(?:\r?\n|\r)\1$)+
Regex-Optionen: Punkt passt auf Zeilenumbruch, ^ und $ passen auf Zeilenumbruch
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
Auch hier sind ein paar Änderungen nötig, um diese Regex mit JavaScript nutzen zu kön-
nen, da JavaScript keine Option Punkt passt auf Zeilenumbruch besitzt.
^(.*)$([\s\S]*?)(?:(?:\r?\n|\r)\1$)+
Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch
darf nicht gesetzt sein)
Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Zu ersetzen durch:
$1$2
Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP
\1\2
Ersetzungstextvarianten: Python, Ruby
Anders als bei den Regexes der Optionen 1 und 2 können in dieser Version nicht alle
doppelten Zeilen mit einem Suchen-und-Ersetzen-Durchlauf erledigt werden. Sie werden
Alle ersetzen so lange anwenden müssen, bis die Regex keine Übereinstimmungen mehr
in Ihrem String findet. Denn erst dann werden keine doppelten Zeilen mehr vorhanden
sein. In „Diskussion“ erfahren Sie mehr Details dazu.

Diskussion
Option 1: Zeilen sortieren und hintereinanderliegende Duplikate entfernen
Diese Regex entfernt bei aufeinanderfolgenden doppelten Zeilen alle bis auf die erste. Sie
entfernt keine doppelten Zeilen, die durch andere Zeilen getrennt sind. Lassen Sie uns
den Prozess Schritt für Schritt durchgehen.
Zuerst passt der Zirkumflex (‹^›) am Anfang des regulären Ausdrucks auf den Zeilenan-
fang. Normalerweise würde er nur am Anfang des Gesamttexts passen, daher müssen Sie
sicherstellen, dass die Option ^ und $ passen auf Zeilenumbruch aktiv ist (in Rezept 3.4
erfahren Sie, wie Sie Regex-Optionen einschalten). Als Nächstes passt ‹.*› innerhalb der
einfangenden Klammern auf den gesamten Inhalt einer Zeile (selbst wenn sie leer ist),
und der Wert wird als Rückwärtsreferenz 1 gespeichert. Damit das korrekt funktioniert,
darf die Option Punkt passt auf Zeilenumbruch nicht aktiv sein. Ansonsten würde die
Punkt-Stern-Kombination auf alles bis zum Ende des Strings passen.

5.9 Doppelte Zeilen entfernen | 327


Innerhalb einer äußeren, nicht-einfangenden Gruppe nutzen wir ‹(?:\r?\n|\r)›, um ein
Zeilenende in Windows (‹\r\n›), Unix/Linux/OS X (‹\n›) oder im alten Mac OS (‹\r›)
zu finden. Die Rückwärtsreferenz ‹\1› versucht dann, die Zeile zu finden, die wir gerade
gefunden hatten. Wenn an dieser Stelle nicht die gleiche Zeile vorhanden ist, gibt es hier
keine Übereinstimmung, und die Regex-Engine fährt mit ihrer Suche fort. Passt die Zeile,
wiederholen wir die Gruppe (bestehend aus einer Zeilenumbruchfolge und der Rück-
wärtsreferenz 1) mit dem Quantor ‹+›, um noch weitere doppelte Zeilen zu finden.
Schließlich nutzen wir das Dollarzeichen am Ende der Regex, um sicherzustellen, dass
wir uns am Zeilenende befinden. Damit finden wir auch nur wirklich identische Zeilen
und nicht solche, die nur mit den gleichen Zeichen beginnen wie die vorherige Zeile.
Da wir hier suchen und ersetzen, wird jede vollständige Übereinstimmung (einschließlich
der Originalzeile und der Zeilentrennzeichen) aus dem String entfernt. Wir ersetzen sie
mit der Rückwärtsreferenz 1, um die Ursprungszeile beizubehalten.

Option 2: Das letzte Vorkommen jeder doppelten Zeile in einer unsortierten Datei
behalten
Im Vergleich zur Regex aus Option 1 gibt es hier eine Reihe von Änderungen, denn die
erste Regex findet nur direkt aufeinanderfolgende doppelte Zeilen. So wurde in der
Nicht-JavaScript-Version der Regex aus Option 2 der Punkt innerhalb der einfangenden
Gruppe durch ‹[^\r\n]› ersetzt (jedes Zeichen außer einem Zeilenumbruch) und die
Option Punkt passt auf Zeilenumbruch aktiviert. Das liegt daran, dass später noch ein
Punkt genutzt wird, um beliebige Zeichen zu finden – eben auch Zeilenumbrüche. Und
es wurde ein Lookahead ergänzt, um nach doppelten Zeilen weiter unten im String zu
suchen. Da das Lookahead keine Zeichen konsumiert, ist der von der Regex gefundene
Text immer eine einzelne Zeile (zusammen mit dem folgenden Zeilenumbruch), die auf
jeden Fall weiter unten im String nochmals vorkommen wird. Ersetzt man alle Überein-
stimmungen durch leere Strings, werden die doppelten Zeilen entfernt, und es bleibt nur
deren jeweils letztes Vorkommen.

Option 3: Das erste Vorkommen jeder doppelten Zeile in einer unsortierten Datei
behalten
Da Lookbehinds in nicht so vielen Varianten unterstützt werden wie Lookaheads (und
man dann auch nicht immer so weit zurückschauen kann, wie man es braucht), unter-
scheidet sich die Regex aus Option 3 deutlich von der vorherigen. Statt Zeilen zu finden,
von denen man weiß, dass sie später noch einmal vorkommen (was die Taktik in Option
2 ist), passt diese Regex auf eine Zeile in einem String, die weiter unten folgende Dublette
dieser Zeile und alle Zeilen dazwischen. Die ursprüngliche Zeile wird als Rückwärtsrefe-
renz 1 gespeichert, die Zeilen dazwischen (wenn es denn welche gibt) als Rückwärts-
referenz 2. Indem Sie jede Übereinstimmung durch die Rückwärtsreferenzen 1 und 2
ersetzen, fügen Sie nur die Teile wieder ein, die Sie behalten wollen. Die abschließende
doppelte Zeile und der direkt davorliegende Zeilenumbruch werden aber verworfen.

328 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Dieses alternative Vorgehen hat allerdings ein paar Haken. Erstens: Eine Übereinstim-
mung mit doppelten Zeilen kann wiederum andere Zeilen enthalten, in denen es auch
(andere) Dubletten gibt. Diese werden aber durch ein Alles ersetzen übersprungen. Zwei-
tens: Wenn eine Zeile nicht nur doppelt, sondern drei- oder mehrfach vorkommt, wird
die Regex die ersten beiden entsprechenden Zeilen finden, aber nicht den Rest. Dort wer-
den (bei mehr als dreifachem Vorkommen) wiederum eventuell Dubletten gefunden.
Damit wird eine einzelne Operation Alles ersetzen höchstens jede zweite Dublette einer
bestimmten Zeile entfernen. Um beide Probleme zu lösen und sicherzustellen, dass alle
Dubletten entfernt sind, müssen Sie wiederholt suchen und ersetzen, bis die Regex keine
Übereinstimmungen mehr findet. Schauen Sie sich einmal an, wie die Regex mit folgen-
dem Ausgangstext arbeitet:
Wert1
Wert2
Wert2
Wert3
Wert3
Wert1
Wert2

Um alle Dubletten aus diesem String zu entfernen, benötigt man drei Durchläufe. In
Tabelle 5-1 werden die Ergebnisse jedes Durchlaufs gezeigt.

Tabelle 5-1: Ersetzungsdurchläufe


Durchlauf 1 Durchlauf 2 Durchlauf 3 Ergebnis
Matchtext Wert1 Wert1 Wert1
Wert2 Wert2 Wert2 Wert2
Wert2 Wert2 Wert3 Wert3
Wert3 Wert3 Wert2
Wert3 Wert3
Wert1 Wert2
Wert2
Eine Übereinstim- Zwei Übereinstim- Eine Übereinstim- Keine weiteren
mung/Ersetzung mungen/Ersetzungen mung/Ersetzung Dubletten

Siehe auch
In Rezept 2.10 werden Rückwärtsreferenzen im Detail erklärt.
In Rezept 5.8 erfahren Sie, wie Sie Wortdubletten finden können.

5.9 Doppelte Zeilen entfernen | 329


5.10 Vollständige Zeilen finden, die ein bestimmtes Wort
enthalten
Problem
Sie wollen alle Zeilen finden, die das Wort Ninja enthalten.

Lösung
^.*\bNinja\b.*$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilen-
umbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Es ist häufig nützlich, vollständige Zeilen zu finden, um sie in einer Liste zu sammeln
oder um sie zu entfernen. Um eine Zeile zu finden, die das Wort Ninja enthält, beginnen
wir mit dem regulären Ausdruck ‹\bNinja\b›. Die Wortgrenzen-Tokens auf beiden Sei-
ten stellen sicher, dass wir „Ninja“ nur finden, wenn es als vollständiges Wort erscheint
(siehe Rezept 2.6).
Um die Regex so zu erweitern, dass sie eine komplette Zeile findet, ergänzen Sie an bei-
den Enden ‹.*›. Die Punkt-Stern-Folge passt auf null oder mehr Zeichen innerhalb der
aktuellen Zeile. Die Stern-Quantoren sind gierig, daher konsumieren sie so viel Text wie
möglich. Die erste Punkt-Stern-Folge passt bis zum letzten Vorkommen von „Ninja“ auf
der Zeile, während die zweite Punkt-Stern-Folge alle Zeichen danach (außer Zeilenum-
brüchen) findet.
Durch ein Zirkumflex am Anfang und ein Dollarzeichen am Ende des regulären Aus-
drucks wird schließlich sichergestellt, dass die Übereinstimmung eine komplette Zeile
umfasst. Streng genommen ist das Dollarzeichen am Ende überflüssig, weil Punkt und
gieriger Stern immer nur bis zum Ende der Zeile laufen. Aber das Dollarzeichen tut nicht
weh, und der reguläre Ausdruck wird dadurch etwas selbsterklärender. Durch das Hin-
zufügen von Zeilen- oder String-Ankern (sofern passend) lassen sich gelegentlich un-
erwartete Phänomene vermeiden, daher ist es keine schlechte Idee, sich dies
anzugewöhnen. Beachten Sie, dass der Zirkumflex im Gegensatz zum Dollarzeichen
nicht überflüssig ist, da man dadurch sicherstellt, dass die Regex die vollständige Zeile
abdeckt, selbst wenn die Suche aus irgendeinem Grund erst in der Mitte der Zeile loslegt.
Denken Sie daran, dass die drei wichtigsten Metazeichen zum Eingrenzen der Überein-
stimmung auf eine einzelne Zeile (die Anker ‹^› und ‹$› sowie der Punkt) keine feste
Bedeutung besitzen. Um sie an Zeilen zu orientieren, müssen Sie die Option aktivieren,
mit der ‹^› und ‹$› an Zeilenumbrüchen passen, während die Option, mit der der Punkt

330 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


auch einen Zeilenumbruch findet, eben nicht aktiviert sein darf. In Rezept 3.4 wird
gezeigt, wie Sie diese Optionen in Ihrem Code setzen können. Wenn Sie JavaScript oder
Ruby nutzen, müssen Sie sich nur um eine der beiden Optionen Gedanken machen, weil
JavaScript keine Option anbietet, mit dem Punkt Zeilenumbrüche zu finden, und die Zir-
kumflex- und Dollarzeichen-Anker bei Ruby immer einen Zeilenumbruch finden.

Variationen
Um nach Zeilen zu suchen, die eines von mehreren Wörtern enthalten, verwenden Sie
eine Alternation:
^.*\b(eins|zwei|drei)\b.*$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilen-
umbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck passt auf jede Zeile, die mindestens eines der Wörter „eins“,
„zwei“ oder „drei“ enthält. Die Klammern um die Wörter erfüllen gleich zwei Zwecke.
Zum einen begrenzen sie die Reichweite der Alternation, und zum anderen fangen sie das
Wort in der Rückwärtsreferenz 1 ein, das tatsächlich gefunden wurde. Wenn die Zeile
mehr als eines der Wörter enthält, enthält die Rückwärtsreferenz das Wort, das in der
Zeile am weitesten rechts steht. Das liegt daran, dass der Stern-Quantor vor den Klam-
mern gierig ist und mit dem Punkt so viel Text wie möglich einfängt. Machen Sie den
Stern genügsam, wie in ‹^.*?\b(eins|zwei|drei)\b.*$›, enthält die Rückwärtsreferenz 1
das Wort aus Ihrer Liste, das am weitesten links steht.
Um Zeilen zu finden, die mehrere Wörter enthalten müssen, verwenden Sie Lookaheads:
^(?=.*?\beins\b)(?=.*?\bzwei\b)(?=.*?\bdrei\b).+$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilen-
umbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Dieser reguläre Ausdruck verwendet positive Lookaheads, um Zeilen zu finden, die drei
notwendige Wörter enthalten. Das ‹.+› wird genutzt, um die eigentliche Zeile zu finden,
nachdem die Lookaheads festgestellt haben, dass die Zeile auch die Anforderungen
erfüllt.

Siehe auch
In Rezept 5.11 wird gezeigt, wie Sie vollständige Zeilen finden, die ein bestimmtes Wort
nicht enthalten.

5.10 Vollständige Zeilen finden, die ein bestimmtes Wort enthalten | 331
5.11 Vollständige Zeilen finden, die ein bestimmtes Wort
nicht enthalten
Problem
Sie wollen vollständige Zeilen finden, die nicht das Wort Ninja enthalten.

Lösung
^(?:(?!\bNinja\b).)*$
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilen-
umbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Um eine Zeile zu finden, die etwas nicht enthält, nutzen Sie negative Lookaheads
(beschrieben in Rezept 2.16). Beachten Sie, dass in diesem regulären Ausdruck ein nega-
tives Lookahead und ein Punkt zusammen in einer nicht-einfangenden Gruppe wieder-
holt werden. Damit ist sichergestellt, dass die Regex ‹\bNinja\b› an jeder möglichen
Position in der Zeile fehlschlägt. Die Anker ‹^› und ‹$› werden an den Anfang und das
Ende des regulären Ausdrucks gesetzt, um auf jeden Fall eine vollständige Zeile zu
finden.
Die Optionen, die Sie auf diesen regulären Ausdruck anwenden, bestimmen, ob der
gesamte Ausgangstext oder immer nur eine Zeile durchforstet wird. Mit der aktiven
Option, ‹^› und ‹$› auf Zeilenumbrüche passen zu lassen, und der deaktivierten Option,
Punkte auf Zeilenumbrüche passen zu lassen, arbeitet dieser reguläre Ausdruck wie
gewünscht zeilenweise. Schalten Sie beide Optionen um, wird der reguläre Ausdruck
jeden String finden, der nicht das Wort „Ninja“ enthält.

Das Testen eines negativen Lookahead an jeder Position einer Zeile oder
eines Strings ist ziemlich ineffizient. Diese Lösung sollte nur in Situa-
tionen genutzt werden, in denen ein einziger regulärer Ausdruck verwen-
det werden kann, zum Beispiel in Anwendungen, die nicht geskriptet
werden können. Beim Programmieren ist Rezept 3.21 eine deutlich effizi-
entere Lösung.

Siehe auch
In Rezept 5.10 wird gezeigt, wie Sie vollständige Zeilen finden können, die ein bestimm-
tes Wort enthalten.

332 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


5.12 Führenden und abschließenden Whitespace entfernen
Problem
Sie wollen führenden und abschließenden Whitespace aus einem String entfernen.

Lösung
Um das Ganze einfach und schnell zu halten, nutzt man am besten zwei Ersetzungs-
durchläufe – einen, um den führenden Whitespace zu entfernen, und einen für den
abschließenden Whitespace:
^\s+
Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht aktiv sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
\s+$
Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht aktiv sein)
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Ersetzen Sie die gefundenen Übereinstimmungen beider regulären Ausdrücke einfach
durch einen leeren String. In Rezept 3.14 können Sie nachlesen, wie das funktioniert. Bei
beiden regulären Ausdrücken müssen Sie nur die erste gefundene Übereinstimmung ent-
fernen, da der gesamte führende beziehungsweise abschließende Whitespace in einem
Schritt gefunden wird.

Diskussion
Führenden und abschließenden Whitespace zu entfernen, ist eine einfache und häufig ver-
langte Aufgabe. Die beiden regulären Ausdrücke enthalten jeweils drei Elemente: einen
Anker, um die Position am Anfang oder Ende des Strings sicherzustellen (‹^› und ‹$›), die
Zeichenklassenabkürzung, um beliebige Whitespace-Zeichen zu finden (‹\s›), und den
Quantor, mit dem die Klasse einmal oder mehrfach gefunden werden kann (‹+›).
Viele Programmiersprachen stellen schon eine Funktion bereit, mit der führender oder
abschließender Whitespace entfernt werden kann. Diese Funktionen heißen oft trim
oder strip. In Tabelle 5-2 sehen Sie, wie Sie diese eingebauten Funktionen oder Metho-
den in einer Reihe von Programmiersprachen verwenden.

Tabelle 5-2: Standardfunktionen, um führenden und abschließenden Whitespace zu entfernen


Programmiersprache(n) Beispielanwendung
C#, VB.NET String.Trim([Zeichen])
Java string.trim()
PHP trim($String)
Python, Ruby string.strip()

5.12 Führenden und abschließenden Whitespace entfernen | 333


JavaScript und Perl besitzen solche Funktionen nicht in ihren Standardbibliotheken, aber
Sie können leicht eine eigene schreiben.
In Perl:
sub trim {
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}

In JavaScript:
function trim (string) {
return string.replace(/^\s+/, '').replace(/\s+$/, '');
}

// Alternativ trim zu einer Methode aller Strings machen:


String.prototype.trim = function () {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};

Sowohl in Perl als auch in JavaScript passt ‹\s› auf alle Zeichen, die durch
den Unicode-Standard als Whitespace definiert sind – also mehr als nur
Leerzeichen, Tab, Line Feed und Carriage Return.

Variationen
Es gibt eigentlich eine ganze Reihe von Möglichkeiten, mit einem regulären Ausdruck
einen String zu „trimmen“. Aber alle sind bei langen Strings (bei denen Performance
durchaus ein Thema ist) langsamer als die beiden einfachen Ersetzungen. Im Folgenden
finden Sie ein paar gebräuchliche Alternativen, die Sie in Betracht ziehen könnten. Alle
sind in JavaScript geschrieben, und da JavaScript die Option Punkt passt auf Zeilen-
umbruch nicht besitzt, greifen die regulären Ausdrücke auf ‹[\s\S]› zurück, um ein belie-
biges Zeichen zu finden – auch Zeilenumbrüche. In anderen Programmiersprachen
verwenden Sie einen Punkt und aktivieren die Option Punkt passt auf Zeilenumbruch.
string.replace(/^\s+|\s+$/g, '');
Dies ist die vermutlich gebräuchlichste Lösung. Sie kombiniert die beiden einfachen
Regexes per Alternation (siehe Rezept 2.8) und nutzt die Option /g (global), um alle
Übereinstimmungen zu ersetzen (wenn es sowohl führenden als auch abschließen-
den Whitespace gibt, passt die Regex zwei Mal). Das ist kein wirklich grässlicher
Ansatz, aber er ist bei langen Strings langsamer als die Verwendung der beiden ein-
fachen Ersetzungen.
string.replace(/^\s*([\s\S]*?)\s*$/, '$1')
Dieser reguläre Ausdruck passt auf den gesamten String und fängt den Text vom
ersten bis zum letzten Nicht-Whitespace-Zeichen (wenn es denn welchen gibt) in
der Rückwärtsreferenz 1. Ersetzen Sie den gesamten String durch die Rückwärtsrefe-
renz 1, erhalten Sie eine zurechtgestutzte Version des Strings.

334 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Dieser Ansatz ist prinzipiell einfach, aber der genügsame Quantor innerhalb der ein-
fangenden Gruppe sorgt dafür, dass die Regex-Engine eine ganze Menge zu tun hat.
Daher wird dieser reguläre Ausdruck bei langen Strings eher langsam sein. Hat die
Regex-Engine die einfangende Gruppe erreicht, sorgt der genügsame Quantor dafür,
dass die Zeichenklasse ‹[\s\S]› so wenig wie möglich gefunden wird. Daher greift
die Regex-Engine immer nur auf ein Zeichen gleichzeitig zu und versucht, nach
jedem Zeichen das restliche Muster (‹\s*$›) zu finden. Wenn das nicht funktio-
niert, weil es keine Nicht-Whitespace-Zeichen mehr nach der aktuellen Position im
String gibt, nimmt die Engine ein weiteres Zeichen dazu und versucht dann erneut,
das restliche Muster zu finden.
string.replace(/^\s*([\s\S]*\S)?\s*$/, '$1')
Diese Regex ist der vorherigen sehr ähnlich, aber der genügsame Quantor wird aus
Performancegründen durch einen gierigen ersetzt. Um sicherzustellen, dass die ein-
fangende Gruppe trotzdem nur bis zum letzten Nicht-Whitespace-Zeichen läuft,
nutzen wir ein abschließendes ‹\S›. Da die Regex dennoch auch Strings aus reinen
Whitespaces erkennen muss, ist die gesamte einfangende Gruppe optional, indem
ihr ein abschließender Fragezeichen-Quantor angehängt wird.
Lassen Sie uns genauer anschauen, wie diese Regex arbeitet. Hier wiederholt der
gierige Stern in ‹[\s\S]*› das Muster für jedes Zeichen, bis das Ende des Strings
erreicht ist. Die Regex-Engine geht dann vom Ende des Strings per Backtracking ein
Zeichen zurück, bis sie das folgende ‹\S› finden kann oder bis wieder das erste
gefundene Zeichen innerhalb der einfangenden Gruppe erreicht wurde. Sofern es
nicht mehr abschließende Whitespace-Zeichen als Text gibt, ist diese Regex im All-
gemeinen schneller als die vorherige mit dem genügsamen Quantor. Aber auch sie
kann die Performance der beiden einfachen Substitutionen nicht erreichen.
string.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, '$1')
Das ist ein recht verbreiteter Ansatz, daher soll er hier als schlechtes Beispiel mit
aufgeführt werden. Es gibt keinen guten Grund, diese Regex zu verwenden, da sie
langsamer als alle anderen hier vorgestellten Regexes ist. Sie entspricht den letzten
beiden Regexes, da sie den gesamten String findet und Sie ihn durch den Teil erset-
zen, den Sie beibehalten wollen. Aber da die innere nicht-einfangende Gruppe
immer nur ein Wort gleichzeitig findet, gibt es eine Reihe von getrennten Schritten,
die die Regex-Engine durchführen muss. Beim Zurechtstutzen kurzer Strings wird
man den Performanceverlust nicht bemerken, aber bei sehr langen Strings mit vie-
len Wörtern kann diese Regex zu einem echten Performanceproblem werden.
Ein paar Regex-Implementierungen enthalten pfiffige Optimierungen, die die hier
beschriebenen internen Suchprozesse verändern und damit einige der vorgestellten
Regexes etwas schneller oder langsamer laufen lassen, als wir beschrieben haben. Aber
die bestechende Einfachheit bei der vorgeschlagenen Lösung mit den beiden Ersetzungen
führt zu einer durchgehend guten Performance – bei unterschiedlichen String-Längen
und -Inhalten. Daher ist es die beste Lösung.

5.12 Führenden und abschließenden Whitespace entfernen | 335


Siehe auch
Rezept 5.13.

5.13 Wiederholten Whitespace durch ein einzelnes


Leerzeichen ersetzen
Problem
Um Benutzereingaben oder andere Daten zunächst „aufzuräumen”, wollen Sie wieder-
holte Whitespace-Zeichen durch ein einzelnes Leerzeichen ersetzen. Jegliche Tabs, Zeilen-
umbrüche oder andere Whitespace-Zeichen sollten ebenfalls durch ein Leerzeichen
ersetzt werden.

Lösung
Um einen der folgenden regulären Ausdrücke zu implementieren, ersetzen Sie einfach
alle Übereinstimmungen durch ein einzelnes Leerzeichen. In Rezept 3.14 ist beschrieben,
wie der Code dafür aussieht.

Alle Whitespace-Zeichen finden


\s+
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Horizontale Whitespace-Zeichen finden


[z\t]+
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Häufig soll man in einem Text alle Whitespace-Zeichen durch ein einzelnes Leerzeichen
ersetzen. In HTML werden zum Beispiel wiederholte Whitespace-Zeichen beim Rendern
einer Seite schlicht ignoriert (von ein paar Ausnahmen abgesehen), daher kann man
durch das Entfernen von überflüssigem Whitespace die Dateigröße reduzieren, ohne sich
negative Effekte einzuhandeln.

336 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


Alle Whitespace-Zeichen finden
In dieser Lösung wird jede Folge von Whitespace-Zeichen (Zeilenumbrüchen, Tabs,
Leerzeichen und so weiter) durch ein einzelnes Leerzeichen ersetzt. Da der Quantor ‹+›
die Whitespace-Klasse (‹\s›) ein Mal oder mehrfach findet, wird zum Beispiel selbst ein
einfaches Tab-Zeichen durch ein Leerzeichen ersetzt. Ersetzen Sie das ‹+› durch ‹{2,}›,
werden nur Folgen von zwei oder mehr Whitespace-Zeichen ersetzt. Das kann zu weni-
ger Ersetzungsvorgängen und damit einer verbesserten Performance führen, aber so kön-
nen auch Tab-Zeichen oder Zeilenumbrüche übrig bleiben, die ansonsten durch
Leerzeichen ersetzt worden wären. Der bessere Ansatz hängt daher davon ab, was Sie
erreichen wollen.

Horizontale Whitespace-Zeichen finden


Diese Regex arbeitet genau so wie die vorherige Lösung, nur dass sie Zeilenumbrüche
bestehen lässt. Lediglich Tabs und Leerzeichen werden ersetzt.

Siehe auch
Rezept 5.12.

5.14 Regex-Metazeichen maskieren


Problem
Sie wollen einen literalen String verwenden, der von einem Anwender oder aus einer
anderen Quelle stammt, um ihn in Ihren regulären Ausdruck einzubauen. Allerdings
wollen Sie alle Regex-Metazeichen innerhalb des Strings maskieren, bevor Sie ihn in Ihre
Regex einbetten, um unerwünschte Nebeneffekte zu verhindern.

Lösung
Indem Sie vor jedem Zeichen, das in einem regulären Ausdruck potenziell eine besondere
Bedeutung haben kann, einen Backslash einfügen, können Sie das sich so ergebende
Muster problemlos verwenden, um eine literale Zeichenfolge zu finden. Von den in die-
sem Buch behandelten Programmiersprachen haben abgesehen von JavaScript alle eine
eingebaute Funktion oder Methode, um diese Aufgabe zu erledigen (siehe Tabelle 5-3).
Aus Gründen der Vollständigkeit zeigen wir hier aber, wie Sie das mit Ihrer eigenen
Regex erreichen – auch in Sprachen, die schon eine fertige Lösung bieten.

Eingebaute Lösungen
In Tabelle 5-3 sind die vorgefertigten Funktionen aufgeführt, die dieses Problem lösen.

5.14 Regex-Metazeichen maskieren | 337


Tabelle 5-3: Eingebaute Lösungen für das Maskieren von Regex-Metazeichen
Sprache Funktion
C#, VB.NET Regex.Escape(str)
Java Pattern.quote(str)
Perl quotemeta(str)
PHP preg_quote(str, [delimiter])
Python re.escape(str)
Ruby Regexp.escape(str)

In dieser Liste fehlt JavaScript – es hat keine eingebaute Funktion, die diesen Zweck
erfüllt.

Regulärer Ausdruck
Auch wenn am besten eine eingebaute Funktion genutzt wird (wenn es sie denn gibt),
können Sie auch den folgenden regulären Ausdruck mit dem entsprechenden Ersetzungs-
String verwenden. Stellen Sie aber sicher, dass nicht nur der erste, sondern alle Überein-
stimmungen ersetzt werden. In Rezept 3.15 finden Sie Code, mit dem Sie Übereinstim-
mungen durch Text ersetzen können, die eine Rückwärtsreferenz enthalten. Die
Rückwärtsreferenz ist hier notwendig, um das gefundene Spezialzeichen zusammen mit
einem führenden Backslash wieder einfügen zu können:
[[\]{}()*+?.\\|^$\-,&#\s]
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung

Die folgenden Ersetzungs-Strings enthalten einen literalen Backslash. Die


Strings werden ohne zusätzliche Backslashs aufgeführt, die notwendig sein
können, um die Backslashs bei String-Literalen zu maskieren. In Rezept 2.19
finden Sie mehr Details über Ersetzungstextvarianten.

\$&
Ersetzungstextvarianten: .NET, JavaScript
\\$&
Ersetzungstextvariante: Perl
\\$0
Ersetzungstextvarianten: Java, PHP
\\\0
Ersetzungstextvarianten: PHP, Ruby

338 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


\\\&
Ersetzungstextvariante: Ruby
\\\g<0>
Ersetzungstextvariante: Python

Beispielfunktion in JavaScript
Hier sehen Sie ein Beispiel dafür, wie Sie in JavaScript mit dem regulären Ausdruck und
dem Ersetzungstext eine statische Methode RegExp.escape erstellen können:
RegExp.escape = function (str) {
return str.replace(/[[\]{}()*+?.\\|^$\-,&#\s]/g, "\\$&");
};

// Testen
var str = "Hello.World?";
var escaped_str = RegExp.escape(str);
alert(escaped_str == "Hallo\\.Welt\\?"); // -> true

Diskussion
Der reguläre Ausdruck in diesem Rezept steckt alle Regex-Metazeichen in eine einzelne
Zeichenklasse. Lassen Sie uns jedes dieser Zeichen anschauen und erklären, warum es
maskiert werden muss. Bei einigen ist das weniger offensichtlich als bei anderen:
[] {} ()
‹[› und ‹]› erzeugen Zeichenklassen. ‹{› und ‹}› erzeugen Intervall-Quantoren
und werden auch in verschiedenen anderen Konstrukten genutzt, wie zum Beispiel
bei Unicode-Eigenschaften. ‹(› und ‹)› werden zum Gruppieren, Einfangen und in
anderen Konstrukten verwendet.
* + ?
Diese drei Zeichen sind Quantoren, die das vorherige Element null Mal oder häufi-
ger, ein Mal oder häufiger oder null oder ein Mal wiederholen. Das Fragezeichen
wird zudem nach einer öffnenden Klammern genutzt, um besondere Gruppen und
andere Konstrukte zu erzeugen (das Gleiche gilt für den Stern in Perl 5.10 und
PCRE 7).
. \ |
Ein Punkt passt auf jedes Zeichen innerhalb einer Zeile oder eines Strings, ein Back-
slash maskiert ein Sonderzeichen oder sorgt dafür, dass ein literales Zeichen etwas
Besonderes darstellt. Ein vertikaler Balken dient als Alternationsoption.
^ $
Zirkumflex und Dollar sind Anker, die den Anfang oder das Ende einer Zeile oder
eines Strings finden. Der Zirkumflex kann zudem eine Zeichenklasse negieren.
Die restlichen Zeichen, die von diesem regulären Ausdruck gefunden werden, haben nur
in besonderen Fällen eine spezielle Bedeutung. Sie sind in der Liste enthalten, um auf
Nummer sicher gehen zu können.

5.14 Regex-Metazeichen maskieren | 339


-
Ein Bindestrich erzeugt in einer Zeichenklasse einen Bereich. Er wird hier maskiert,
um das unabsichtliche Erstellen von Bereichen zu vermeiden, wenn in einer Zei-
chenklasse Text eingefügt wird. Wenn Sie Text in eine Zeichenklasse einfügen, soll-
ten Sie daran denken, dass damit nicht der eingebettete Text gefunden wird,
sondern nur eines der Zeichen aus dem String.
,
Ein Komma wird innerhalb eines Intervall-Quantors wie zum Beispiel ‹{1,5}› ver-
wendet. Da die meisten Regex-Varianten geschweifte Klammern als literale Zeichen
betrachten, wenn sie keinen gültigen Quantor bilden, ist es möglich (wenn auch
recht unwahrscheinlich), durch das Einfügen von Text ohne maskierte Kommata
einen Quantor an einer Stelle zu erzeugen, an der dies so gar nicht gedacht war.
&
Das Kaufmanns-Und ist in der Liste enthalten, weil zwei aufeinanderfolgende Kauf-
manns-Und-Zeichen in Java eine Zeichenklassen-Schnittmenge erzeugen. In anderen
Programmiersprachen kann man das Kaufmanns-Und gefahrlos aus der Liste der zu
maskierenden Zeichen entfernen, aber es macht auch nichts, es drin zu lassen.
# und Whitespace
Das Rautezeichen und Whitespace (durch ‹\s› gefunden) sind nur im Freiform-
Modus Metazeichen. Auch für sie gilt, dass es nicht schadet, sie zu maskieren.
Beim Ersetzungstext wird eines von fünf Tokens («$&», «\&», «$0», «\0» oder «\g<0>»)
genutzt, um die gefundenen Zeichen zusammen mit einem führenden Backslash wieder
einzufügen. In Perl ist $& eine echte Variable. Nutzt man sie in einem regulären Ausdruck,
kann das nachteilige Auswirkungen auf alle regulären Ausdrücke haben. Wird $&
irgendwo anders in Ihrem Perl-Programm verwendet, können Sie sie so viel verwenden,
wie Sie wollen, weil der Performanceverlust nur einmal auftritt. Ansonsten ist es vermut-
lich besser, die gesamte Regex in eine einfangende Gruppe zu stecken und im Ersetzungs-
text $1 statt $& zu nutzen.

Variationen
Wie schon in „Blockmaskierung“ auf Seite 29 in Rezept 2.1 beschrieben, können Sie in
einem regulären Ausdruck eine Blockmaskierungsfolge erzeugen, indem Sie ‹\Q...\E›
nutzen. Aber solche Blockmaskierungen werden nur in Java, der PCRE und Perl unter-
stützt, und selbst in diesen Sprachen sind sie nicht absolut idiotensicher. Um vollständig
sicher zu sein, müssen Sie immer noch jedes Vorkommen von \E maskieren. In den meis-
ten Fällen ist es vermutlich günstiger, einfach alle Regex-Metazeichen zu maskieren.

Siehe auch
In Rezept 2.1 wird besprochen, wie man literale Zeichen findet. Die dort aufgeführte
Liste mit zu maskierenden Zeichen ist kürzer, da sie sich nicht um Zeichen kümmert, die
im Freiform-Modus oder beim Einfügen in ein längeres Muster maskiert werden müssen.

340 | Kapitel 5: Wörter, Zeilen und Sonderzeichen


KAPITEL 6
Zahlen

Reguläre Ausdrücke sind eigentlich dazu gedacht, mit Texten zu arbeiten. Sie verstehen
nichts von der numerischen Bedeutung, die Menschen einer Folge von Ziffern zuordnen.
Für einen regulären Ausdruck ist 56 nicht die Nummer sechsundfünfzig, sondern ein
String mit zwei Zeichen, die die Ziffern 5 und 6 darstellen. Die Regex-Engine weiß, dass
es sich um Ziffern handelt, da sie über die Zeichenklassenabkürzung ‹\d› gefunden wer-
den können (siehe Rezept 2.3). Aber das war es auch schon. Die Engine weiß nicht, dass
56 eine höhere Bedeutung besitzt, so wie sie auch nicht weiß, dass man in :-) mehr
sehen kann als drei Satzzeichen, die durch ‹\p{P}{3}› gefunden werden.
Aber Zahlen sind ein wichtiger Bestandteil der Benutzereingaben oder Dateiinhalte, mit
denen Sie arbeiten. Manchmal müssen Sie sie innerhalb eines regulären Ausdrucks fin-
den, statt sie einfach an eine normale Programmiersprache zu geben – zum Beispiel wenn
Sie herausbekommen möchten, ob eine Zahl im Bereich von 1 bis 100 liegt. Deshalb
handelt dieses ganze Kapitel davon, wie man mit regulären Ausdrücken alle möglichen
Arten von Zahlen finden kann. Wir beginnen mit ein paar Rezepten, die vielleicht trivial
erscheinen, aber wichtige grundlegende Konzepte aufzeigen. Die folgenden Rezepte dre-
hen sich dann um kompliziertere Regexes, und dort wird davon ausgegangen, dass Sie
diese Konzepte verstanden haben.

6.1 Integer-Zahlen
Problem
Sie wollen verschiedene Arten von ganzen Zahlen in einem längeren Text finden oder
prüfen, ob eine String-Variable eine dezimale Ganzzahl enthält.

| 341
Lösung
Positive dezimale Ganzzahlen (Integer-Zahlen) in einem längeren Text finden:
\b[0-9]+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Prüfen, ob ein String nur aus einer positiven dezimalen Ganzzahl besteht:
\A[0-9]+\Z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^[0-9]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
Finden einer positiven dezimalen Ganzzahl, die in einem größeren Text für sich steht:
(?<=^|\s)[0-9]+(?=$|\s)
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby 1.9
Finden einer positiven dezimalen Ganzzahl, die in einem größeren Text für sich steht,
wobei führender Whitespace mit gefunden werden soll:
(^|\s)([0-9]+)(?=$|\s)
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Finden einer beliebigen dezimalen Ganzzahl mit einem optionalen Vorzeichen:
[+-]?\b[0-9]+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Prüfen, ob ein String nur aus einer dezimalen Ganzzahl mit einem optionalen Vorzeichen
besteht:
\A[+-]?[0-9]+\Z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^[+-]?[0-9]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

342 | Kapitel 6: Zahlen


Finden einer dezimalen Ganzzahl mit einem optionalen Vorzeichen, wobei sich zwischen
Vorzeichen und Zahl Leerzeichen befinden dürfen. Whitespace ohne Vorzeichen werden
dabei nicht gefunden:
([+-]z*)?\b[0-9]+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Eine ganze Zahl besteht aus der Folge einer oder mehrerer Ziffern zwischen null und
neun. Dies lässt sich leicht durch eine Zeichenklasse (Rezept 2.3) und einen Quantor
(Rezept 2.12) darstellen: ‹[0-9]+›.

Wir nutzen lieber den expliziten Bereich ‹[0-9]› statt die Abkürzung ‹\d›.
In .NET und Perl passt ‹\d› auf jede Ziffer aus einem beliebigen Schriftsys-
tem, während ‹[0-9]› immer nur die zehn Ziffern aus der ASCII-Tabelle
findet. Wenn Sie wissen, dass Ihr Ausgangstext keine Nicht-ASCII-Ziffern
enthält, können Sie sich ein paar Tastendrücke sparen und ‹\d› statt ‹[0-
9]› verwenden.
Sollten Sie nicht wissen, ob Ihr Ausgangstext auch Ziffern enthält, die
nicht in der ASCII-Tabelle zu finden sind, müssen Sie überlegen, was Sie
mit den Regex-Übereinstimmungen machen wollen und was der Anwen-
der erwartet, um zu entscheiden, ob Sie ‹\d› oder ‹[0-9]› verwenden.
Wenn Sie den vom regulären Ausdruck gefundenen Text in eine Integer-
Zahl umwandeln wollen, müssen Sie herausfinden, ob die Funktion zum
Umwandeln von Strings in Integer-Zahlen in Ihrer Programmiersprache
auch mit Nicht-ASCII-Ziffern umgehen kann. Wenn jemand Dokumente
in seinem Schriftsystem schreibt, wird er davon ausgehen, dass Ihre Soft-
ware auch Ziffern in diesem Schriftsystem erkennt.

Abgesehen davon, dass es sich bei der Zahl um eine Folge von Ziffern handelt, muss sie
auch für sich allein stehen. A4 ist eine Papiergröße, aber keine Zahl. Es gibt eine Reihe
von Möglichkeiten, mit denen sichergestellt werden kann, dass Ihre Regex nur reine Zah-
len findet.
Wenn Sie prüfen wollen, ob Ihr String nichts anderes außer einer Zahl enthält, ergänzen
Sie Ihre Regex einfach um String-Anfangs- und String-Ende-Anker. ‹\A› und ‹\Z› sind da
die beste Wahl, da sich ihre Bedeutung nicht ändert. Leider werden sie nicht von Java-
Script unterstützt. In JavaScript verwenden Sie ‹^› und ‹$› und stellen sicher, dass Sie
nicht die Option /m verwenden, mit der Zirkumflex und Dollar an Zeilenumbrüchen pas-
sen. In Ruby passen Zirkumflex und Dollar immer auf Zeilenumbrüche, daher können
Sie sie dort nicht zuverlässig verwenden, um mit Ihrer Regex den ganzen String zu finden.

6.1 Integer-Zahlen | 343


Wenn Sie nach Zahlen in einem längeren Text suchen, sind Wortgrenzen (Rezept 2.6)
eine einfache Lösung. Platzieren Sie diese vor oder nach einem Regex-Token, mit dem
eine Ziffer gefunden wird, stellen die Wortgrenzen sicher, dass es direkt vor oder nach
der gefundenen Ziffer kein Wortzeichen gibt. So passt zum Beispiel ‹4› auf 4 in A4. ‹4\b›
passt ebenfalls, weil es nach der 4 kein Wortzeichen gibt. ‹\b4› und ‹\b4\b› passen nicht
auf A4, da ‹\b› zwischen den beiden Wortzeichen A und 4 eben nicht passt. In regulären
Ausdrücken gehören Buchstaben, Ziffern und der Unterstrich zu Wortzeichen.
Wenn Sie Nicht-Wortzeichen wie ein Plus- oder Minuszeichen oder Whitespace in Ihre
Regex aufnehmen, müssen Sie beim Setzen der Wortgrenzen vorsichtig sein. Um +4 zu
finden, +4B aber auszuschließen, verwenden Sie ‹\+4\b› statt ‹\b\+4\b›. Letzteres passt
nicht zu +4, da es kein Wortzeichen vor dem Plus gibt, durch das die Wortgrenze passt.
‹\b\+4\b› passt auf +4 im Text 3+4, da 3 ein Wortzeichen und + kein Wortzeichen ist.
‹\+4\b› benötigt nur eine Wortgrenze. Das erste ‹\b› in ‹\+\b4\b› ist überflüssig. Wenn
diese Regex passt, findet sich das erste ‹\b› immer zwischen einem + und einer 4,
wodurch nie etwas ausgeschlossen wird. Das erste ‹\b› wird dann wichtig, wenn das
Pluszeichen optional ist. ‹\+?\b4\b› passt nicht auf 4 in A4, ‹\+?4\b› schon.
Wortgrenzen sind nicht immer die richtige Lösung. Schauen Sie sich den Ausgangstext
€ 123.456,78 an. Wenn Sie mit der Regex ‹\b[0-9]+\b› über diesen String iterieren, passt
sie auf 123, 456 und 78. Das Eurozeichen, der Punkt und das Komma sind keine Wortzei-
chen, daher passen die Wortgrenzen zwischen einer Ziffer und einem dieser Zeichen. Das
mag manchmal das gewünschte Ergebnis liefern, manchmal aber auch nicht.
Wenn Sie nur Ganzzahlen finden wollen, die von Whitespace oder dem Anfang oder
Ende eines Strings begrenzt sind, benötigen Sie Lookarounds statt Wortgrenzen.
‹(?=$|\s)› passt auf das Ende des Strings oder vor einem Whitespace-Zeichen (dazu
gehören auch Zeilenumbrüche). ‹(?<=^|\s)› passt entweder auf den Anfang des Strings
oder nach einem Whitespace-Zeichen. Sie können ‹\s› auch durch eine Zeichenklasse
ersetzen, die auf eines der Zeichen passt, die Sie vor oder nach der Zahl zulassen wollen.
In Rezept 2.16 erfahren Sie, wie Lookarounds arbeiten.
JavaScript und Ruby 1.8 unterstützen keine Lookbehinds. Sie können stattdessen eine
normale Gruppe verwenden, um herauszufinden, ob die Zahl am Anfang des Strings
steht oder ob sich vor ihr Whitespace befindet. Der Nachteil ist, dass das Whitespace-
Zeichen Teil des Suchergebnisses ist, wenn die Zahl nicht am String-Anfang steht. Um
dieses Problem zu umgehen, können Sie den Teil der Regex, der die Zahl findet, in eine
einfangende Gruppe stecken. Die fünfte Regex im Abschnitt „Lösung“ fängt das
Whitespace-Zeichen in der ersten einfangenden Gruppe und die Zahl in der zweiten ein-
fangenden Gruppe ein.

Siehe auch
Rezepte 2.3 und 2.12.

344 | Kapitel 6: Zahlen


6.2 Hexadezimale Zahlen
Problem
Sie wollen hexadezimale Zahlen in einem längeren Text finden oder prüfen, ob eine
String-Variable eine hexadezimale Zahl enthält.

Lösung
Finden einer hexadezimalen Zahl in einem längeren Text:
\b[0-9A-F]+\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
\b[0-9A-Fa-f]+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Prüfen, ob ein String nur eine hexadezimale Zahl enthält:
\A[0-9A-F]+\Z
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^[0-9A-F]+$
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
Eine hexadezimale Zahl mit dem Präfix 0x finden:
\b0x[0-9A-F]+\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Eine hexadezimale Zahl mit dem Präfix &H finden:
&H[0-9A-F]+\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Eine hexadezimale Zahl mit dem Suffix H finden:
\b[0-9A-F]+H\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

6.2 Hexadezimale Zahlen | 345


Einen hexadezimalen Bytewert (8 Bit) finden:
\b[0-9A-F]{2}\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen hexadezimalen Wortwert (16 Bit) finden:
\b[0-9A-F]{4}\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen hexadezimalen Doppelwortwert (32 Bit) finden:
\b[0-9A-F]{8}\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen hexadezimalen Quad-Wortwert (64 Bit) finden:
\b[0-9A-F]{16}\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen String mit hexadezimalen Bytes finden (also eine gerade Anzahl an hexadezimalen
Ziffern):
\b(?:[0-9A-F]{2})+\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion
Um hexadezimale Zahlen mit einem regulären Ausdruck zu finden, geht man genau so
vor wie bei dezimalen Ganzzahlen. Der Unterschied ist nur, dass die Zeichenklasse, mit
der eine einzelne Ziffer gefunden wird, jetzt auch die Buchstaben A bis F enthalten muss.
Sie müssen sich überlegen, ob die Buchstaben entweder nur als Groß- oder Kleinbuchsta-
ben erkannt werden sollen oder ob auch gemischte Angaben zulässig sein sollen. Die hier
gezeigten regulären Ausdrücke lassen allesamt gemischte Angaben zu.
Standardmäßig achten regulären Ausdrücke auf Groß- und Kleinschreibung. ‹[0-9a-f]›
lässt nur Kleinbuchstaben für die hexadezimalen Ziffern a bis f zu, während mit ‹[0-9A-
F]› nur Großbuchstaben erlaubt sind. Um auch gemischte Schreibweisen zu ermögli-
chen, verwenden Sie ‹[0-9a-fA-F]› oder schalten die Option ein, mit der Ihr regulärer
Ausdruck Groß- und Kleinschreibung ignoriert. In Rezept 3.4 ist beschrieben, wie Sie das
mit den in diesem Buch behandelten Programmiersprachen erreichen. Die erste Regex in

346 | Kapitel 6: Zahlen


dieser Lösung ist doppelt aufgeführt, um die beiden möglichen Wege zu zeigen, mit
denen sich Groß- und Kleinschreibung ignorieren lassen können. Die anderen gezeigten
Regexes nutzen nur die zweite Methode.
Wenn Sie lediglich Großbuchstaben in hexadezimalen Zahlen zulassen wollen, verwen-
den Sie die vorgestellten Regexes, schalten aber die Option zum Ignorieren von Groß-
und Kleinschreibung ab. Um nur Kleinbuchstaben zuzulassen, schalten Sie diese Option
ebenfalls ab und ersetzen zusätzlich ‹A-F› durch ‹a-f›.
‹(?:[0-9A-F]{2})+› passt auf eine gerade Zahl hexadezimaler Ziffern. ‹[0-9A-F]{2}› passt
auf genau zwei hexadezimale Ziffern. ‹(?:[0-9A-F]{2})+› ermöglicht ein einmaliges oder
mehrfaches Vorkommen dieses Blocks. Die nicht-einfangende Gruppe (siehe Rezept 2.9)
ist notwendig, weil das Pluszeichen die Kombination aus Zeichenklasse und Quantor
‹{2}› wiederholen soll. ‹[0-9]{2}+› ist in Java, PCRE und Perl 5.10 kein Syntaxfehler,
aber diese Regex tut nicht das, was Sie vielleicht erwarten. Das zusätzliche ‹+› sorgt
dafür, dass die ‹{2}› possessiv wird. Das hat keinen Effekt, da ‹{2}› sowieso nicht weni-
ger als zwei Mal ausgeführt werden kann.
Einige der Lösungen zeigen, wie man dafür sorgen kann, dass die hexadezimale Zahl eine
der häufiger verwendeten Präfixe oder Suffixe besitzt. Diese werden verwendet, um zwi-
schen Dezimalzahlen und Hexadezimalzahlen, die nur aus Dezimalziffern bestehen,
unterscheiden zu können. So kann 10 zum Beispiel die Dezimalzahl zwischen 9 und 11
sein oder die Hexadezimalzahl zwischen F und 11.
Die meisten Lösungen werden mit Wortgrenzen gezeigt (Rezept 2.6). Dadurch können
Sie Zahlen finden, die in einem längeren Text stehen. Beachten Sie, dass die Regex mit
dem Präfix &H keine Wortgrenze am Anfang hat. Das liegt daran, dass das Kaufmanns-
Und kein Wortzeichen ist. Würden wir davor noch eine Wortgrenze setzen, würden wir
nur Hexadezimalzahlen finden, vor denen direkt ein Wortzeichen steht.
Wenn Sie prüfen wollen, ob Ihr String nur eine Hexadezimalzahl enthält, ergänzen Sie
Ihre Regex einfach um Anker für den Anfang und das Ende des Strings. ‹\A› und ‹\Z›
sind die beste Wahl, da sich ihre Bedeutung nicht ändert. Leider werden sie von Java-
Script nicht unterstützt. Dort müssen Sie ‹^› und ‹$› nutzen und darauf achten, dass Sie
nicht die Option /m verwenden, mit der Zirkumflex und Dollar auch bei Zeilenumbrü-
chen passen. In Ruby passen Zirkumflex und Dollar immer auf Zeilenumbrüche, daher
können Sie diese Zeichen dort nicht nutzen, um zuverlässig dafür zu sorgen, dass die
Regex den gesamten String abdeckt.

Siehe auch
Rezepte 2.3 und 2.12.

6.2 Hexadezimale Zahlen | 347


6.3 Binärzahlen
Problem
Sie wollen Binärzahlen in einem längeren Text finden oder prüfen, ob eine String-Vari-
able nur eine Binärzahl enthält.

Lösung
Finden einer Binärzahl in einem längeren Text:
\b[01]+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Prüfen, ob ein Text-String nur eine Binärzahl enthält:
\A[01]+\Z
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby
^[01]+$
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python
Eine Binärzahl mit dem Suffix B finden:
\b[01]+B\b
Regex-Optionen: Groß-/Kleinschreibung ignorieren
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen binären Bytewert (8 Bit) finden:
\b[01]{8}\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen binären Wortwert (16 Bit) finden:
\b[01]{16}\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby
Einen Byte-String finden (zum Beispiel eine Folge mehrerer 8-Bit-Werte):
\b(?:[01]{8})+\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

348 | Kapitel 6: Zahlen


Diskussion
Alle diese Regexes nutzen die Techniken, die in den vorherigen beiden Rezepten
beschrieben wurden. Der Hauptunterschied liegt hier nur darin, dass jede Ziffer entwe-
der eine 0 oder eine 1 ist. Diese lassen sich leicht durch eine kleine Zeichenklasse finden:
‹[01]›.

Siehe auch
Rezepte 2.3 und 2.12.

6.4 Führende Nullen entfernen


Problem
Sie wollen eine Ganzzahl finden und entweder die Nummer ohne führende Nullen
zurückgeben oder diese führenden Nullen entfernen.

Lösung
Regulärer Ausdruck
\b0*([1-9][0-9]*|0)\b
Regex-Optionen: Keine
Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung
$1
Ersetzungstextvarianten: .NET, Java, JavaScript, PHP, Perl
\1
Ersetzungstextvarianten: PHP, Python, Ruby

Auslesen der Zahlen in Perl


while ($subject =~ m/\b0*([1-9][0-9]*|0)\b/g) {
push(@list, $1);
}

Führende Nullen in PHP entfernen


$result = preg_replace('/\b0*([1-9][0-