Reguläre Zauberformeln

Man nennt sie „reguläre Ausdrücke“ oder englisch „Regular Expressions“, kurz RegEx oder RegExp. Es sind sehr mächtige Werkzeuge, die das Durchsuchen, Zerlegen und Validieren von Zeichenketten nach klaren Regeln ermöglichen. Somit erfreuen sie sich beispielsweise in der Programmierung und auch in der Textverarbeitung großer Beliebtheit. Doch wie es bei mächtigen Werkzeugen üblich ist, wirkt auch die Anwendung solcher Ausdrücke zunächst sehr kompliziert, wodurch sie sich vielen Anwendern nicht wirklich erschließen. Genau hier soll dieser Artikel ansetzen und eine leicht verständliche Einführung in die Thematik liefern.

Einleitung

Bevor nun die Funktionsweise erläutert wird, sollen zuerst die Möglichkeiten in ein paar einfachen Beispielen vorgestellt werden:

  • Textsuche:
    Eine typische Suchfunktion ermöglicht es nur, einen Text nach den genauen Vorkommen einer Zeichenkette zu durchsuchen. Häufig wird diese Funktion noch um Wildcards ergänzt, so dass man per „*hund“ sowohl den Schoß-, als auch den Wachhund findet. Jedoch findet man mit einer solchen Abfrage auch viel „Schund„. Über reguläre Ausdrücke lässt sich eine solche Suche stark erweitern, indem man z.B. direkt nach [Wach ODER Schoß]hund sucht oder vorgibt, dass vor „hund“ noch mehr als zwei weitere Zeichen stehen müssen, wodurch der „Schund“ entfällt.
  • Programmierung:
    Zur Programmierung eines Web-Spiders, der vollautomatisch Internetseiten katalogisieren soll, ist es nötig, die bereits eingetragenen Webseiten nach Links zu durchsuchen. Nun kann man zunächst im Quelltext die < suchen, anschließend prüfen, ob ein a und dann ein href folgt - und abschließend den Link auslesen. Oder man formuliert eine RegEx, die alle gültigen Link-Tags im Dokument sucht und die jeweilige URL zurückgibt.
  • Validierung:
    Es ist sehr ärgerlich, wenn ein Besucher ein Kontaktformular auf einer Webseite ausfüllt und sich dann bei seiner Email-Adresse vertippt. Zwar kann eine RegEx nicht die tatsächliche Existenz einer Adresse ermitteln, aber man kann mit ihr immerhin überprüfen, ob die Adresse eine gültige Form hat. So muss man nicht die Mailadresse mit Orientierung an @ und dem letzten . zerlegen und jeden Teil für sich auf gültige Zeichen prüfen, sondern kann all dies mit einem Einzeiler erledigen.
  • URL-Optimierung:
    Links wie index.php?kategorie=17432&seite=43267 können sich Besucher nicht wirklich gut merken. Außerdem bevorzugen auch Suchmaschinen klare URLs anstatt langer Parameterangaben. Bevor man jetzt jedoch alle Skripte einer Webseite mehrfach unter anderen Namen ablegt (index1_themaname.php, index2_anderesthema.php, usw.) kann man mit dem Apache Module mod_rewrite die eigebene URL mit einer RegEx zerlegen und korrekt an den Server weitergeben. So ruft man zwar index1_themaname.html auf, jedoch wird intern index.php?article=themaname verarbeitet.

An dieser Stelle kann man also festhalten, dass reguläre Ausdrücke vieles Vereinfachen und Verkürzen können. Betrachtet man jedoch als Unkundiger einen solchen Ausdruck, wünscht man sich dann doch schnell ein paar klare Schleifen mit Stringoperationen, da diese Zeichensammlungen eher an Hieroglyphen als an klare Suchabfragen erinnern. Doch hier gilt (wie so oft im Leben): Übung macht den Meister!

Was ist denn ein regulärer Ausdruck? Grundsätzlich kann man sagen: Es handelt sich um ein Entwurftsmuster (pattern) einer Zeichenkette. Auf dessen Basis lassen sich andere Zeichenketten (Strings, Texte, etc.) wie oben beschrieben durchsuchen, zerlegen oder validieren.

Anwendung in der Praxis

Vorweg etwas zur Praxis: Wozu die RegEx letztlich verwendet wird, also was die Rückgabe einer RegEx-Funktion ist, hängt von der verwendeten Funktion der jeweiligen Programmiersprache ab (z.B. TRUE, wenn die Vergleichskette zur RegEx passt, oder einen Array von Indizes, an deren Stelle der Ausdruck gefunden wurde, usw.) – hierzu kann ich nur auf entsprechende Dokumentationen zur jeweiligen Sprache verweisen:

Bei der Verwendung einer RegEx zur Suche in einem Texteditor sollte das Ergebnis klar sein: Es wird das erste Vorkommen einer entsprechenden gesucht und angezeigt. Über eine entsprechende Funktion „Weitersuchen“ o.ä. kann man dann, wie auch bei einer ganz normalen Suche, die nächsten Vorkommen durchblättern. Bei Unklarheiten empfiehlt sich auch hier ein Blick in die Dokumentation der Software.

Literale

Aber nun zurück zur Theorie. Die einfachsten Regular Expressions bestehen aus simplen Literalen. Sprich: Wenn man ein konkretes Zeichen bzw. eine konkrete Zeichenfolge suchen möchte, notiert man dieses bzw. diese als solche:

Zeichenkette: Hallo Welt!
RegEx: a
Gefunden: Hallo Welt!

Zeichenkette: Hallo Welt!
RegEx: elt
Gefunden: Hallo Welt!

Einzig sogenannte Metazeichen dürfen nicht als Literale verwendet werden, da sie eigene Bedeutungen innerhalb einer RegEx haben: [ ] \ ^ . | ? * + ( ) Solche Zeichen müssen mit einem Backslash escaped werden:

Zeichenkette: Hallo Welt?
RegEx: \?
Gefunden: Hallo Welt?

Übrigens sind reguläre Ausdrücke im Normalfalls case-sensitive, also beachten Groß- und Kleinschreibung.

Der Punkt als Alleskönner

Ein ganz einfacher Punkt dient als Platzhalter für beliebige Zeichen (meist außer des Zeilenumbruchs).

RegEx: H.nd
Passende Zeichenkette: Hund
Passende Zeichenkette: Hand

Jedoch sollte ein solcher Punkt nur benutzt werden, wenn dies zwingend nötig ist. Möchte man nur „Hund“ und „Hand“ suchen, würde die obige RegEx aber auch „Hxnd“ oder „H!nd“ liefern.

Zeichenklassen

Um im vorigen Beispiel wirklich nur „Hund“ und „Hand“ zu finden, eignen sich Zeichenklassen. In eckigen Klammern gibt man eine beliebige Menge von Zeichen an, von denen eines an der entsprechenden Stelle stehen muss:

RegEx: H[au]nd
Passende Zeichenkette: Hund
Passende Zeichenkette: Hand
Nicht passende Zeichenkette: Hxnd

Alternativ lassen sich auch Bereiche definieren. So findet [A-Z] einen beliebigen Großbuchstaben, [a-z] einen beliebigen Kleinbuchstaben, [0-9] eine beliebige Ziffer, [A-Za-z] einen beliebigen Buchstaben oder [A-Za-z0-9] gar ein beliebiges alphanumerischen Zeichen. Natürlich lassen sich solche Bereiche auch verkürzen, so dass [0-3] 0, 1, 2 oder 3 entspricht.

RegEx: [A-Z]und
Passende Zeichenkette: Hund
Passende Zeichenkette: Fund
Nicht passende Zeichenkette: rund

Übrigens kann man solche Zeichenklassen auch negieren, so dass [^a] ein beliebiges Zeichen außer a erwartet:

RegEx: H[^a]nd
Passende Zeichenkette: Hund
Passende Zeichenkette: Hxnd
Nicht passende Zeichenkette: Hand

Abschließend muss noch erwähnt werden, dass eine solche Zeichenklasse auch wirklich genau ein entsprechendes Zeichen erwartet, d.h. kein Zeichen führt auch zu keiner Übereinstimmung:

RegEx: sei[dt]
Passende Zeichenkette: seid
Passende Zeichenkette: seit
Nicht passende Zeichenkette: sei

Vordefinierte Zeichenklassen


Damit man das Rad nicht immer wieder neu erfinden muss, gibt es bereits vordefinierte Zeichenklassen. So entspricht \w einem alphanumerischen Zeichen (inkl. Unterstrich), \d entspricht einer beliebigen Ziffer \s entspricht einem Leerzeichen (inkl. Tabulator und Zeilenumbruch). Schreibt man das jeweilige Kürzel groß, so handelt es sich um die entsprechende Negation (\W = Negation von \w).

RegEx: H\wnd
Passende Zeichenkette: Hund
Passende Zeichenkette: Hand

RegEx: H\w\snd
Passende Zeichenkette: Hu nd
Passende Zeichenkette: Ha nd

RegEx: H[\w\s]nd
Passende Zeichenkette: Hund
Passende Zeichenkette: H nd

RegEx: H\Dnd
Passende Zeichenkette: Hund
Nicht passende Zeichenkette: H1nd

Wiederholungen


Häufig ist es so, dass gleich mehrere Zeichen in Folge der gleichen Zeichenklasse entsprechen müssen. Dazu muss diese natürlich nicht entsprechend oft wiederholt werden – ein einfacher Zusatz genannt Quantifier erledigt dies von alleine: Zeichenklasse{n} verlangt eine n-fache Wiederholung von Zeichenklasse.

RegEx: [0-9]{4}
Passende Zeichenketten: 0000 bis 9999
Nicht passende Zeichenkette: 42

RegEx: [1-9][0-9]{4}
Passende Zeichenketten: 10000 bis 99999

Doch oft kann man nicht genau sagen, wie oft eine Zeichenklasse wiederholt werden muss, beispielsweise bei einer ID. Auch hier gibt es Lösungen:

  • ? – Nicht oder einmal, z.B. http[s]?: um http: und https: zu matchen
  • + – Mindestens einmal, z.B. [0-9]+ für beliebig viele Ziffern
  • * – Nicht bis beliebig oft, z.B. [0-9]* für keine Angabe oder beliebig viele Ziffern
  • {x,} – Mindestens x-mal, z.B. [0-9]{2,} für mindestens zwei Ziffern
  • {,y} – Maximal y-mal, z.B. [0-9]{,2} für maximal zwei Ziffern
  • {x,y} – Mindestens x-mal und maximal y-mal, z.B. [0-9]{2,4} für zwei, drei oder vier Ziffern

Anfang und Ende


Nicht immer ist es erwünscht, dass der gesuchte Ausdruck innerhalb der Zeichenkette gefunden wird. Deshalb lässt sich mit ^ der Anfang der Zeichenkette (oder Zeile) und mit $ das Ende der Zeichenkette (oder Zeile) markieren. Möchte man nun eine URL validieren, kann der Anfang so aussehen:

RegEx: ^http[s]?://
Passende Zeichenkette: http://…
Passende Zeichenkette: https://…
Nicht passende Zeichenkette: ftp://…
Nicht passende Zeichenkette: Testhttp://…

Soll diese URL nun auf jeden Fall auf .htm enden, bietet sich diese RegEx an:

RegEx: \.htm$
Passende Zeichenkette: http://…/test.htm
Nicht passende Zeichenkette: http://…/test.php
Nicht passende Zeichenkette: http://…/test.html

Zum Vergleich ohne $:

RegEx: \.htm
Zeichenkette: http://…/test.html
Gefunden: http://…/test.html

Gruppierungen


Um noch komplexere Ausdrücke zu erzeugen, kann man innerhalb dieser Gruppierungen mit runden Klammern vornehmen. Diese können dann auch wieder einem Quantifier versehen werden:

RegEx: Hallo (Welt)
Passende Zeichenkette: Hallo Welt

RegEx: Hallo( Welt)+
Passende Zeichenkette: Hallo Welt
Passende Zeichenkette: Hallo Welt Welt

RegEx: Hallo( Welt)?
Passende Zeichenkette: Hallo
Passende Zeichenkette: Hallo Welt

Alternativen oder „oder“

Abschließend sei hier noch die Pipe | erwähnt, die wie ein „oder“ funktioniert. Schauen wir wieder auf unsere URL – diesmal sollen nur URLs betrachtet werden, die auf .php4 oder .php5 enden:

RegEx: \.php4|5$
Passende Zeichenkette: .php4
Passende Zeichenkette: .php5
Nicht passende Zeichenkette: .php6
Nicht passende Zeichenkette: .php

Um nun auch die allgemeine Endung .php zu erlauben, wird der Ausdruck ergänzt:

RegEx: \.php(4|5)?$
Passende Zeichenkette: .php4
Passende Zeichenkette: .php5
Passende Zeichenkette: .php
Nicht passende Zeichenkette: .php6

Dank Gruppierungen und „oder“ können nun auch Schoß- und Wachhunde eindeutig in einer einzigen Suchabfrage gefunden werden:

RegEx: (Wach|Schoß)hund
Passende Zeichenkette: Wachhund
Passende Zeichenkette: Schoßhund
Nicht passende Zeichenkette: Schund
Nicht passende Zeichenkette: Kampfhund

Das dicke Ende

Für den Einstieg sollte dies zunächst reichen. Wenn dieser Artikel sein Ziel nicht verfehlt hat, sollte sich nun folgendes Fazit ziehen lassen:

Mächtig? Ja! Kompliziert? Nein!

Leider muss aber betont werden, dass hier längst nicht das gesamte Thema Regular Expressions abgedeckt wird. Sie sind also viel mächtiger, aber auch entsprechend komplexer. Immerhin sollte „komplex“ nach diesen ersten Schritten nicht mehr gleichzeitig „kompliziert“ bedeuten.

Zur weiteren Vertiefung der Thematik sei an dieser Stelle die (englische) Homepage von Jan Goyvaerts empfohlen: http://www.regular-expressions.info/. Dort finden sich ein Quick Start Tutorial, das bereits mehr abdeckt als dieser Artikel, ein komplettes Regular Expression Tutorial, einige Tools und viele Beispiele.

Und nun: Viel Spaß mit regulären Ausdrücken!