01.01.01 – Reguläre Ausdrücke und einfache Suchmuster

1. Teil

Dieser Workshop ist für Einsteiger in die Problematik regulärer Ausdrücke gedacht. Er ist eine verkürzte und überarbeitete Version eines Tutorials, das ich auf meiner Website für Benutzer eines Mailprogramms TheBat! anbiete, ohne dass dieser dadurch ersetzt wird. Dieses Tutorial ist wirklich dazu geschrieben, ausschließlich die Syntax zu erlernen, unabhängig davon, wo man später die Regexe verwenden will. Ich habe mich bemüht, die Beispiele und Aufgaben allgemeinen Problemen anzupassen. Allerdings wird die Leserin oder der Leser feststellen, dass eine Vielzahl der Beispiel-Regenechsen aus der Welt der Mails stammen. Ich hoffe dennoch, dass der Workshop als Einstieg zum allgemeinen Verständnis der faszinierenden Welt der „Regenechsen“ geeignet ist.

1.1. Was genau heißt „Regulärer Ausdruck“

Regexe finden sich in vielen UNIX-Tools, in Programmiersprachen wie Perl (Practical Extraction and Report Language), PHP, Javascript oder sogar in verschiedenen Editoren wie UltraEdit oder jEdit. Mein Perl-Buch sagt zu diesem Begriff, dass er auf den ersten Blick unsinnig erscheint (bei mir auch auf dem zweiten), da es sich nicht um richtige Ausdrücke handelt und außerdem kaum zu erklären ist, was an ihnen eigentlich „regulär“ ist. Nehmen wir einfach hin, dass der Begriff „reguläre Ausdrücke“ der formalen Algebra entstammt und in der Tat sind Regexe ein Teil der Mathematik.

Vielleicht ist die die richtige Stelle, darauf hinzuweisen, dass es wie bei jeder Sprache auch bei Regexisch Dialekte gibt. Ich konzentriere mich bei der Darstellung auf den bei PERL verwendeten Dialekt PCRE. Andere Tools wie z.B. bei GNU grep verwenden andere Dialekte. Zwar ist die Grundstruktur gleich, deswegen ist dieses Tutorial dennoch ein Einstieg für alle, aber Kleinigkeiten können halt abweichen. Bitte informiert euch zuvor, ob PCRE verwendet wird. Im Kapitel 5.4 erwähne ich diesen Unterschied explizit für die Optionen und Modifikatoren.

Und wenn wir schon dabei sind: ich beschreibe hier nur das Regex, nicht die Syntax, mit der das Regex in der jeweiligen Sprache eingesetzt wird. Einerseits würde das den Rahmen dieses Tutorials sprengen und andererseits wird die Verwendung in den anderen Sprachen sicherlich besser in den dazugehörigen Tutorials und Handbüchern beschrieben als ich es jemals könnte 🙂

Am einfachsten umschreibt man reguläre Ausdrücke wohl als Suchmuster für „Pattern Matching“ (Suchmusterübereinstimmung). Jeder von uns, der auf DOS-Ebene oder im Explorer mal nach Dateien gesucht hat, hat solche Suchmuster verwendet:

dir *.doc

copy *.??t c:\temp

Hier werden Suchmuster bestehend aus Sternchen und Fragezeichen verwendet, um die Auswahl von Dateien einzugrenzen. Im ersten Beispiel sollten alle Dateien gelistet werden, welche die Endung .doc haben. Im zweiten Beispiel sollten nur Dateien kopiert werden, die eine dreibuchstabige Endung haben und als letzten Buchstaben ein t aufweisen.

Aber diese „Regexe“ sind lediglich reine Platzhalter und trivial. Sie sind in keinster Weise so mächtig wie Regexe, welche – wie wir bald sehen werden- eben nicht nur Platzhalter für Zeichen sind.

2. Teil

Um im folgenden Beispiele für Regexe geben zu können, müssen wir uns auf eine Darstellungsform einigen. Ich werde die Regexe mit Anführungszeichen begrenzen und als Code formatiert, also so. Wollt ihr sie ausprobieren, so müsst ihr die Eintragungen zwischen den „-Zeichen verwenden. Testen? Ja, man kann die Wirksamkeit der Muster in der Hilfe testen: Bitte, ladet euch den Regex-Coach für den Kurs herunter, falls ihr die Regexe testen wollt.

Dazu geht zum Regex-Coach und installiert ihn. Bedienhinweise entnehmt ihr bitte dem Produkt selbst. Für Benutzer der Editoren Weaverslave oder Ultraedit gibt es Plugins bzw. vorhandene Funktionen zur Nutzung der Regexe im Editor. Achtung, zum Teil haben diese Programme eine eigene Syntax. Bitte vorher die Hilfe lesen!

2.1. Einfache bekannte Zeichen

Fangen wir mit einfachen Suchmustern an: „dies oder das“

Ja, das ist schon ein Regex: es findet die Zeichenfolge ‚dies oder das‘ in einem Text und zwar genau die. Nein, das ‚oder‘ bewirkt nicht, dass entweder ‚dies‘ oder ‚das‘ gefunden wird, sondern genau nur die Zeichenfolge in den Anführungszeichen.

Regexe sind stur: sie suchen genau das, was man ihnen aufträgt. Sie unterscheiden Groß- und Kleinschreibung und sie interessieren sich nicht für Wortgrenzen, wenn das niemand sagt. Im obigen Beispiel wird die Zeichenfolge in ‚Das Paradies oder das Weib‘ gefunden.

2.2. Suche nach Metazeichen

Mit einem Regex lässt sich nach allen beliebigen Zeichen – alphanumerische, hexadezimale, binäre usw. – suchen. Eine kleine, aber wichtige Ausnahme bilden Zeichen, die das Regex als besondere Zeichen, den Metazeichen, einsetzt. Diese Metazeichen sind:

* + ? . ( ) [ ] { } \ / | ^ $

(Hallo Experten: Ihr habt ja recht. Ich habe da ein bisschen geschummelt. Nicht alle sind tatsächlich Metazeichen. Aber lasst uns mal einfach annehmen, es wäre so. Ich zeige später, warum ich diese Art der Definition von Metazeichen bevorzuge.)

Ihre Bedeutung werden wir im Verlaufe des Workshops noch kennen lernen, dazu also später. Nur soviel vorweg: wer diese Zeichen in ihrem ursprünglichen Sinn suchen will, also literal, der muss dem Regex dies in irgendeiner Form erkennbar machen. Dem Metazeichen muss ein Escape-Zeichen vorweg gestellt werden: es ist der Backslash \

Wird also nach einem Fragezeichen gesucht, so muss das Regex „\?“ lauten. Wird nach dem Schrägstrich gesucht, so muss es „\/“ heißen. Na ja, auch wenn es komisch aussieht, aber wer ein Backslash sucht, muss halt zwei solche eingeben: „\\“

2.3. Einfache unbekannte Zeichen

Das erste Metazeichen ist der Punkt „.“ Er steht für genau ein beliebiges Zeichen, egal was dieses Zeichen darstellt. (Na, schon wieder ein paar Experten, die mehr wissen *g*? Laßt uns zu Ausnahmen später kommen, ok?)

„M.ier“ findet somit ‚Maier‘, ‚Meier‘ und den ‚Maier‘ in ‚Maiering‘ (auf das ‚ing‘ passt der Suchstring nicht mehr), aber nicht ‚Manieren‘. „H..s“ findet sowohl ‚Hans‘ als auch ‚Haus‘; es wird aber nicht ‚Hase‘ finden. Das Wort ‚Hirse‘ wird bis auf das ‚e‘ gefunden; das Suchmuster passt genau auf ‚Hirs‘.

Zu einem späteren Zeitpunkt werden wir sehen, dass man weitere Metazeichen verwenden kann, um mehr als nur ein unbekanntes Zeichen suchen zu können, ohne es mehrfach mit einem „.“ zu kennzeichnen.

2.4. Zeichengruppen und -klassen

Ein weiteres mächtiges Werkzeug sind die Metazeichen für Zeichengruppen. Hier unterscheiden wir gleich mehrere Möglichkeiten. Beginnen wir mit den einfachen:

„\d“ steht für eine Ziffer (digit). „\d\d“ sucht also nach zwei aufeinanderfolgenden Ziffern.

„\w“ steht für einen beliebigen Buchstaben, eine beliebige Zahl oder einen Unterstrich (word), auch alphanumerische Zeichen genannt.

Auf diese Weise lassen sich schon komplexere Suchmuster aufbauen.

„Re \[\d\]:“ sucht also einen String nach der Zeichenkette ‚Re‘ gefolgt von einem Leerzeichen, einer öffnenden eckigen Klammer, einer beliebigen Ziffer und einer schließenden eckigen Klammer mit einem Doppelpunkt als Abschluss.

Die Regex bieten auch das jeweilige Gegenstück zu den beiden oben genannten: „\W“ und „\D“ (non-Digit und non-Word)

Hierbei steht \W für jedes beliebige nicht-alphanumerische Zeichen und \D für jedes Zeichen, das keine Ziffer ist.

Eine weitere elegante Methode Zeichengruppen zu definieren ist die Verwendung von [ ] für Zeichenklassen. Mit dieser eckigen Klammer wird nur genau ein Zeichen gesucht, unabhängig, wie viele Zeichen in der Klammer aufgeführt werden: „[AEX]“ Diese Kombination sucht Zeichenketten, die aus nur genau einem Zeichen bestehen, welches auch noch nur A, E oder X heißen muss.

Will man ganze Bereiche angeben, so muss man nicht alle Elemente einzeln aufführen, vielmehr darf man das erste und das letzte Element mit einem Bindestrich verbinden: „[e-z]“ heißt alle Buchstaben von e beginnend bis z sollen gefunden werden.

Eine sehr mächtige Methode: „[0-3][0-9]\.[0-1][0-9]\.“ Hiermit werden nur Datumsangaben im Format TT.MM. gefunden. Andere Zahlenkombinationen, die kein Datum sein können, wie 47.35., werden nicht gefunden (Ja, aufmerksame Leser haben festgestellt, dass mein Regex oben immerhin auch den 39.19. findet, was definitiv kein irdisches Datum ist. Dazu kommen wir nachher, es fehlt uns noch etwas…).

Sehr praktisch ist hieran auch, dass man mit einem Schlag das Suchkriterium negieren kann, also nach dem Motto: „Finde alle Zeichen, sofern sie keine 1, 2, 3 oder 4 sind!“ Das Regex lautet: „[^1-4]“ Die Negation wird mit einem ^ bewirkt. Hoppla, das sollten wir uns merken, denn wir werden nachher sehen, dass dieses ^ eine ganz andere Bedeutung hat, wenn es nicht in eckigen Klammern steht.

2.5. Überblick über dieses Kapitel

In diesem Kapitel haben wir einfache Suchmuster kennen gelernt:

  • direkt eingegebene Zeichenketten werden als solche gesucht. „er“ sucht nach den aufeinander folgenden Buchstaben e und r . Groß- und Kleinschreibung wird unterschieden
  • Regexe verwenden Metazeichen, nach denen man nur dann literal suchen kann, wenn man ein Backslash voranstellt: * + ? . ( ) [ ] { } \ / | ^ $
  • der Punkt „.“ dient dazu, nach einem beliebigen unbekannten Zeichen zu suchen. Sucht man nach dem Punkt als Zeichen, so stellt man ein Backslash voran „\.“
  • Regexe verwenden Zeichengruppen wie
    • \d für Ziffern ([0-9])
    • \D für nicht-Ziffern ([^0-9])
    • \w für alphanumerische Zeichen ([a-zA-Z0-9_])
    • \W für nicht-alphanumerische Zeichen ([^a-zA-Z0-9_])
  • Zeichenklassen können durch Angabe in eckigen Klammern selbst definiert werden „[A-Z]“ Diese Angabe kann durch ein ^ als erstes Zeichen in den eckigen Klammern negiert werden.

Aufgaben

Was sucht die folgenden Regexe

"\d\d\.\d\d\.\d\d\d\d"
"\w\w\w, \d\d \w\w\w \d\d\d\d"
".. \[[0-9]\]:"
"[a-zA-Z]"

Lösung für den ersten Fall: zwei Ziffern. Es folgt der Backslash und dann erst der Punkt, das bedeutet, es soll der Punkt literal, also als Punkt und nicht als beliebiges Zeichen gesucht werden. Erneut sollen zwei Ziffern mit einem Punkt folgen und abschließend nochmals eine vierstellige Zahl. Ein Datum in der Form TT.MM.JJJJ

Der zweite Fall sucht nach drei alphanumerischen Zeichen mit einem Komma, ein Leerzeichen, zwei Ziffern, wieder drei alphanumerischen Zeichen mit ein Leerzeichen und abschließend eine vierstellige Zahl. Auch das sieht nach einem Datum aus, aber in der anglo-amerikanischen Schreibweise: Tue, 19 Feb 2002. Leider ist dieses Regex nicht optimal: es erkennt nur Daten mit zweistelligen Tageszahlen. Wir werden etwas später sehen, wie man das Regex modifiziert, um sowohl einstellige als auch zweistellige Tageszahlen zu finden.

Der dritte Fall sucht nach zwei x-beliebigen Zeichen auf die ein Leerzeichen und eine geöffnete eckige Klammer folgt. Als nächstes wird die eckige Klammer nicht mehr von einem Backslash begleitet, hier beginnt also eine Zeichengruppe. In dieser Zeichengruppe sind alle Ziffern von 0 bis 9 erlaubt. Nach der Ziffer soll eine geschlossene eckige Klammer und ein Doppelpunkt folgen. Also zum Beispiel: ‚Re [2]:‘

Im letzten Fall soll das Regex nur ein einziges Zeichen finden, das nur aus Klein- oder Großbuchstaben bestehen darf. Warum an dieser Stelle eigentlich kein „\w“? Nun, das würde auch Unterstriche beinhalten, was ggf. ungewünscht ist.

weiter