Tutorial - Reguläre Ausdrücke

Dies hier ist eine kleine Anleitung, um das scheinbare Geheimnis von StringRegExp() zu enträtseln.

StringRegExp( "test", "pattern" [, flag ] )

"test" = Die Zeichenkette, die nach Treffern durchsucht werden soll.
"pattern" = Eine Zeichenkette, bestehend aus bestimmten Schlüsselzeichen, der die Funktion genau wissen lässt was gesucht wird. Kein wenn und aber, es gibt einen Treffer oder es gibt keinen.
flag[optional] = Sagt der Funktion ob nur nach dem pattern gesucht werden soll, oder ob nur der erste Treffer aus der test-Zeichenkette zurückgegeben wird oder alle.

Die wirklichen Grundkenntnisse

Wie sie vielleicht schon herausgefunden haben, ist nur das Suchmuster (pattern) der einzigst komplizierte Teil von StringRegExp() (im Folgenden nur noch SRE genannt). Ich finde es ist am einfachsten sich ein Muster vorzustellen, das der Funktion sagt eine Zeichenkette Zeichen für Zeichen mit diesem Muster abzugleichen. Es gibt verschiedene Wege ein bestimmtes Zeichen zu finden: Wenn beispielsweise die Zeichenfolge "test" gesucht wird, dann sollte es recht einfach sein diese zu finden. Es soll der Funktion SRE mitgeteilt werden als erstes nach einem"t" in der Zeichenkette zu suchen. Wird eines von der Funktion gefunden, nimmt sie an eine Übereinstimmung gefunden zu haben und der Rest des Suchmusters wird dazu genutzt, zu prüfen ob das was gefunden wurde keine Übereinstimmung ergibt. Also, wenn das nächste Zeichen ein "e" ist, könnte es immer noch eine Übereinstimmung sein. Sagen wir das nächste Zeichen ist ein "x". Dann weiß SRE unverzüglich, dass es keine Übereinstimmung ergibt, weil wir im Suchmuster festgelegt haben, dass das dritte Zeichen ein "s" ist.

Beispiel 1

MsgBox(0, "SRE Beispiel 1 - Ergebnis", StringRegExp("text", 'test'))

In diesem Beispiel, sollte die MessageBox "0" ausgeben, was bedeutet, das Suchmuster "test" wurde in der zu testenden Zeichenkette "text" nicht gefunden. Ich weiß, dies erscheint kinderleicht, aber jetzt wissen wir wenigstens auch warum "test" nicht gefunden wurde.

Um als nächstes ein Suchmuster festzulegen, nutzen wir eine Zeichenklasse ("[ ... ]"). Die Abgleichung der Zeichenmuster in Zeichenklassen können wir uns wie das logische "OR" vorstellen. Nehmen wir wieder das vorherige Beispiel. Wir wollen entweder die Zeichenkette "test" oder "text" finden. Nun, die Art wie ich beginne nach einem Suchmuster Ausschau zu halten, ist so zu denken wie SRE es auch tun würde: Das erste abzugleichende Zeichen ist "t", dann der Buchstabe "e", dies gilt bis jetzt für beide Suchmuster. Jetzt möchten wir ein "s" OR "x", demnach können wir eine Zeichenklasse als Ersatz nutzen für: "[sx]" bedeutet, suche nach einer Übereinstimmung mit einem "s" oder einem "x". Der letzte Buchstabe ist nun wieder ein "t", was auch wieder für beide Suchmuster gilt.

Beispiel 2

MsgBox(0, "SRE Beispiel 2 - Ergebnis", StringRegExp("text", 'te[sx]t'))
MsgBox(0, "SRE Beispiel 2 - Ergebnis", StringRegExp("test", 'te[sx]t'))

Beide Aufrufe von SRE sollten zu dem Ergebnis "1" führen, denn das Suchmuster sollte eine Übereinstimmung in beiden Fällen "test" und "text" ergeben.

Es kann außerdem festgelegt werden, wie oft jedes Zeichen übereinstimmen soll, indem man "{Anzahl der Übereinstimmungen}" dem Zeichen anfügt oder man legt einen Bereich "{min, max}" fest. Das erste Beispiel weiter unten ist eigentlich überflüssig, aber zeigt was ich meine:

Beispiel 3

MsgBox(0, "SRE Beispiel 3 - Ergebnis", StringRegExp("text", 't{1}e{1}[sx]{1}t{1}'))
MsgBox(0, "SRE Beispiel 3 - Ergebnis", StringRegExp("aaaabbbbcccc", 'b{4}'))



Die etwas fortgeschrittenen Grundlagen

In diesem Moment wird man möglicherweise denken "Ist dies nicht bloß eine bessere StringInStr() Funktion?". Nun, nutzt man nur den flag Wert 0, liegt man mit diesem Gedanken meist richtig. Aber SRE ist wesentlich mächtiger als StringInStr(). Wenn man SRE's immer öfter und mehr benutzt, mag man denken sehr wenig darüber zu wissen und noch weniger darüber die Art des Suchmusters festzulegen. Doch es gibt Mittel und Wege jedes zu suchende Zeichen mehr oder weniger genau in dem Suchmuster zu definieren. Nehmen wir, zum Beispiel, eine Zeile aus einem Chat-Log eines Spieles: "Knorriges Monster schlägt dich und fügt dir 18 Schadenspunkte zu." Jetzt möchten wir herausfinden wie viele Schadenspunkte Knorriges Monster uns zugefügt hat. Nun gut, wir können StringInStr() nicht dafür benutzen, denn wir suchen nicht nach "18", wir suchen "????", wobei ? irgendeine Zahl sein kann.

Hier zeige ich nun wie ich das Suchmuster zusammensetzen würde:
1) Wir wissen, dass es IMMER nichts anderes als Zahlen enthält.
2) Wir wissen, dass es MANCHMAL 2 Zeichen lang ist.
2a) Wir wissen vom Spielen her, dass die maximalen Schadenspunkte die ein Monster uns zufügen kann 999 ist.
2b) Wir wissen, dass die minimalen Schadenspunkte die ein Monster uns zufügen kann 0 ist.
3) Wir wissen, dass es IMMER zwischen 1 und 3 Zeichen lang ist.
4) Wir wissen, dass es keine anderen Zahlen in der zu testenden Zeichenkette gibt.

An diesem Punkt, wollen wir uns an den flag Wert "1" und Zeichengruppen "()" herantasten. Der Wert "1" des Flags bedeutet, dass SRE nicht bloß auf Übereinstimmung des Suchmusters prüft, sondern auch ein Array zurückgibt, in dem jedes Element eine übereinstimmende "Gruppe" von Zeichen enthält. Um nicht zu weit vom Kurs abzukommen, nehmen wir dieses Beispiel:

Beispiel 4

$asResult = StringRegExp("Dies ist ein Test-Beispiel", '(Test)', 1)
If @error == 0 Then
MsgBox(0, "SRE Beispiel 4 - Ergebnis", $asResult[0])
EndIf
$asResult = StringRegExp("Dies ist ein Test-Beispiel", '(Te)(st)', 1)
If @error == 0 Then
MsgBox(0, "SRE Beispiel 4 - Ergebnis", $asResult[0] & "," & $asResult[1])
EndIf

So, als erstes muss das Suchmuster in der zu testenden Zeichenkette eine Übereinstimmung finden. Ist dies der Fall, dann wird SRE mitgeteilt jegliche Gruppen ("()")"einzufangen" und sie in dem zurückzugebenden Array abzulegen. Es können mehrfache Gruppen verwendet werden, wie im zweiten Teil des Beispielcodes.

Ok, zurück zum knorrigen Monster. Jetzt, wo wir wissen wie man Text "einfängt", lasst uns das Suchmuster zusammensetzen: Seitdem wir wissen, dass wir Zahlen suchen, bieten sich uns 3 Wege an für "Finde eine Zahl": "[:digit:]", "[0-9]", und "\d". Das erste ist vermutlich am einfachsten zu verstehen. Es gibt ein paar Klassen (digit, alnum, space, etc. siehe in der Hilfedatei für eine vollständige Liste) die wir nutzen können um Zeichenklassen festzulegen, eine von ihnen ist für Zahlen. "[0-9]" legt lediglich einen Bereich aller Zahlen zwischen 0 und 9 fest. "\d" ist bloß ein Sonderzeichen mit der gleichen Bedeutung wie die ersten beiden Arten. Zwischen den dreien besteht kein Unterschied, und mit allen SRE's gibt es wenigstens ein paar Wege um irgendein Suchmuster zusammenzusetzen.

Nun, das erste was wir wissen ist, dass wir die Zahlen(-gruppe) einfangen wollen, also weisen wir daraufhin indem wir eine Klammer öffnen "(". Als nächstes wissen wir, dass wir zwischen 1 und 3 Zeichen einfangen wollen, alle aus Zahlen bestehend, sodass unser Suchmuster jetzt aussieht wie "([0-9]{1,3}". Und letztendlich schließen wir das ganze mit einer Klammer um auf das Ende der Gruppe hinzuweisen: "([0-9]{1,3})". Lasst es uns ausprobieren:

Beispiel 5

$asResult = StringRegExp("Knorriges Monster schlägt dich und fügt dir 18 Schadenspunkte zu.", _
'([0-9]{1,3})', 1)
If @error == 0 Then
MsgBox(0, "SRE Beispiel 5 - Ergebnis", $asResult[0])
EndIf

Na bitte, die MessageBox zeigt korrekt "18" an.

Als nächstes schauen wir uns die nicht-einfangenden Gruppen an. Die Art wie wir diese Gruppen kenntlich machen ist "(?:" anstatt nur "(". Lasst uns annehmen euer Log sagt "Du konntest 36 von Knorriges Monster 279 Schadenspunkte ausweichen.". Wenn wir nun Beispiel 5 ausführen, erhalten wir "36" anstatt "279". Nun möchte ich an dieser Stelle den Unterschied der beiden Zahlen untersuchen. Das erste was mir auffällt ist, dass der zweiten Zahl immer ein Leerzeichen folgt und dann das Wort "Schadenspunkte". Wir könnten nun unser voriges Suchmuster abändern zu "([0-9]{1,3} Schadenspunkte)", aber was ist wenn unser Skript nur nach der Menge der Schadenspunkte sucht, ohne "Schadenspunkte" an das Ende der Zahl anzuheften? An dieser Stelle können wir nicht-einfangende Gruppen einsetzen um unser Ziel zu erreichen.

Beispiel 6

$asResult = StringRegExp("Du konntest 36 von Knorriges Monster 279 Schadenspunkte ausweichen.", '([0-9]{1,3})(?: Schadenspunkte)', 1)
If @error == 0 Then
MsgBox(0, "SRE Beispiel 6 - Ergebnis", $asResult[0])
EndIf

Dieses Thema könnten wir noch weiter ausdehnen, aber Zielsetzung dieses Tutorials ist die Grundlagen zu vermitteln, wie reguläre Ausdrücke funktionieren und hauptsächlich wie SRE "denkt". Hier noch einige Sachen die man im Hinterkopf behalten sollte:
- Nicht vergessen über das Suchmuster für jedes Zeichen nachzudenken
- Die StringRegExp() Funktion findet das erste Zeichen in dem Suchmuster, danach ist es dein Job genügend Belege zu erbringen um zu "Beweisen" ob oder ob keine Übereinstimmung erfolgt. Beispiel 6 ist ein gutes Beispiel dafür.
- Nicht vergessen, [ ... ] bedeutet OR ([xyz] finde ein "x", ein "y", oder ein "z")
Sollten noch Fragen offen sein, sollte der erste Griff zur Hilfedatei sein! Diese erklärt im Detail die wesentliche Syntax, die mit SRE daherkommt. Eine Sache auf die man ein besonderes Augenmerk legen sollte ist die Sektion über "Wiederkehrende Zeichen". Dies macht das Suchmuster leichter lesbar, wenn man für bestimmte Zeichen Bereiche festlegt. Zum Beispiel: "*" ist gleichbedeutend mit {0,} oder dem Bereich von 0 bis irgendeine Anzahl von Zeichen.

Viel Glück, Reguläre Ausdrücke können großartig dazu genutzt werden um die Länge des Codes zu reduzieren, und machen eine spätere Änderung dessen wesentlich einfacher. Korrekturen und Resonanz sind stets willkommen!

Quellen


Wikipedia Artikel - Regulärer Ausdruck - Vielen Dank an blindwig.

The 30 Minute Regex Tutorial - von Jim Hollenhorst.


GUI um verschiedene Suchmuster mit StringRegExp() zu testen - Vielen Dank an steve8tch. Anerkennung: w0uter

Vielen Dank an neogia für dieses Tutorial.