Hallo und herzlich Willkommen zu meinem aller ersten Tutorial. Ich möchte euch in diesem Tutorial die Regulären Ausdrücke näher bringen. Aber nicht nur das: Ich möchte euch genauso den Spaß am "RegExen" vermitteln, und hoffe natürlich alles genauestens erklären zu können. Solltet ihr etwas nicht verstehen, oder noch offene Fragen haben, dann möchte ich euch bitten, natürlich jederzeit im Thread zu posten, um mich auf evtl. Schwachstellen des Tutorials aufmerksam zu machen oder um selbstverständlich Antworten auf offene Fragen zu bekommen.
Mein Dank gilt an dieser Stelle einerseits den Machern der Webseite www.regenechsen.de, deren Workshop mich seinerzeit die Regulären Ausdrücke ( nachfolgend RegEx oder RegExp genannt ) gelehrt hat, aber auch der Firma JGSoft, ohne deren RegExBuddy(Ein Leistungsstarkes RegEx-Tool mit Syntaxhighlighting uvm.) ich wohl oft verzweifelt wäre. Genug der vielen Worte. Ich hoffe ihr seid bereit, denn wir heben in Kürze ab
In der Informatik ist ein regulärer Ausdruck (engl. regular expression, Abk. RegExp oder Regex) eine Zeichenkette, die der Beschreibung von Mengen beziehungsweise Untermengen von Zeichenketten mit Hilfe bestimmter syntaktischer Regeln dient.
Das sagt zumindest Wikipedia. Ich werde mein bestes geben, dieses Kauderwelsch für euch zu entschlüsseln. Zuerst solltet ihr wissen: Ihr habt wahrscheinlich alle schonmal einen RegExp benutzt. Zum Beispiel bei der Suche nach mp3-Dateien auf euren Computern: Man öffnet die Windows-Suche und sucht nach
*.mp3
Gefunden werden alle Dateien, deren Name auf ".mp3" endet. Das Sternchen steht für eine beliebige Anzahl unbekannter Zeichen.
Möchten wir alle Dateien finden, deren Namen ein "S" beinhaltet, worauf 2 Zeichen später ein "G" folgt und auf .txt endet, schreiben wir
*s??g.txt
Hier werden also ? und * als Platzhalter verwendet. Diese "RegExe" sind aber eher infantil; und keinesfalls so mächtig wie ein richtiger Regulärer Ausdruck - Denn in eben jenem sind die Zeichen nicht nur Platzhalter.
Ein RegExp ist also ein Suchmuster. Zum Beispiel ist es leicht, alle Worte in einem Text zu finden, die auf E beginnen, und mit n enden. Zu meinem Lieblingsbeispiel komme ich gleich.
Mit _Stringbetween müsste man jetzt nach allem zwischen "> und </td> suchen. Ich kann mir aber vorstellen, dass es da massig Ergebnisse geben wird.
Das heißt wir müssten uns erst mit Stringbefehlen ein Stück aus dem Quellcode zurechtschneiden, hier mal trimmen, da mal etwas ersetzen, nur um dann am Ende den Lagerstand herauszufinden. Das sind wieder 10 Zeilen unnötiger Code. Per RegExp macht man einfach folgendes:
Um das Tutorial effektiv zu gestalten, möchte ich mich mit euch auf eine Darstellungsform einigen. Ich werde Die RegExe IMMER in [ Code ]-Tags hüllen, damit man Sie besser erkennt.
Ich möchte euch bitten, den RegExBuddy zu installieren, damit ihr die RegExe testen, und Aufgaben im Tutorial leichter verwirklichen könnt. Die Demo Version könnt ihr hier herunterladen.
Der RegExpbuddy ist für den RegExer, wie der Pinsel für den Maler. Wie der Taktstock für einen Dirigenten, oder der Kugelschreiber für den Rapper - Ein Werkzeug um Kunst zu erstellen! (Na gut, beim Rapper müsst ihr mir nicht zwingend zustimmen ). Der RegExbuddy hat vieles was nützlich ist, und noch viel mehr, was man schon garnicht mehr braucht. Hauptsächlich interessant sind allerdings die gute Verlässlichkeit, sowie das Syntaxhighlighting. Mal ganz zu schweigen von der Fehlererkennung (zb bei falscher Klammersetzung). Was er alles kann werdet ihr selbst herausfinden müssen. Ich kann euch jedoch nur raten, euch das Ding anzuschaffen.
Hier habe ich mal die wichtigsten Bedienelemente im RegExbuddy hervorgehoben:
(sorry, man kann scheinbar keine bilder in spoiler-tags einfügen)
ACHTUNG: Schaltet die RegExp Engine (Im Bild auf "Perl") auf "PCRE". Das ist die RegExp Engine, die in AutoIt verwendet wird. (Weiß ich auch erst seit ca. 1 Stunde.)
Ja, das ist schon ein Regulärer Ausdruck! Gesucht wird nun nach der Zeichenkette "grund oder folge". Und die RegEx-Maschine ist stur und erbarmungslos! Sie sucht genau das was man ihr aufträgt; Gefunden wird nicht nur bei
Ganz wichtig (und Grund dafür, dass RegExe bei mir früher nicht immer Funktioniert haben), ist, dass AutoIt-RegExp immer Case-sensitive arbeiten. Soll heißen: Groß- und Kleinschreibung werden unterschieden. Um dieses Verhalten zu unterdrücken, gibt es verschiedene Wege. Ich werde aber, da wir gerade erst mit dem Tutorial begonnen haben, nur eine erläutern:
Ihr solltet deswegen im RegExpBuddy die Buttons "Case insensitive" zu deaktiveren, und "^$ match at linebreaks" zu aktivieren. Zu letzterem komm ich später.
Mit einem Regex lässt sich alles suchen: Alphanumerische (a-Z, 0-9), Hexadezimale, Binäre Zeichen uvm.
Eine kleine Ausnahme bilden allerdings die Metazeichen einer RegEx-Maschine. Diesen sind nämlich besondere Aufgaben innerhalb eines RegEx zugewiesen.
Zitat
* + ? . ( ) [ ] { } \ / | ^ $
Metazeichen haben bestimmte Funktionen innerhalb eines RegExp. Als Beispiel wären da ^ und $, welche Zeilenanfang und -ende repräsentieren. Oder *,+,? die als Wiederholungszeichen dienen. (Dazu kommen wir in Kapitel 5)
Einige Experten werden nun aufschreien oder vom Stuhl kippen. Ja, nicht alle dieser Zeichen sind tatsächlich Metazeichen. (Zum Beispiel sind die geschweiften Klammern 'eigentlich' keine Metazeichen). Aber nehmen wir mal an, es wäre so.
Ich habe all diese Zeichen hier aufgelistet, weil man sie "escapen" muss um sie zu finden. Möchte man also nach einem Fragezeichen suchen, so schreibt man \?.
Sucht man nach einer schließenden geschweiften Klammer, so schreibt man \}. Sucht man nach einem Backslash, so schreibt man \\.
Alles was zwischen \Q und \E steht, wird auch genauso gesucht. Die "Funktionen" der Metazeichen werden also nicht aktiv.
Allerdings ist das normale escapen i.d.R. einfacher, schneller und überischtlicher.
Achtung: Wenn ihr mal nicht wisst, ob ihr etwas escapen sollt oder nicht, dann escaped lieber einmal mehr als zu wenig.
Ein
Unser erstes Metazeichen, welches wir uns anschauen ist der Punkt "."
Ein Punkt steht für ein beliebiges, unbekanntes Zeichen. Ein Punkt kann für jedes Zeichen stehen. Standartmäßig aber nicht für Zeilenumbrüche. Das kann allerdings auch mit einem speziellen RegExp-Flag (wie schon bei der Groß- Kleinschreibung) geändert werden.
Langsam geht es los! In diesem Abschnitt lernt ihr etwas über Zeichenklassen. Zeichenklassen sind eine "Bündelung von Zeichen", besser erklären kann ich es in einem Beispiel.
Dieser RegExp sucht nach 2 Alphanumerischen Zeichen gefolgt von einem Komma, einem Leerzeichen, zwei Ziffern und einem Punkt, gefolgt von einem Leerzeichen und 3 alphanumerischen Zeichen.
Gefunden wird zB:
"Mo, 12. Jan", "Sa, 03. Mai" und "Fr, 13. Dezember" aber nicht "Do, 4 Apr" oder "Mi 25. Jul"
Eine sehr elegante Methode um eigene Zeichenklassen zu erstellen, sind eckige Klammern "[]".
Mit diesen eckigen klammern wird EIN ZEICHEN gesucht, egal wie viele in den klammern stehen.
sucht nach einer Ziffer, gefolgt von einem Großbuchstaben(!) von A-G.
Gefunden wird also "9B", "7D", "3F" aber nicht "6X" oder "5yA".
Anstatt jedes mal [ABCDEFG] schreiben zu müssen, eröffnet uns RegExp die Möglichkeit einfach
[A-G] zu schreiben. Das ist kürzer und sieht gleich noch viel cooler aus!
Begeben wir uns mal an einen etwas schwereren RegExp:
findet alle Datumsangaben im Format "TT.MM." Komplett falsche Daten wie zb "66.29." werden hier bereits ausgeschlossen.
Der ein oder andere hat vielleicht schon gemerkt: Ein Datum wie zb "39.19." wird trotzdem gefunden. Verbessern wir den RegExp später, wenn wir etwas mehr Erfahrung im Bereich der logischen Operatoren gesammelt haben, okey?
Die so erstellten Zeichenklassen lassen sich ohne Probleme umdrehen:
sucht nach allen Zeichen, die keine Großbuchstaben von A-G sind.
Achtung: Das Zirkumflex "^" hat eine vollkommen andere Bedeutung, wenn es außerhalb der eckigen Klammern steht. Das solltet ihr euch merken. Aber dazu später mehr.
Wir haben einfache RegExe und Metazeichen kennengelernt.
Eine im RegExp vorkommende Zeichenkette wird auch als solche gesucht! "scite" sucht nach dem Wort "scite". Groß und Kleinschreibung wird unterschieden.
Die RegEx Maschine in AutoIt arbeitet Case Sensitive. Soll Groß- und Kleinschreibung ignoriert werden, so nutzt man den Flag (?i).
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 "\."
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 umgekehrt werden.
Diesen RegExp hatten wir so ähnlich schon bei Abschnitt 3.5. Gesucht werden 3 alphanumerische Zeichen, gefolgt von einem Komma und einem Leerzeichen, gefolgt von 2 Ziffern, einem Leerzeichen und 3 weiteren alphanumerischen Zeichen. Darauf folgen noch ein Leerzeichen und 4 Ziffern. 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.
Gesucht wird das Wort "Re" gefolgt von einem Leerzeichen, einer öffnenden eckigen Klammer, dann eine Ziffer von 0-9 und abschließend eine schließende eckige Klammer.
Das war ja kinderleicht oder? Genau! Aber leider waren viele RegExe die ich eben gezeigt habe sehr unflexibel. Wäre das schon die volle Macht der RegExp gewesen, so hätte es sich nicht gelohnt ein Tutorial dafür zu schreiben. RegExe können noch mehr. Noch viel viel mehr! Und ich zeige euch jetzt auch WAS sie alles können!
Dieses "Pattern", so wie der RegExp-String in Autoit genannt wird, findet nur noch "Grund oder Folge der Armut", aber nicht mehr "Fahre ich in den Abgrund oder folge ich der Straße?"
Da ich noch garnicht erwähnt habe, wie RegExp in AutoIt angewand wird, werde ich das nun nachholen. Das eben gezeigte Beispiel wird nun mal in AutoIt eingebaut.
AutoIt-Quelltext
#include <Array.au3>
$sString = _
"Grund oder Folge der Armut?" & @CRLF & _
"Fahre ich in den Abgrund oder folge ich der Straße?"
$aResult = StringRegExp($sString,"(?i)\bgrund oder folge\b",3)
_ArrayDisplay($aResult,"StringRegExp Results")
"Fahre ich in den Abgrund oder folge ich der Straße?"
$aResult=StringRegExp($sString,"(?i)\bgrund oder folge\b",3)
_ArrayDisplay($aResult,"StringRegExp Results")
Ihr könnt ja mal testweise die Wortgrenzen \b entfernen
Die Parameter sollten verständlich sein. $sString ist der Ausgangsstring, in dem wir suchen. Danach folgt unser RegExp-Pattern. Der letzte Parameter gibt an, in welcher Form der Array $aResult zurückgegeben werden soll. In 99% der Fällen reicht Flag 3 vollkommen aus. (Ich habe glaub ich selbst noch nie ein anderes Flag benutzt.)
Hier allerdings mal ein kleines Script, nur um die verschiedenen Rückgabeformen zu verdeutlichen:
AutoIt-Quelltext
#include <Array.au3>
$sString = _
'<td><img src="img/x.gif" class="r1" alt="Holz" title="Holz"></td>' & @CRLF & _
'<td id="l4" title="600">132411/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r2" alt="Lehm" title="Lehm"></td>' & @CRLF & _
'<td id="l3" title="600">168007/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r3" alt="Eisen" title="Eisen"></td>' & @CRLF & _
'<td id="l2" title="750">32743/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r4" alt="Getreide" title="Getreide"></td>' & @CRLF & _
'<td id="l1" title="79">78451/240000</td>'
$aFlag1 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 1)
$aFlag2 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 2)
$aFlag3 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 3)
$aFlag4 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 4)
MsgBox(0, "", "Flag 0 gibt 1 (matched) oder 0 (no match) zurück.")
MsgBox(0, "Flag 0", StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)"))
MsgBox(0, "", "Flag 1 zeigt das erste Match (nur Subpatterns)")
_ArrayDisplay($aFlag1, "Flag 1")
MsgBox(0, "", "Flag 2 zeigt das erste Match (Full Match + Subpatterns)")
_ArrayDisplay($aFlag2, "Flag 2")
MsgBox(0, "", "Flag 3 zeigt alle Matches (nur Subpatterns)")
_ArrayDisplay($aFlag3, "Flag 3")
MsgBox(0, "", "Flag 4 gibt einen Array voller Arrays zurück.")
_ArrayDisplay($aFlag4, "Flag 4")
MsgBox(0, "", "Die einzelnen Arrays beinhalten Full Match + Subpatterns." & @CRLF & _
"Leider kann man Arrays in Arrays nicht direkt ansprechen" & @CRLF & _
"(ohne sie zwischenzuspeichern), deswegen benutze ich statt Flag 4 immer Flag 3." & @CRLF & _
"Hier mal alle Arrays in $aFlag4:")
For $i = 0 To UBound($aFlag4) - 1
$aDummy = $aFlag4[$i]
_ArrayDisplay($aDummy, "$aFlag4[" & $i & "]")
Next
Manchmal will man, dass ein bestimmter Text explizit am Zeilenanfang, oder vielleicht sogar am absoluten Stringanfang gefunden wird. Dafür gibt es bestimmte Metazeichen, von denen wir eines schon im letzen Kapitel kennengelernt haben. ^ und $ stehen für Zeilenanfang und -ende.
Quellcode
#include <Array.au3>
$sString = _
'<td><img src="img/x.gif" class="r1" alt="Holz" title="Holz"></td>' & @CRLF & _
'<td id="l4" title="600">132411/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r2" alt="Lehm" title="Lehm"></td>' & @CRLF & _
'<td id="l3" title="600">168007/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r3" alt="Eisen" title="Eisen"></td>' & @CRLF & _
'<td id="l2" title="750">32743/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r4" alt="Getreide" title="Getreide"></td>' & @CRLF & _
'<td id="l1" title="79">78451/240000</td>'
$aFlag1 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 1)
$aFlag2 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 2)
$aFlag3 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 3)
$aFlag4 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 4)
MsgBox(0, "", "Flag 0 gibt 1 (matched) oder 0 (no match) zurück.")
MsgBox(0, "Flag 0", StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)"))
MsgBox(0, "", "Flag 1 zeigt das erste Match (nur Subpatterns)")
_ArrayDisplay($aFlag1, "Flag 1")
MsgBox(0, "", "Flag 2 zeigt das erste Match (Full Match + Subpatterns)")
_ArrayDisplay($aFlag2, "Flag 2")
MsgBox(0, "", "Flag 3 zeigt alle Matches (nur Subpatterns)")
_ArrayDisplay($aFlag3, "Flag 3")
MsgBox(0, "", "Flag 4 gibt einen Array voller Arrays zurück.")
_ArrayDisplay($aFlag4, "Flag 4")
MsgBox(0, "", "Die einzelnen Arrays beinhalten Full Match + Subpatterns." & @CRLF & _
"Leider kann man Arrays in Arrays nicht direkt ansprechen" & @CRLF & _
"(ohne sie zwischenzuspeichern), deswegen benutze ich statt Flag 4 immer Flag 3." & @CRLF & _
"Hier mal alle Arrays in $aFlag4:")
For $i = 0 To UBound($aFlag4) - 1
$aDummy = $aFlag4[$i]
_ArrayDisplay($aDummy, "$aFlag4[" & $i & "]")
Next
findet "Mein liebstes Hobby ist AutoIt.", aber nicht "Sag mir: Was ist besser als AutoIt?"
Das gleiche gibt es statt für Zeilenanfang und -ende auch als Metazeichen für den kompletten String. \A steht für den Stringanfang, \z steht für das Stringende. Eine Besonderheit gibt es hier auch: \Z ist eine Mischung aus \z und $. Denn es steht für Stingende ODER Zeilenende.
Diese Metazeichen helfen uns auch, unseren RegExp zu beschleunigen! Stellen wir uns die Zeichenkette "AutoIt ist sehr praktisch" vor. Wir wenden nun diesen RegExp darauf an:
Quellcode
#include <Array.au3>
$sString = _
'<td><img src="img/x.gif" class="r1" alt="Holz" title="Holz"></td>' & @CRLF & _
'<td id="l4" title="600">132411/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r2" alt="Lehm" title="Lehm"></td>' & @CRLF & _
'<td id="l3" title="600">168007/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r3" alt="Eisen" title="Eisen"></td>' & @CRLF & _
'<td id="l2" title="750">32743/240000</td>' & @CRLF & _
'<td><img src="img/x.gif" class="r4" alt="Getreide" title="Getreide"></td>' & @CRLF & _
'<td id="l1" title="79">78451/240000</td>'
$aFlag1 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 1)
$aFlag2 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 2)
$aFlag3 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 3)
$aFlag4 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 4)
MsgBox(0, "", "Flag 0 gibt 1 (matched) oder 0 (no match) zurück.")
MsgBox(0, "Flag 0", StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)"))
MsgBox(0, "", "Flag 1 zeigt das erste Match (nur Subpatterns)")
_ArrayDisplay($aFlag1, "Flag 1")
MsgBox(0, "", "Flag 2 zeigt das erste Match (Full Match + Subpatterns)")
_ArrayDisplay($aFlag2, "Flag 2")
MsgBox(0, "", "Flag 3 zeigt alle Matches (nur Subpatterns)")
_ArrayDisplay($aFlag3, "Flag 3")
MsgBox(0, "", "Flag 4 gibt einen Array voller Arrays zurück.")
_ArrayDisplay($aFlag4, "Flag 4")
MsgBox(0, "", "Die einzelnen Arrays beinhalten Full Match + Subpatterns." & @CRLF & _
"Leider kann man Arrays in Arrays nicht direkt ansprechen" & @CRLF & _
"(ohne sie zwischenzuspeichern), deswegen benutze ich statt Flag 4 immer Flag 3." & @CRLF & _
"Hier mal alle Arrays in $aFlag4:")
For $i = 0 To UBound($aFlag4) - 1
$aDummy = $aFlag4[$i]
_ArrayDisplay($aDummy, "$aFlag4[" & $i & "]")
Next
Die RegExp Maschine überprüft ob das erste Zeichen der Zeilenanfang ist -> Wahr
Nun wird geprüft ob das zweite Zeichen ein R ist -> Falsch.
Die RegExp Maschine bricht ab. Hätten wir ^ nicht benutzt, so würde die RegEx Maschine nun den kompletten String durchgehen, und nach "RegExp" suchen, nur um festzustellen, dass der String nicht vorhanden ist.
Nachdem wir eben Wort-, Zeilen,- und Stringgrenzen kennengelernt haben, kommen wir zu einer anderen sehr häufig genutzten Zeichenklasse. Den Whitespaces. Mit \s wird ein Whitespace gesucht - Also ein Zeichen, welches eine weiße Fläche hinterlässt. Dazu gehören @CRLF, @CR, @LF, @TAB, und das Leerzeichen.
Beispiel
AutoIt-Quelltext
#include <Array.au3>
$sString = "Grund"&@TAB&"oder"&@CR&"Folge"&@CRLF&"der Armut?" & @CRLF & _
"Fahre ich in den Abgrund oder folge ich der Straße?"
$aResult = StringRegExp($sString,"(?i)grund\soder\sfolge",3)
;~ _ArrayDisplay($aResult,"StringRegExp Results") ; Da _ArrayDisplay keine Tabs & co darstellen kann,
; Zeige ich euch die Ergebnisse in einer MsgBox.
MsgBox(0,"",$aResult[0])
MsgBox(0,"",$aResult[1])
Also logische Operatoren bezeichnet man in der Informatik u.a AND und OR.
OR, also einen Oder-Operator gibt es in RegExp auch: Mehrer oder-Blöcke werden durch eine sog. Pipe "|" voneinander getrennt. Das geht sowohl außerhalb einer Klammer als auch darin. Zu den Klammern komme ich im Nächsten Kapitel, wenn wir mit Subpatterns arbeiten. Ich hoffe aber dass das Beispiel auch ohne vorherige Erklärung ersichtlich ist.
Beispiel
Ignoriert das "?:" bitte vorerst.
AutoIt-Quelltext
#include <Array.au3>
$sString = "Mein Name ist Peter"& @CRLF
$sString &= "Mein Name ist Karl"& @CRLF
$sString &= "Mein Name ist Christian"& @CRLF
$sString &= "Mein Name ist Max"& @CRLF
$sString &= "Mein Name ist Paul"& @CRLF
$sString &= "Mein Name ist Manfred"& @CRLF
$aResult = StringRegExp($sString,"Mein Name ist (?:Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
Schreibe einen Pattern, der nach einer Seriennummer sucht. 4 Blocks a 4 Zeichen von A-Z und Zahlen von 0-6, mit Bindestrichen getrennt. Teststring Aufgabe 2 KKJD-4WR6-1562-Z31A
22JE-2XAB-RRY4-U24U 22JE-2XAB-R9Y4-U24U 5PLM-4GHS-2TZ6-1132 99A5-7777-ABCD-9786
(die Roten sind falsche Seriennummern) Lösung Aufgabe 2
Lösung Aufgabe 3Das erste Muster sucht nach allen Texten, die einen Zeilenanfang haben oder am Zeilenanfang beginnen. Es werden also alle Strings, sogar der leere String gefunden!
Das zweite Muster sucht nach Zeilen, in denen nur der Buchstabe x steht, direkt nach Zeilenbeginn und am Ende der Zeile.
Und das letzte Muster sucht einfach nach allen Texten, die Zeilenanfang und -ende haben, aber nichts dazwischen. Es werden leere Zeilen gesucht.[/spoiler]
5. Quantifizer, Fangende- und nicht Fangende Gruppierungen
In diesem Kapitel fängt der wirklich harte Stoff an. Am besten ihr lest es langsam und sorgfältig durch, und testet so viele der Beispiele im RegexBuddy.
Sobald wir mit dem Kapitel fertig sind, macht ihr es am besten noch einmal.
Das ist der Part, an dem RegExe interessant werden, da man nun endlich "richtige" Patterns bauen kann. Ein Quantifizer ist ein Wiederholungszeichen. Es gibt an, wie oft etwas wiederholt werden soll, damit der RegExp erfolgreich ist. Dabei ist es egal, ob der Quantifizer auf Zeichenklassen, einzeilne Buchstaben, Subpatterns (dazu kommen wir im nächsten Abschnitt) oder sonstwas angewandt wird.
Es gibt 3 wichtige Quantifizer: ? + *
Das Malzeichen "*" gibt an, dass etwas beliebig oft, aber auch keinmal gefunden werden soll. Dieser Quantifizer ist gierig.
Moment..., Habe ich da gerade "gierig" gesagt? Ja! Man unterscheidet nämlich zwischen gierig (greedy) und faul (lazy) bei Quantifizern. Aber dazu gleich mehr.
Findet "Hamer", Haer" aber nicht mehr "Hammer", und schon garnicht "Hammmmmer"
Wie schon bei den Zeichenklassen, sind wir aber nicht darauf beschränkt was RegExp uns von Haus aus bietet. WIr können eigene Quantifizer erstellen.
Diese sehen zB. so aus: {1,2}
Rate doch mal was dieser Quantifizer macht!
Lösung
Genau! Er gibt an, dass die Zeichenklasse, das Subpattern oder sonst was Mindestens 1 mal, höchstens aber 2 mal gefunden werden soll.
Man kann die zweite Zahl auch weglassen. (Das Komma muss aber bleiben) Das bedeutet dann so viel wie "ohne Grenze nach oben". {3,} sucht also mindestens 3 vorkommen, findet es mehr ist das auch i.O. Die oben genannten Quantifizer + * ? sind nur Kurzformen für diese Art der Quantifizierung.
+ = {1,}
* = {0,}
? = {0,1}
Wenn wir das Komma "," auch noch weglassen, dann wird nach einer exakten übereinstimmung gesucht.
Aber... Moment?! Warum wird denn jetzt nichts gefunden?
Habt ihr den Fehler entdeckt? Richtig! Hinter dem letzten Block folgt ja garkein Bindestrich.
Das heißt wir suchen nur nach 3 Blöcken mit bindestrich, gefolgt von einem ohne Bindestrich.
Ihr habt es jetzt schon in ein paar Patterns gesehen und von der Mathematik sollte man es auch bereits kennen: Mit Klammern kann man Gruppieren. Das dient nicht nur dem Zweck, dass der Term, oder hier beim Regexen das Pattern hübsch aussieht, sondern man kann explizit angeben, welcher Teil eines Terms (Patterns) zueinander gehört. Das ist beim RegExen praktisch, da man auf die Abschnitte in Klammern ua. Quantifizer benutzen kann. Man kann den OR-Operator sinnvoll nutzen uvm.
Grundlegend, wird alles was wir einklammern in sog. Subpatterns gespeichert. Subpattern sind temporäre Variablen, die wir für spätere Zwecke noch verwenden können.
Hier mal ein Beispiel:
Wie ihr seht, wird das erste Wort (auf welches " Email: " folgt) in einer temporären Variable gespeichert ( weil eingeklammert )
Diese Variable wird mit \1 abgerufen. Die 1 steht dabei für die Nummer des Subpatterns.
Subpatterns haben außerdem den Vorteil, dass jedes SubPattern in einem eigenen Array Element gespeichert wird. Das hilft uns unter anderem besonders wenn es um Arbeit mit Quelltexten geht.
Hier mal ein Beispiel mit einem etwas fortgeschritteneren RegExp.
$sQuellCode&='<div class="coy">63)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=100593">06 ? Noch n Dorf[DD]</a></td><td class="aligned_coords">'&@CRLF
Jetzt komme ich nochmal auf das Verhalten der Quantifizer zurück. Ich habe ja eben schon angemerkt, dass die Quantifizer "gierig" sind.
Ich nehme jetzt mal das Beispiel aus dem Workshop von Regenechsen.de
Wir wollen ein Regex, das beliebigen Text in Hochkommata findet und als Subpattern ablegt. Wir benutzen den *-Quantifizer, der ja bekanntlich gierig ist.
$aRegExp=StringRegExp("Die Abkürzung 'ISP' heißt 'Internet Service Provider'.",".*'(.*)'.*",3)
_ArrayDisplay($aRegExp)
Und was wurde in unserem Subpattern gespeichert?
'Internet Service Provider'. Dabei steht doch 'ISP' zuerst im Text. Das liegt daran, dass das * so gierig ist, dass es dem hinteren Teil alles wegfrisst. Es lässt nur so viel für die anderen Elemente des RegExp übrig, wie unbedingt nötig.
Das erste Subpattern ist wieder sehr gierig, und nimmt sich 12-34.abc weg. Danach kommt der Punkt "\.".
Zuguterletzt folgt vor dem @ Zeichen noch ein SubPattern, welches das def aufnimmt.
Vorsicht: Da das 2te Subpattern noch einmal Quantifiziert wird, wird das "def" mit dem neuen Fund überschrieben. Da dieses mal aber Nichts "" übrig bleibt (Denn * ist auch mit Nichts zufrieden), wird das 2te Subpattern durch Nichts "" überschrieben.
Wir wollen also dem RegEx irgendwie klarmachen, dass das "*" nicht so gierig sein soll, sondern eher faul. Und das geht ganz leicht mit einem Fragezeichen "?" nach dem Quantifizer.
+? = Mindestens ein Treffer, ohne Grenze nach oben. Dieser Quantifizer ist nun faul, und wird versuchen so viel wie möglich für die anderen Elemente übrig zu lassen
*? = Mindestens null Treffer, ohne Grenze nach oben. Auch dieser Quantifizer ist nun faul.
?? = Ein Treffer oder überhaupt keiner. Wenn es möglich ist, findet dieser Quantifizer nichts. Wenn das nicht klappt "muss" er versuchen etwas zu finden. Ein ?? wird sehr selten bis nie benutzt. Ich wollte es der vollständigkeit halber aber mal auflisten.
Nachdem wir unsere faulen Quantifizer kennengelernt haben, nehmen wir uns das Beispiel von gerade nochmal zur Hand. Diesmal mit einem lazy "*" im ersten Subpattern.
Eben haben wir ja schon Subpatterns kennengelernt. Manchmal möchte man allerdings RegEx-Elemente gruppieren, ohne dass sie in einem SubPattern gespeichert werden. Dazu verwendet man "?:" am Anfang einer Klammer. Ich nehme noch einmal das Beispiel zur Hand, welches wir bei dem OR-Operator hatten.
Beispiel zu "?:"
Wir haben unser Pattern, bei dem wir nur Sätze finden wollen, in an deren Ende Karl, Max oder Paul steht.
Testen wir dieses Pattern, wird uns immer nur der Name angezeigt, (Da er in ein SubPattern geschrieben wird)
AutoIt-Quelltext
$sString = "Mein Name ist Peter"& @CRLF
$sString &= "Mein Name ist Karl"& @CRLF
$sString &= "Mein Name ist Christian"& @CRLF
$sString &= "Mein Name ist Max"& @CRLF
$sString &= "Mein Name ist Paul"& @CRLF
$sString &= "Mein Name ist Manfred"& @CRLF
$aResult = StringRegExp($sString,"Mein Name ist (Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
$aResult=StringRegExp($sString,"Mein Name ist (Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
Fügen wir jedoch das "?:"-Flag am Anfang der Klammer ein,
Quellcode
$sString = "Mein Name ist Peter"& @CRLF
$sString &= "Mein Name ist Karl"& @CRLF
$sString &= "Mein Name ist Christian"& @CRLF
$sString &= "Mein Name ist Max"& @CRLF
$sString &= "Mein Name ist Paul"& @CRLF
$sString &= "Mein Name ist Manfred"& @CRLF
$aResult = StringRegExp($sString,"Mein Name ist (Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
$aResult=StringRegExp($sString,"Mein Name ist (Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
so wird kein Subpattern mehr erstellt. Wir erhalten den vollen Satz als Ergebnis:
AutoIt-Quelltext
$sString = "Mein Name ist Peter"& @CRLF
$sString &= "Mein Name ist Karl"& @CRLF
$sString &= "Mein Name ist Christian"& @CRLF
$sString &= "Mein Name ist Max"& @CRLF
$sString &= "Mein Name ist Paul"& @CRLF
$sString &= "Mein Name ist Manfred"& @CRLF
$aResult = StringRegExp($sString,"Mein Name ist (?:Karl|Max|Paul)",3)
_ArrayDisplay($aResult)
Wir haben Quantifizer und Subpatterns kennengelernt.
Es gibt 3 Standard-Quantifizer "+", "*", "?". Diese sind gierig. Gieriege (greedy) Quantifizer können mit dem ?-Flag lazy, also faul gemacht werden. "*?", "+?", "??".
Alles was eingeklammert wird, wird in SubPatterns geschrieben. diese kann man mit \1,\2...,\n abrufen.
Möchte man RegExp-Elemente gruppieren, aber nicht in ein neues Subpattern schreiben, so verwendet man "?:" hinter der öffnenden Klammer.
Schreibe ein Pattern, welches alle Funktionen einer .au3-Datei auflistet, und die Parameter für den Funktionsaufruf in ein Subpattern speichert.
Nutze dieses Script als vorlage:
Achtung: Man kann es immer unterschiedlich lösen. Ihr habt ein anderes Pattern heraus? Nicht schlimm! Es ist sogar noch kürzer? Perfekt!
Filtere aus dem gegebenen Quelltext die Angaben über Rang, Geld und Einwohner und lege sie in SubPatterns ab.
Benutze den gegebenen RegExp-Tester. (Ich hatte mit Regexbuddy selbst Probleme, da die Regexbuddy eine mächtigere Regexp-Engine hat als AutoIt.)
$sString&="ass='float center' style='width:100px'><img src='http://stargods.de/images/symbols/error64.png' height='64' width='64' alt='Vorsicht!' title='Vorsicht!' /></div><div class='float75'><div style='margin:15px 10px 5px;font-size:105%'>Wir haben in dieser Version viele Änderungen am Grundsystem, sowie 'spontane' Erweiterungen vorgenommen. Auf Grund des kurzen Zeitrahmens können wir eine Fehlerfreiheit leider nicht garantieren! Spielt deshalb die ersten Tage bitte besonders aufmerksam und teilt uns Fehler über den Kontakt mit!<br /><br />Danke, das Stargods Team</div></div><div class='clearer'></div></div><div class='box_bottom'></div> --> <div class='title'><h2> </h2></div><div class='box'><div class='float' style='width:52.99%;padding-top:5px'> <iframe id='Footer300links' name='Footer300links' src='http://stargods.de/advertising/footer300.html' framespacing='0' frameborder='no' scrolling='no' width='300' height='250' allowtransparency='true' border='0' target='_blank' a"
$sString&="lt='' /></a></iframe> </div><div class='float justify' style='width:46.99%'> <h1>Willkommen zurück!</h1><br /> Hier hast du eine kleine Übersicht über den aktuellen Status deiner Stadt und deines Volkes. Um nähere Informationen zu den jeweiligen Punkten zu erlangen, benutze bitte die Hauptnavigation.<br /><br /> <div id='belohnungen'></div> </div><div class='clearer'></div></div><div class='box_bottom'></div><br /> <div class='title'><h2>Unterstützt Stargods</h2></div><div class='box bold center'>Helft mit einfachen Mitteln!<br /><br /> <b><font color='#ffff00' face='Arial' size='2'>Zähle zu unserem stargods-Team.</font></b><br /> <a href='http://www.kostenlose-browsergames.de' target='_blank'><img src='http://stargods.de/images/kostenlosebrowsergames.gif' alt='Kostenlose Browsergames' style='border: 1px solid #212121;margin-top:5px;' width='88' height='31' /></a> <br /><font face='Arial' size='1' color='#FFFF00'>Vote <b>1x am Tag</b> für uns.</font><"
$sString&="br /><br /> Und nun wünsche ich euch eine erfolgreiche Zeit.</div><div class='box_bottom'></div><br /> <div class='title'><h2>Neuigkeiten</h2></div><div class='box'>Es sind noch keine Neuigkeiten vorhanden</div><div class='box_bottom'></div> <script type='text/javascript' src='http://stargods.de/layer.js'></script> </div></div> <div id='right'> <p style='margin-bottom:7px'><img src='http://stargods.de/images/icons/layout_delete.png' height='16' width='16' alt='Layout Delete' title='Layout Delete' /> Werbefrei: <a href='http://s1.stargods.de/ucp/adfree'>Aktivieren</a></p> <p style='margin-bottom:8px'><img src='http://stargods.de/images/icons/user_add.png' height='16' width='16' alt='Freundesliste (0)' /> <a href='http://s1.stargods.de/messages/contacts'>Freundesliste (0)</a><br /> <img src='http://stargods.de/images/icons/user_delete.png' height='16' width='16' alt='Blockierliste' /> <a href='http://s1.stargods.de/messages/blocked'>Blockierliste</a><br /> <img src='http://stargods."
$sString&="ass='float center' style='width:100px'><img src='http://stargods.de/images/symbols/error64.png' height='64' width='64' alt='Vorsicht!' title='Vorsicht!' /></div><div class='float75'><div style='margin:15px 10px 5px;font-size:105%'>Wir haben in dieser Version viele Änderungen am Grundsystem, sowie 'spontane' Erweiterungen vorgenommen. Auf Grund des kurzen Zeitrahmens können wir eine Fehlerfreiheit leider nicht garantieren! Spielt deshalb die ersten Tage bitte besonders aufmerksam und teilt uns Fehler über den Kontakt mit!<br /><br />Danke, das Stargods Team</div></div><div class='clearer'></div></div><div class='box_bottom'></div> --> <div class='title'><h2> </h2></div><div class='box'><div class='float' style='width:52.99%;padding-top:5px'> <iframe id='Footer300links' name='Footer300links' src='http://stargods.de/advertising/footer300.html' framespacing='0' frameborder='no' scrolling='no' width='300' height='250' allowtransparency='true' border='0' target='_blank' a"
$sString&="lt='' /></a></iframe> </div><div class='float justify' style='width:46.99%'> <h1>Willkommen zurück!</h1><br /> Hier hast du eine kleine Übersicht über den aktuellen Status deiner Stadt und deines Volkes. Um nähere Informationen zu den jeweiligen Punkten zu erlangen, benutze bitte die Hauptnavigation.<br /><br /> <div id='belohnungen'></div> </div><div class='clearer'></div></div><div class='box_bottom'></div><br /> <div class='title'><h2>Unterstützt Stargods</h2></div><div class='box bold center'>Helft mit einfachen Mitteln!<br /><br /> <b><font color='#ffff00' face='Arial' size='2'>Zähle zu unserem stargods-Team.</font></b><br /> <a href='http://www.kostenlose-browsergames.de' target='_blank'><img src='http://stargods.de/images/kostenlosebrowsergames.gif' alt='Kostenlose Browsergames' style='border: 1px solid #212121;margin-top:5px;' width='88' height='31' /></a> <br /><font face='Arial' size='1' color='#FFFF00'>Vote <b>1x am Tag</b> für uns.</font><"
$sString&="br /><br /> Und nun wünsche ich euch eine erfolgreiche Zeit.</div><div class='box_bottom'></div><br /> <div class='title'><h2>Neuigkeiten</h2></div><div class='box'>Es sind noch keine Neuigkeiten vorhanden</div><div class='box_bottom'></div> <script type='text/javascript' src='http://stargods.de/layer.js'></script> </div></div> <div id='right'> <p style='margin-bottom:7px'><img src='http://stargods.de/images/icons/layout_delete.png' height='16' width='16' alt='Layout Delete' title='Layout Delete' /> Werbefrei: <a href='http://s1.stargods.de/ucp/adfree'>Aktivieren</a></p> <p style='margin-bottom:8px'><img src='http://stargods.de/images/icons/user_add.png' height='16' width='16' alt='Freundesliste (0)' /> <a href='http://s1.stargods.de/messages/contacts'>Freundesliste (0)</a><br /> <img src='http://stargods.de/images/icons/user_delete.png' height='16' width='16' alt='Blockierliste' /> <a href='http://s1.stargods.de/messages/blocked'>Blockierliste</a><br /> <img src='http://stargods."
Schreibe ein Pattern, dass alle Daten in der Liste findet. Falsche Daten wie 31.02.2008 können vorerst ignoriert werden. Dazu kommen wir dann beim konditionalen RegExp.
Zuerst einmal: Wir sind über den Berg! Den schwersten Teil haben wir jetzt hinter uns.
Das soll aber nicht heißen, dass das kommende Kapitel es nicht in sich hat
Vorbereitung: Stell die RegEx Engine in RegExBuddy (Combobox oben links) auf "PCRE" !
Konditionales RegExp arbeitet nach einem If-Then-Else Prinzip. Der Syntaxaufruf lautet
Jetzt kommt erstmal ein ausführliches Beispiel. Ich werde den kompletten "Zusammenbau" des Patterns erklären.
So haben wir auch noch etwas Wiederholungsstoff
Wir haben folgenden String:
Beispielstring
DE: 02.01.2009
US: 11-19-2008
UK: 5-10-2004
DE: 27.3.99
DE: 04.12.2002
US: 9-8-98
UK: 08-12-08
Wie ihr seht, sind das Daten. Einmal in US/UK-Schreibweise, und einmal in der deutschen Schreibweise.
Wir möchten diese Daten finden. Wir möchten aber nur ein Arrayelement pro gefundenem Datum.
Jedes Arrayelement soll nur das Datum, nicht aber "DE,US oder UK" enthalten.
Beispiel Konditionales RegExp
Was wir machen müssen ist also folgendes:
Falls die Zeile mit "DE:" beginnt, suchen wir nach deutschem Datumsformat,
Falls nicht nach dem Englischen.
Dieses Pattern soll gefunden werden, wenn der String mit "DE: " anfängt.
Das DE soll aber nicht gefunden werden. Deswegen kommt es in ein nicht-fangendes klammerpaar (im Then-Part)
Ist euch auf aufgefallen, dass wir "^DE\: " 2 mal schreiben mussten? Das könnte man zwar durch ein Subpattern lösen, welches man per \1 ansteuert (Backtracking - dazu komme ich später nochmal näher), aber wir wollten ja explizit nur ein Array Element. Im nächtsen Teil bei den Assertionen sehen wir, dass es auch einfach geht, denn Assertionen sind mit Konditionen verknüpfbar.
Jetzt also das englische Datum:
Am Zeilenanfang sollte "UK: " oder "US: " zu finden sein.
gefolgt von Monat, einem Bindestrich dem Tag, Bindestrich und jahreszahl.
Das ganze soll in ein Subpattern (damit wir UK oder US nicht im Ergebnis haben)
Dieses Pattern hat mich übrigens ca 35 mins gekostet - also selbst für mich geht sowas nicht mal "eben so von der Hand".
Dieses Pattern überprüft nun, ob der Monat ein Februar ist. Wenn ja, werden Tage von 0-29 gesucht. Wenn nicht, Tage von 0-31.
Und jetzt baut ihr das Ding mal ohne RegExbuddy zusammen
Verschachtelter Konditionaler Regexp unter der Lupe
Mit einer Assertion kann man angeben, dass etwas vor (Lookahead) oder hinter (Lookbehind) einem anderen RegExp-Abschnitt stehen soll.
Eine Assertion hat den Vorteil, dass der Fund nicht mit ins Ergebnis übernommen wird. Schaut euch am besten mal das Beispiel an.
Achtung: Eine Bedingung ist an den Suchbegriffen im Assertion allerdings zu stellen: ihre Länge muss definiert sein, das heißt, es können keine Quantifizierer verwendet werden! Die Assertion "(?<=\d+,)\d\d" führt zu einem Laufzeitfehler.
[/h]
Assertionen understützen den OR-Operator (allerdings nicht in tiefere Klammerebenen).
Assertionen können mit konditionalem RegExp verknüpft werden. Schaut euch das Kapitel noch mal an und ihr werdet sehen, dass wir immer einen positiven Lookbehind (?=) benutzt haben. Statt
Ich habe es ja schon im Teil über Subpatterns erwähnt. Man kann sich auf das Ergebnis eines Subpatterns, oder besser gesagt auf dessen Inhalt zurückbeziehen.
Lange Rede Kurzer Sinn:
RegExBuddy
Rad und Handschlag
Rad und Radschlag_
Hand und Handschlag
Hand und Anschlag
Es wird also immer mittels \1 das Ergebnis des ersten Subpattern geprüft und später im RegExp verwendet. Deswegen findet dieses Pattern auch nur "Hand und Handschlag " und "Rad und Radschlag"
Möchte man die Subpattern-nummer von einer "richtigen" zahl trennen, so benutzt man geschweifte klammern.
Bsp: Wir wollen uns per Backreference auf das 4te Subpattern beziehen. Danach soll eine 9 stehen.
\49 findet allerdings das 49te Subpattern. Deswegen machen wir folgendes:
\{4}9
Backreference kann aber auch mit konditionalem RegExp verwendet werden. Der Syntaxaufruf ist dann
Wir suchen also erst nach "e" und schreiben es in ein Subpattern.
Nun kommt die Kondition (1)...|...). Wenn Subpattern 1 am Match teilgenommen hat, dann soll nach "r" gesucht werden. (Damit wir es in den Ergebnissen angezeigt bekommen, müssen wir das natürlich auch in einem Subpattern speichern. Sollte das 1. Subpattern nciht am Match teilgenommen haben, so wird der Else-Teil der Kondition aktiv, und es wird nach "in" gesucht.
In diesem Teil lernten wir etwas über Assertionen. Desweiteren haben wir unser Wissen zu Backreferencen aufgefrischt, und das über konditionalen RegExp erweitert.
Es gibt 4 Arten von Assertionen: Positiver und Negativer Lookahead (?<=...), (?<!...). (Man erkennt sie an den "Pfeilen" "<".) Und positiven / negativen lookbehind. (?=...), (?!...)
Assertionen müssen eine feste Länge haben! Quantifizer in Assertionen sind NICHT möglich!
Wir können Backreference nutzen, um uns auf ein bereits gefundenes Subpattern zu beziehen. Dies funktioniert mit \1-99 oder $1-99
Backreference kann als kondition verwendet werden. (wenn Subpattern gefunden, dann..., ansonsten....)
Nun haben wir allerdings ein Problem: Die Buchstaben, die ersetzt werden, werden sofort wieder zurück übersetzt. (logisch!). Finde eine Lösung für dieses Problem. (Da es ein RegExp Tutorial ist, sollte die Lösung mit StringRegExp/StringRegExpReplace arbeiten.)
Lösung Aufgabe 1
Wir schreiben die Buchstaben, die ersetzt wurden, in eckige Klammern. Ersetzt werden sollen dann natürlich auch nur die Buchstaben, die NICHT in eckigen Klammern stehen. (Eine Assertion ist hier von Vorteil!)
Aufgabe 2:
Wir haben eine Liste mit eMail-Adressen. Schreibe einen RegExp, dass mithilfe von Backreference doppelte Namen findet. (Der Hostname wie zB. gmx.de kann außer Acht gelassen werden.)