l ria it to M s-Tu g ie
t ns Ei
Detaillierte Lösungen für acht Programmiersprachen
Reguläre Ausdrücke Kochbuch O’Reilly
Jan Goyvaerts & Steven Levithan 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:
[email protected]
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
..................................... Literalen Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nicht druckbare Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein oder mehrere Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein beliebiges Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Etwas am Anfang und/oder Ende einer Zeile finden. . . . . . . . . . . . . . . . . . Ganze Wörter finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode . . . . . Eine von mehreren Alternativen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . Gruppieren und Einfangen von Teilen des gefundenen Texts . . . . . . . . . . Vorher gefundenen Text erneut finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . Teile des gefundenen Texts einfangen und benennen . . . . . . . . . . . . . . . . Teile der Regex mehrfach wiederholen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Minimale oder maximale Wiederholung auswählen . . . . . . . . . . . . . . . . . Unnötiges Backtracking vermeiden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aus dem Ruder laufende Wiederholungen verhindern. . . . . . . . . . . . . . . . Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden. . . . . . . 2.18 Kommentare für einen regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . . . . 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12 2.13 2.14 2.15 2.16
27 28 30 33 37 39 44 47 59 61 64 66 69 72 75 78 81 87 90
| V
2.19 2.20 2.21 2.22
Literalen Text im Ersetzungstext nutzen. . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Einfügen des Suchergebnisses in den Ersetzungstext . . . . . . . . . . . . . . . . . 95 Teile des gefundenen Texts in den Ersetzungstext einfügen . . . . . . . . . . . . 96 Suchergebniskontext in den Ersetzungstext einfügen . . . . . . . . . . . . . . . . 100
3 Mit regulären Ausdrücken programmieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.20 3.21
Literale reguläre Ausdrücke im Quellcode . . . . . . . . . . . . . . . . . . . . . . . . Importieren der Regex-Bibliothek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erstellen eines Regex-Objekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optionen für reguläre Ausdrücke setzen. . . . . . . . . . . . . . . . . . . . . . . . . . Auf eine Übereinstimmung in einem Text prüfen. . . . . . . . . . . . . . . . . . . Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Auslesen des übereinstimmenden Texts . . . . . . . . . . . . . . . . . . . . . . . . . . Position und Länge der Übereinstimmung ermitteln . . . . . . . . . . . . . . . . Teile des übereinstimmenden Texts auslesen . . . . . . . . . . . . . . . . . . . . . . Eine Liste aller Übereinstimmungen erhalten . . . . . . . . . . . . . . . . . . . . . . Durch alle Übereinstimmungen iterieren . . . . . . . . . . . . . . . . . . . . . . . . . Übereinstimmungen in prozeduralem Code überprüfen . . . . . . . . . . . . . Eine Übereinstimmung in einer anderen Übereinstimmung finden . . . . . Alle Übereinstimmungen ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übereinstimmungen durch Teile des gefundenen Texts ersetzen . . . . . . . Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde . . Alle Übereinstimmungen innerhalb der Übereinstimmungen einer anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Übereinstimmungen zwischen den Übereinstimmungen einer anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen String aufteilen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen String aufteilen und die Regex-Übereinstimmungen behalten . . . . Zeile für Zeile suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 Validierung und Formatierung 4.1 4.2 4.3 4.4 4.5 4.6 4.7
VI | Inhalt
...................................... E-Mail-Adressen überprüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nordamerikanische Telefonnummern validieren . . . . . . . . . . . . . . . . . . . Internationale Telefonnummern überprüfen . . . . . . . . . . . . . . . . . . . . . . Klassische Datumsformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassische Datumsformate exakt validieren . . . . . . . . . . . . . . . . . . . . . . . Klassische Zeitformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datums- und Uhrzeitwerte im Format ISO 8601 validieren . . . . . . . . . . .
109 115 117 123 131 137 142 149 154 162 167 173 177 181 189 194 200 203 208 217 222
227 227 233 239 241 245 250 252
4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18
Eingabe auf alphanumerische Zeichen beschränken . . . . . . . . . . . . . . . . Die Länge des Texts begrenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Zeilenanzahl eines Texts beschränken . . . . . . . . . . . . . . . . . . . . . . . . Antworten auswerten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . US-Sozialversicherungsnummern validieren. . . . . . . . . . . . . . . . . . . . . . . ISBN validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ZIP-Codes validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kanadische Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Britische Postleitzahlen validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deutsche Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.19 Kreditkartennummern validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.20 Europäische Umsatzsteuer-Identifikationsnummern . . . . . . . . . . . . . . . .
5 Wörter, Zeilen und Sonderzeichen 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14
................................... Ein bestimmtes Wort finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eines von mehreren Wörtern finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ähnliche Wörter finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Wörter außer einem bestimmten finden . . . . . . . . . . . . . . . . . . . . . . Ein beliebiges Wort finden, auf das ein bestimmtes Wort nicht folgt . . . Ein beliebiges Wort finden, das nicht hinter einem bestimmten Wort steht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wörter finden, die nahe beieinanderstehen . . . . . . . . . . . . . . . . . . . . . . . Wortwiederholungen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Doppelte Zeilen entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vollständige Zeilen finden, die ein bestimmtes Wort enthalten . . . . . . . . Vollständige Zeilen finden, die ein bestimmtes Wort nicht enthalten . . . Führenden und abschließenden Whitespace entfernen . . . . . . . . . . . . . . Wiederholten Whitespace durch ein einzelnes Leerzeichen ersetzen . . . . Regex-Metazeichen maskieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6 Zahlen 6.1 6.2 6.3 6.4 6.5 6.6
......................................................... Integer-Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hexadezimale Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Binärzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Führende Nullen entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zahlen innerhalb eines bestimmten Bereichs . . . . . . . . . . . . . . . . . . . . . . Hexadezimale Zahlen in einem bestimmten Bereich finden . . . . . . . . . . .
257 260 265 269 271 274 281 282 282 283 285 288 294
301 301 304 306 310 312 313 317 323 325 330 332 333 336 337
341 341 345 348 349 350 357
Inhalt | VII
6.7 Gleitkommazahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 6.8 Zahlen mit Tausendertrennzeichen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 6.9 Römische Zahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
7 URLs, Pfade und Internetadressen 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13 7.14 7.15 7.16 7.17 7.18 7.19 7.20 7.21 7.22 7.23 7.24 7.25
................................... URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URLs in einem längeren Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . URLs in Anführungszeichen in längerem Text finden . . . . . . . . . . . . . . . URLs mit Klammern in längerem Text finden . . . . . . . . . . . . . . . . . . . . . URLs in Links umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URNs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Generische URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Schema aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . Den Benutzer aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . Den Host aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Port aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Pfad aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Query aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Fragment aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . Domainnamen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IPv4-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IPv6-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Pfad unter Windows validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . Pfade unter Windows in ihre Bestandteile aufteilen . . . . . . . . . . . . . . . . . Den Laufwerkbuchstaben aus einem Pfad unter Windows extrahieren . . Den Server und die Freigabe aus einem UNC-Pfad extrahieren . . . . . . . . Die Ordnernamen aus einem Pfad unter Windows extrahieren . . . . . . . . Den Dateinamen aus einem Pfad unter Windows extrahieren . . . . . . . . . Die Dateierweiterung aus einem Pfad unter Windows extrahieren . . . . . Ungültige Zeichen aus Dateinamen entfernen . . . . . . . . . . . . . . . . . . . . .
367 367 371 373 374 376 377 379 385 386 388 390 392 396 397 398 400 403 417 420 425 426 427 430 431 432
8 Markup und Datenaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435 8.1 8.2 8.3 8.4 8.5
Tags im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-Tags durch <strong> ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Tags im XML-Stil außer <em> und <strong> entfernen . . . . . . . . . . XML-Namen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einfachen Text durch Ergänzen von - und
- Tags nach HTML konvertieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.6 Ein bestimmtes Attribut in Tags im XML-Stil finden . . . . . . . . . . . . . . . .
VIII | Inhalt
442 459 463 466 473 476
8.7 Tags vom Typ
ein Attribut „cellspacing“ hinzufügen, die es noch nicht haben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.8 Kommentare im XML-Stil entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.9 Wörter in Kommentaren im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . 8.10 Ändern der Feldbegrenzer in CSV-Dateien . . . . . . . . . . . . . . . . . . . . . . . . 8.11 CSV-Felder aus einer bestimmten Spalte extrahieren . . . . . . . . . . . . . . . . 8.12 Sektionsüberschriften in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . 8.13 Sektionsblöcke in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.14 Name/Wert-Paare in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . .
Index
..........................................................
481 484 488 493 496 500 502 503
505
Inhalt | IX
Vorwort
Im letzten Jahrzehnt ist die Beliebtheit regulärer Ausdrücke deutlich angestiegen. Heutzutage gibt es in allen verbreiteten Programmiersprachen mächtige Bibliotheken zur Verarbeitung regulärer Ausdrücke. Zum Teil bietet die Sprache sogar selbst die entsprechenden 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önnen, 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 weniger 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 Varianten 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 Implementierung für reguläre Ausdrücke zu kontrollieren. Selbst wenn sie herausfinden, dass ein Feature, 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 Varianten 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 übergangen 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 aufgebaut, 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 vertraut 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 Uneinheitlichkeiten zu beschreiben, die wir in diesen verschiedenen Varianten finden konnten. Das Kapitel zur Programmierung (Kapitel 3) enthält Code-Listings in C#, Java, JavaScript, PHP, Perl, Python, Ruby und VB.NET. Auch hier gibt es zu jedem Rezept Lösungen 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 Postleitzahlen in den verschiedenen Staaten. Kapitel 5, Wörter, Zeilen und Sonderzeichen, behandelt häufig auftretende Textbearbeitungsaufgaben, 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 Funktionsnamen, Werte, die das Ergebnis einer Ersetzung mithilfe eines regulären Ausdrucks sind, und für Elemente oder Eingabetexte, die einem regulären Ausdruck übergeben 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 Suchfeld einer Anwendung eingeben würden. Leerzeichen in regulären Ausdrücken werden 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 ersetzenden 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 entstehen, 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 Eingabetaste 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. Beispielsweise 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 CDROM 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 Kochbuch von Jan Goyvaerts & Steven Levithan, Copyright 2010, O’Reilly Verlag, ISBN 9783-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 [email protected] 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 Fragezeichen in Ihren Code einzubauen. Falls Sie schon so weit sind: nur zu! Die verwendbaren 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 subtilen 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 Informatik. Dort steht er für eine Eigenschaft mathematischer Ausdrücke namens Regularität. Solch ein Ausdruck kann als Software mithilfe eines deterministischen endlichen Automaten (DEA) implementiert werden. Ein DEA ist ein endlicher Automat, der kein Backtracking 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 Ausdrü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 Programmierund 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 besonders spannend zu schreiben ist und der sich auch nur schwer warten lässt. Mit dem passenden 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 Computer 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 Entwickler einer textverarbeitenden Anwendung eine andere Vorstellung davon, was genau ein regulärer Ausdruck tun sollte. Daher sehen wir uns einem ganzen Reigen von Varianten 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, einschließ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. Skriptsprachen 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 programmieren, 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 verbreitetsten sind. Es handelt sich bei allen um Regex-Varianten im Perl-Stil. Manche der Varianten 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 Versionen 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 betreffen, ä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 PerlStil 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 aufzuführen, welche Regex-Variante genau genutzt wird, ist es sehr wahrscheinlich PCRE. .NET Das .NET Framework von Microsoft stellt über das Paket System.Text.RegularExpressions eine vollständige Regex-Variante im Perl-Stil bereit. Dieses Buch behandelt 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 RegexUnterstü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 Entwicklungsumgebung (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 eingebaute Unterstützung regulärer Ausdrücke anbietet. Schnell wurden dadurch die verschiedenen Regex-Bibliotheken von dritter Seite verdrängt. Abgesehen davon, dass es sich um ein Standardpaket handelt, bietet es auch einen vollständige Regex-Variante 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-Variante 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 implementieren alle Version 3 von ECMA-262. Trotzdem gibt es in allen Browsern unterschiedliche 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 RegexVariante 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 behandelt 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 Oniguruma-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 werden. Die Implementatoren haben sogar Features darin gelassen, die man wohl besser 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 werden. Solch eine Funktion übernimmt einen Ausgangstext, einen regulären Ausdruck und einen Ersetzungsstring als Eingabe. Die Ausgabe ist der Ausgangstext, in dem alle Übereinstimmungen mit dem regulären Ausdruck durch den Ersetzungstext ausgetauscht wurden. Obwohl der Ersetzungstext kein regulärer Ausdruck ist, können Sie bestimmte Syntaxelemente 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 Varianten 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 Übereinstimmung 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 Programmierer 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 entsprechende 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 regulä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 Ersetzungen 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ückwä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 Funktion preg_replace. Diese Funktion nutzt die PCRE-Variante für die regulären Ausdrücke und die PHP-Variante für den Ersetzungstext. Andere Programmiersprachen, die PCRE nutzen, verwenden nicht die gleiche Ersetzungstextvariante wie PHP. Abhängig davon, woher die Designer Ihrer Programmiersprache 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 Variante 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 verwenden die gleiche Ersetzungstextvariante. Die neuen Features in .NET 2.0 für die regulä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 Ersetzungstextsyntax 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. Beides ist in Edition 3 des ECMA-262-Standards definiert. Python Pythons Modul re stellt eine Funktion sub bereit, mit der gesucht und ersetzt werden 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 RegexUnterstü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 Standardkompilierung 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ährend 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, empfehlen wir Ihnen, Ihre ersten Experimente in einem Tool durchzuführen und nicht in Quellcode. 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 BackslashDschungel finden. Die in diesem Abschnitt beschriebenen Tools ermöglichen es Ihnen auch, reguläre Ausdrü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 verschiedenen Varianten zu konvertieren. RegexBuddy wurde von Jan Goyvaerts entworfen und entwickelt, einem der Autoren dieses 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 RegexBuddy 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 einfach 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 passenden 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 RegexBuddy die Ergebnisse dynamisch aktualisiert, wenn Sie Ihre Regex oder den Ausgangstext anpassen. Um genau zu sehen, wie Ihre Regex funktioniert (oder warum nicht), klicken Sie im TestPanel 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, welches 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 Quellcode-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 GREPPanel, um eine beliebige Anzahl von Dateien und Ordnern zu durchsuchen (und eventuell Ersetzungen vorzunehmen). Wenn Sie in Code, den Sie warten, eine Regex finden, kopieren Sie sie in die Zwischenablage – 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 Programmiersprache aus. Ihre Regex wird dann in RegexBuddy als pure Regex erscheinen – ohne die zusätzlichen Anführungszeichen oder Maskierungen, die für String-Literale notwendig 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 Bibliothek 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 Benutzerforum 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/RegexBuddyCookbook.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 werden, 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 SyntaxHighlighting an, wodurch Sie auch Syntaxfehler in der Regex erkennen können. RegexPal 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 angenehmsten 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 JavaScript 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 RegexVariante, die von der PHP-Funktion ereg verwendet wird, aber in diesem Buch keine weitere Erwähnung findet. Wenn Sie JavaScript auswählen, arbeiten Sie mit der JavaScriptImplementierung 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 umzuwandeln. Strings oder Arrays, die vom Code zurückgegeben werden, finden Sie im ResultFeld. 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ächtigen, wie zum Beispiel das Ignorieren von Groß- und Kleinschreibung, aber auch implementierungsspezifische 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 unteren 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 klicken 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 nochmals 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üssen 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 beiden 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 aktivieren. im schaltet beide Optionen ein. Auch wenn diese Konventionen ein wenig unpraktisch zu sein scheinen, wenn Sie mit Ruby noch nicht so vertraut sind, entsprechen 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 dieses 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ählen Sie im Edit-Menü den Punkt Paste Regex from Java String aus. Im gleichen Menü können 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 verschiedene Tests ausgeführt werden können: Find Alle Übereinstimmungen zum regulären Ausdruck werden im Ausgangstext hervorgehoben. 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 grafische 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-Button. 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 Automaten, 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-Anwendung, 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 Funktionalitä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 SyntaxHighlighting. Im Bereich Regex Analyzer wird automatisch eine kurze Analyse Ihres regulä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 Ausdruck auch im anderen Modus (Testmodus) erstellen. Im Testmodus geben Sie Ihren Ausgangstext in der linken unteren Ecke ein. Dann klicken 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 Ausgangstext 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 werden. Dabei handelt es sich um eine weitere .NET-Anwendung für das Erstellen und Testen 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 SyntaxHighlighting, 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, welche 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 ersetzen 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 englischer Sprache. Diese wird allerdings nicht automatisch erstellt und ist auch nicht interaktiv. 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 ActionMenü einfach auf Clear und geben Ihren regulären Ausdruck in das Suchfeld des ActionPanels 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 ActionPanels 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 enthalten 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 kostenlose Testversion über http://www.powergrep.com/PowerGREPCookbook.exe herunterladen. 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ängerfristig 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 umzubenennenden 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 kombiniert, 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 brauchen, wenn Sie nur nach literalem Text suchen wollen. Aber wenn Sie eine Regex erstellen, 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 bestimmten 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 finden, 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 vollständig sämtliche Überlegungen zur Programmierung. Das nächste Kapitel ist das mit den ganzen Codebeispielen. Sie können schon mal einen Blick auf „Programmiersprachen und Regex-Varianten“ in Kapitel 3 werfen, um herauszufinden, welche Variante regulärer Ausdrücke Ihre Programmiersprache nutzt. Die Varianten, die in diesem Kapitel behandelt werden, wurden in „Regex-Varianten in diesem Buch“ auf Seite 3, vorgestellt.
| 27
2.1
Literalen Text finden
Problem Erstellen eines regulären Ausdrucks, der genau auf den so hübsch ausgedachten folgenden 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 einfach 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 Bindestrich - 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 Syntaxfehler. 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 Literale 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 verwenden. Fehler in der Implementierung führen dazu, dass reguläre Ausdrü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 einschalten 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 EMPFINDLICHunempfindlichEMPFINDLICH. ‹(?i)› schaltet das Ignorieren von Groß- und Kleinschreibung für den Rest der Regex ein, während ‹(?-i)› dies wieder deaktiviert. Beide lokalen Modifikatoren funktionieren als Umschalter. Rezept 2.10 zeigt, wie man lokale Modus-Modifikatoren mit Gruppen statt mit Umschaltern 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 Syntax, 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 JavaScript-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 ASCIITabelle 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ßgeschrieben 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 Konsolensystemen 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 englischen Alphabet nicht mehr abbilden, daher müssen wir in diesem Fall in unserem regulä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 Kombinationen 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
0 1 2 3 4 5 6 7 8 9 NUL SOH STX ETX EOT ENQ ACK BEL BS HT DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SP ! " # $ % & ' ( ) 0 1 2 3 4 5 6 7 8 9 @ A B C D E F G H I P Q R S T U V W X Y ` a b c d e f g h i p q r s t u v w x y
A B C LF VT FF SUB ESC FS * + , : ; < J K L Z [ \ j k l z { |
D CR GS = M ] m }
E SO RS . > N ^ n ~
F SI US / ? O _ o 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ünsten 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 hexadezimales 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 Zeichenklasse passt zu einem einzelnen Zeichen aus einer Liste möglicher Zeichen. Die drei Klassen 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 innerhalb 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 Metazeichen 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 gefunden, das sich nicht in der Liste befindet. Eine negierte Zeichenklasse passt zu Zeilenumbruchzeichen, 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 dazwischenliegen. Um zu wissen, welche Zeichen das sind, müssen Sie sich die ASCII- oder Unicode-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 dementsprechend 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 (Kyrillisch, 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 anderen 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 Zeilenumbrü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ürzung 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 „Wortzeichen“ 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-Modifikator 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 regulä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 alphanumerischen 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-[Subtrahend]]›. Die Differenz von Zeichenklassen ist insbesondere im Zusammenhang mit UnicodeEigenschaften, -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 Zeichen 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 Kaufmanns-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 Beispiel ‹\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 beliebiges Zeichen zwischen den Anführungszeichen findet, mit Ausnahme eines Zeilenumbruchs. 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 enthielt der Ausgangstext auch nie Zeilenumbrüche. Die in diesem Buch besprochenen Programmiersprachen 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 verschiedenen 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 bevorzugten Programmiersprache lautet – bezeichnen Sie sie am besten als „Punkt passt zu Zeilenumbruch“-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 finden. 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äteren Kapitel behandelt. Aber es ist sehr leicht, den Punkt durch eine passendere Zeichenklasse 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 ModusModifikatoren und deren Fehlen in JavaScript in Abschnitt „Übereinstimmungen unabhä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 regulä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 Zeichen. 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 Zeilenumbrü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 unterstü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 deaktivieren. 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 Beispiel 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 ignorieren. Um die Verarbeitung zu vereinfachen, entfernen Perl-Programmierer häufig die Zeilenumbrü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 Zeilenumbrü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öglich, sowohl den „Singleline“- als auch den „Multiline“-Modus gleichzeitig einzuschalten. 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 Zeilenumbruch, 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 Zeilenumbruch 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 Zeilenumbruch“-Modus einzuschalten. Rubys ‹(?m)› hat nichts mit dem Zirkumflex- und Dollar-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 Ausdrü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“ enthä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 Wortgrenzen 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 einnimmt, 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 „Multiline“-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 auszuschließ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 Zeichen 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 passend 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 werden. JavaScript, PCRE und Ruby betrachten nur ASCII-Zeichen als Wortzeichen. ‹\w› entspricht ‹[a-zA-Z0-9_]›. Bei diesen Varianten können Sie eine Wortsuche in Sprachen vornehmen, 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 Wortzeichen. 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 einzelnen 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 TrademarkZeichen 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 Extended“ 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-8Unterstü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-8Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine UnicodeRegexes.
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-8Unterstützung durch den Muster-Modifikator /u aktiviert. .NET, JavaScript und Python unterstützen keine Unicode-Schriftsysteme. Ruby 1.8 unterstützt gar keine UnicodeRegexes.
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-8Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine UnicodeRegexes.
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›-Syntax 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 einzelnen Unicode-Kategorie. Diese Begriffe haben dabei die gleiche Bedeutung. Es gibt 30 Unicode-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ßschreibung 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 werden, 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 östlichen 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 „Kleinbuchstabe“ 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 werden. ‹\P{Ll}› passt zu den Codepoints mit der Eigenschaft Lu (und jeder anderen Eigenschaft 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 überlappender Bereich von Codepoints. Auch wenn viele Blöcke die Namen von Unicode-Schriftsystemen 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 Eigenschaft ‹\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 unterstü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 zugewiesenen Codepoints bis U+FFFF gehören zu einem der folgenden Schriftsysteme (Skripten): ‹\p{Common}› ‹\p{Arabic}› ‹\p{Armenian}› ‹\p{Bengali}› ‹\p{Bopomofo}› ‹\p{Braille}› ‹\p{Buhid}› ‹\p{CanadianAboriginal}› ‹\p{Cherokee}› ‹\p{Cyrillic}› ‹\p{Devanagari}› ‹\p{Ethiopic}› ‹\p{Georgian}› ‹\p{Greek}› ‹\p{Gujarati}› ‹\p{Gurmukhi}› ‹\p{Han}› ‹\p{Hangul}› ‹\p{Hanunoo}›
‹\p{Katakana}› ‹\p{Khmer}› ‹\p{Lao}› ‹\p{Latin}› ‹\p{Limbu}› ‹\p{Malayalam}› ‹\p{Mongolian}› ‹\p{Myanmar}› ‹\p{Ogham}› ‹\p{Oriya}› ‹\p{Runic}› ‹\p{Sinhala}› ‹\p{Syriac}› ‹\p{Tagalog}› ‹\p{Tagbanwa}› ‹\p{Taile}› ‹\p{Tamil}› ‹\p{Telugu}› ‹\p{Thaana}›
2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 55
‹\p{Hebrew}› ‹\p{Hiragana}› ‹\p{Inherited}› ‹\p{Kannada}›
‹\p{Thai}› ‹\p{Tibetan}› ‹\p{Yi}›
Ein Schriftsystem ist eine Gruppe von Codepoints, die von einem bestimmten menschlichen Schreibsystem genutzt wird. Manche Schriftsysteme, wie zum Beispiel Thai, entsprechen 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 normalerweise 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 letter 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-Codepoints 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 Zeichensä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 behandelten 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 Codepoints 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 Codepoint 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 Unicode-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 Zeichenklassen zu – sofern sie sie überhaupt unterstützen. Das vom Codepoint repräsentierte 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 Schriftsysteme 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 Codepoints. 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 Zeichenklasse 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 entfernen. 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 Schriftsysteme 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 einsames 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 Permutationen des regulären Ausdrucks an jeder Stelle des Ausgangstexts ausprobiert werden, 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 Vorkommen 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 Ausdruck 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 regulären Ausdruck anzuwenden. Sobald eine der Alternativen passt, wird an der ersten Position abgebrochen. Wenn wir im restlichen Text weitersuchen, wird Susanne gefunden. Die vierte Suche findet dann erneut Maria. Bitten Sie die Engine jedoch um eine fünfte Suche, wird diese fehlschlagen, 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 Zeichen 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 PerlStil, 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 Alternativen 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ässiger 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 Wortgrenzen für die ganze Regex reichen, anstatt dass für jede Alternative Paare genutzt werden 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 Werten im Code, der die Regex-Auswertung startet, arbeiten zu können. Es soll davon ausgegangen 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 vorkommen.
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 Wortgrenze. 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 Alternation 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 Gruppen, sobald die schließende Klammer erreicht wird. Findet unsere Regex den Wert 200805-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 Ersetzungstext einfügen, wenn Sie suchen und ersetzen. Rezept 3.9 im nächsten Kapitel erläutert, 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 Gruppen 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 nutzen 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 ModusModifikatoren 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 Minuszeichen 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 Zeilenumbruch (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-mmdd 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 Ausdruck 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 verstehen 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 abgespeichert. Das nächste Token ist der Bindestrich, der als Literal passt. Dann kommt die Rückwärtsreferenz. 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, entdeckt 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-0707 gefunden. Da die Regex-Engine von links nach rechts vorgeht, müssen Sie die einfangenden Klammern 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 Übereinstimmung 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 jahrzehntelangen Tradition der Rückwärtsreferenzen bricht. In JavaScript, zumindest in Implementierungen, die dem JavaScript-Standard folgen, ist eine Rückwärtsreferenz auf eine Gruppe, die noch nicht ausgewertet wurde, immer erfolgreich – so wie eine Rückwärtsreferenz auf eine Gruppe mit NullLä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 eingefangenen Text die beschreibenden Namen „Jahr“, „Monat“ und „Tag“ zugewiesen werden. 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. Einfangen 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)-(?\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\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 nummerierte 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 einfangende Gruppen. Der einzige Unterschied zwischen benannten und nummerierten Gruppen 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 Gruppen lassen Ihren regulären Ausdruck lesbarer und wartbarer werden. Fügt man eine einfangende Gruppe in eine bestehende Regex ein, können sich die Zahlen ändern, die den einfangenden Gruppen zugewiesen sind. Selbstvergebene Namen bleiben dagegen bestehen. Python nutzte die erste Regex-Variante, die benannte Captures unterstützte. Dort wurde die Syntax ‹(?PRegex)› genutzt. Der Name musste aus Wortzeichen bestehen, die durch ‹\w› gefunden werden können. ‹(?P› 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 Captures, die zwei Versionen ermöglicht. ‹(?Regex)› simuliert die Python-Syntax, nur ohne das P. Der Name muss aus Wortzeichen bestehen, die durch ‹\w› gefunden werden können. ‹(?› 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 Captures unterstützte. PCRE 7, die Version, die in Perl 5.10 neue Features ergänzt hat, unterstü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 dementsprechend ä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 nummerierten 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 RegexToken. .NET nutzt die Syntax ‹\k› und ‹\k'Name'›. Beide Versionen sind funktional identisch 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 auftauchen. 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 nummerierter Gruppen mit benannten Gruppen. Anstatt die Unterschiede zu erläutern, empfehle 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 verpflichtenden 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 Alternativen 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 Wiederholungen. 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 bedeutet 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 Übereinstimmung 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ändigen 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 Fragezeichen sind keine Quantoren, sondern einfach Teil der Syntax für nicht-einfangende 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 Übereinstimmungen 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 einfangende Gruppe um den Quantor herum einrichten und nicht die einfangende Gruppe wiederholen: ‹((?:\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 und
und den Text dazwischen finden. Der Text zwischen den Tags kann andere XHTML-Tags enthalten.
Lösung .*?
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 weiterzumachen, 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 folgenden einfachen Ausschnitt eines XHTML-Texts an: Die <em>Aufgabe ist es, den Anfang eines Absatzes zu finden.
Dann müssen Sie das Ende des Absatzes finden.
Es gibt hier zwei öffnende -Tags und zwei schließende
-Tags. Sie wollen das erste mit dem ersten
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: .*
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 -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 Quantors über dessen Minimum hinaus speichert der reguläre Ausdruck eine BacktrackingPosition. 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 ‹
› vollständig gefunden werden kann. Was ist jetzt das Problem? Da der Stern gierig ist, findet der falsche reguläre Ausdruck alles vom ersten in der XHTML-Datei bis zum letzten
. Aber um einen XHTMLAbsatz korrekt zu finden, müssen wir das erste zusammen mit dem ersten folgenden
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 BacktrackingPosition 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 durchlaufen wird, wird sich der Quantor erweitern, bis er seine maximale Anzahl an Wiederholungen erreicht hat oder bis das wiederholte Regex-Token nicht mehr passt. ‹.*?
› nutzt einen genügsamen Quantor, um einen XHTML-Absatz korrekt zu finden. Wenn ‹› passt, macht ‹.*?› nichts außer abzuwarten. Wenn direkt nach dem
ein ‹
› kommt, wird ein leerer Absatz gefunden. Wenn nicht, springt die Engine zurück zu ‹.*?›, das dann ein Zeichen findet. Wenn ‹› immer noch fehlschlägt, greift ‹.*?› auf das nächste Zeichen zurück. Das geht so weiter, bis entweder ‹› 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, werden 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 gleichen 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 ansonsten 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önnen, 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 regulä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 Gruppen. 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 einsetzen. 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 Quantoren. 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 atomare 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 gierigen ‹\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 weiteren 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 anzuwenden, was sich durch einen possessiven Quantor nicht ändert. Wenn die Regex den ganzen 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 angewandten zweiten ‹\b› ein Backtracking durchführt. Die Regex-Engine wird dann (unnötigerweise) ‹\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 werden 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 BacktrackingPositionen 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 vorhanden 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 atomaren 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 Buchstaben 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 werden 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 messbar 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 (?>.*?)(?>.*?)(?>.*?) (?>.*?)(?>.*?]*>)(?>.*?).*?
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenumbruch 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ächsten 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 folgender naiven Lösung beginnen: .*?.*?.*? .*?.*?]*>.*?.*?
Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Wenn Sie diese Regex mit einer korrekten HTML-Datei testen, funktioniert sie wunderbar. ‹.*?› springt über alles andere hinweg, weil wir Punkt passt zu Zeilenumbruch aktiviert 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