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.