StringRegExp Tutorial / Reguläre Ausdrücke in AutoIt

  • Zitat

    Auf Wunsch von Pee poste ich mein StringRegExp Tutorial nun auch hier. Ich bitte zu beachten, dass die Stellen, welche Beispiele oder Aufgaben beinhalten, welche sich auf Browsergame Quelltexte beziehen, werden die nächsten Tagen abgeändert.


    Inhaltsverzeichnis:
    1. Vorwort
    2. Was ist ein Regulärer Ausdruck?
    3. Unser erster RegExp
    4. Komplexere RegExp
    5. Quantifizer, Fangende- und nicht Fangende Gruppierungen
    6. Konditionales RegExp
    7. Assertionen, Backreference
    Fortsetzung folgt...)



    1. Vorwort
    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 ;)


    2. RegExp?!


    2.1Was ist ein Regulärer Ausruck?

    Zitat von Wikipedia

    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.


    2.2 Was bringt mir ein RegExp überhaupt?!
    Wir nehmen mal folgenden Quelltextausschnitt. WIr wollen den Lagerstand herausfinden.



    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:


    $aResult = StringRegExp($HTML,"l[1-4][^>]+>(\d+\/\d+)",3)
    _Arraydisplay($aResult)


    Ich glaube das sollte als Erklärung reichen ;)


    3. Unser erster RegExp
    3.1 Vorbereitung
    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.

    Code
    Also so!

    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 :D). 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)


    [Blockierte Grafik: http://i48.tinypic.com/xbjmef.jpg]

    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.)



    3.2 Das erste Suchmuster
    Unser erster RegEx wird simpel, denn bekanntlich muss jeder einmal klein anfangen.

    Code
    grund oder folge


    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

    • grund oder folge der Armut?

    sondern auch bei

    • Fahre ich zum Abgrund oder folge ich der Straße?



    3.3 Case Sensitivity in AutoIt
    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:

    Code
    (?i)


    (?i) sorgt dafür, dass ab dem Punkt, wo es im RegExp steht, Groß- und Kleinschreibung ignoriert wird. (?-i) Macht das genaue Gegenteil


    BEISPIEL:
    "SciTE ist die Standard AutoIt-IDE"
    Lassen wir nun den folgenden RegExp auf diesen String los, werden wir keinen Treffer landen:

    Code
    scite


    Machen wir das ganze aber mit unserem gerade gelernten (?i), so finden wir das Wort "SciTE":

    Code
    (?i)scite


    Ihr solltet deswegen im RegExpBuddy die Buttons "Case insensitive" zu deaktiveren, und "^$ match at linebreaks" zu aktivieren. Zu letzterem komm ich später.


    3.3 Literale Suche nach Metazeichen
    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 \\.


    Eine andere Möglichkeit ist das benutzen von

    Code
    \Q...\E


    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

    Code
    \:

    wird zu keinem Fehler führen


    3.4 Der Punkt unter der Lupe.
    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.

    Code
    M.ier

    findet "Maier" und "Meier" , aber nicht "Meyer". Außerdem findet es "Meierling" bis zum "r".

    Code
    H..d


    findet "Hand", "Herd", "Hardrock" aber nicht "Hausdach" oder "Haende"


    Später im Tutorial zeige ich euch, wie man u.a. nach mehreren unbekannten Zeichen suchen kann, ohne deren Anzahl explizit angeben zu müssen.



    3.5 Zeichenklassen
    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.

    Code
    \d

    sucht nach einer Zahl von 0-9. \d\d sucht folglich nach 2 aufeinanderfolgenden Zahlen.

    Code
    \w

    sucht nach einem Alphanumerischen Zeichen, sprich einem Klein oder Großbuchstaben, einer Zahl oder einem Unterstrich "_"


    Damit lassen sich schon schwierigere RegExe zusammenbauen:

    Code
    \w\w, \d\d\. \w\w\w


    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.

    Code
    \d[ABCDEFG]

    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:

    Code
    [0-3][0-9]\.[0-1][0-9]\.

    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:

    Code
    [^A-G]

    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.


    3.6 Überblick über Kapitel 3
    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.


    3.7 Aufgaben
    Was suchen die folgenden RegExe:

    Code
    \d\d:\d\d



    Code
    \w\w\w, \d\d \w\w\w \d\d\d\d



    Code
    "Re \[\d\]"



    4. Komplexere RegExp
    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!


    4.1 Wortgrenzen und die Verwendung von RegExp in AutoIt
    Erinnert ihr euch an unser erstes Beispiel?


    Fahre ich in den Abgrund oder folge ich der Straße?
    Grund oder Folge der Armut?


    Code
    grund oder folge


    Wenn wir nun mit der sog. Wortgrenzen-Zeichenklasse \b arbeiten, erhalten wir

    Code
    (?i)\bgrund oder folge\b


    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.


    #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")


    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:


    4.2 Zeilen- oder Zeichenkettengrenzen
    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.

    Code
    ^AutoIt


    Findet "AutoIt ist total cool!", aber nicht "Ich finde AutoIt toll!".

    Code
    AutoIt\.$

    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:

    Code
    ^RegExp


    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.


    4.3 Whitespaces
    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.


    4.4 Logischer Operator: OR
    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.



    4.5 Übersicht über Kapitel 4

    Wir haben komplexer Patterns, erweiterte Metazeichen und den OR-kennengelernt.

    • Mit \b suchen wir noch Wortgrenzen. ^ steht für den Zeilenanfang, $ für das Zeilenende. \A und \z stehen für Stringanfang und -ende
    • \s findet Whitespace Zeichen. Also alle Zeichen die leere Stellen erzeugen. Tabs, Zeilenumbrüche, Leerzeichen usw.
    • RegExp werden in AutoIt mit StringRegExp() aufgerufen. Die Funktion gibt einen Array zurück.
    • RegExp verfügen in bedingtem Maße über logische Operatoren Operatoren. OR kann mit einer Pipe "|" verwendet werden.


    4.6 Aufgaben.
    Schreibe ein Pattern, welches alle korrekten Uhrzeiten im Teststring findet.


    Benutze dieses Script als vorlage:



    Schreibe einen Pattern, der nach einer Seriennummer sucht. 4 Blocks a 4 Zeichen von A-Z und Zahlen von 0-6, mit Bindestrichen getrennt.



    Was bedeuten die folgenden Suchmuster?

    Code
    ^


    Code
    ^x$


    Code
    ^$



    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.


    5.1 Quantifizer
    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.


    Das Pluszeichen "+" gibt an, dass etwas beliebig oft, mindestens aber ein mal(!) vorkommen soll. Dieser Quantifizer ist gierig.


    Das Fragezeichen "?" gibt an, dass etwas einmal, oder keinmal gefunden werden soll. Dieser Quantifizer ist gierig.


    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!


    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.

    Code
    \w{4}


    Sucht nach allen 4-Stelligen Strings.


    Mit diesem Wissen können wir schon mal versuchen unser Seriennummern Pattern aus Kapitel 4 aufzubessern. Das sah ja wirklich elendig lang aus.


    Code
    [A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]


    Wie müsste man das Pattern umschreiben, damit es zwar immer noch alle Seriennummern findet, aber kürzer und besser aussieht?



    5.1 Gruppierung
    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.


    Man unterscheided zwischen Fangender Gruppierung und Nicht fangender Gruppierung.


    5.1.1 Fangende Gruppierungen aka. Subpatterns.
    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:


    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.


    5.1.2 Gierige und faule Quantifizer
    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.

    #include <Array.au3>
    $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.


    Hier ein anderes Beispiel:

    #include <Array.au3>
    $aRegExp = StringRegExp("12-34.abc.def@mail.de","(.*)\.(.*)*@(.*)\.(.*)",3)
    _ArrayDisplay($aRegExp)


    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.

    #include <Array.au3>
    $aRegExp = StringRegExp("12-34.abc.def@mail.de","(.*?)\.(.*)*@(.*)\.(.*)",3)
    _ArrayDisplay($aRegExp)


    Das erste Subpattern frisst jetzt nur 12-34. Super oder? :thumbup:




    5.1.3 Nicht-aufzeichnende Gruppierungen.
    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.


    5.2 Übersicht über Kapitel 5
    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.


    5.3 Aufgaben
    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:




    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.)



    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.




    6. Konditionales 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

    Code
    (?(?=if)then|else)


    Jetzt kommt erstmal ein ausführliches Beispiel. Ich werde den kompletten "Zusammenbau" des Patterns erklären.
    So haben wir auch noch etwas Wiederholungsstoff :P


    Wir haben folgenden String:


    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.



    6.1 Übersicht über Kapitel 6
    Wir haben konditionales RegExp kennengelernt.

    • Konditionales RegExp wird mit
      Code
      (?(?=if RegExp)Then RegExp|Else RegExp)

      aufgerufen.

    • Der Else-Teil ist optional.
    • Konditionales RegExp kann verschachtelt werden
    • Man braucht es eigentlich nie :D


    6.2 Aufgaben
    Baue das Pattern aus Aufgabe 5.3.3 (Datums-Checker) so um, dass es den 30 & 31 Februar ausschließt!


    Aufgabe 2:
    Baue ein Pattern, das korrekte IPv4-Adressen findet. Von 0.0.0.0 bis 255.255.255.255


    7. Assertionen und Backreference
    7.1 Assertionen
    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.


    Wir schauen uns mal einen positiven Lookahead an:


    foobar

    Code
    (?<=foo)bar


    findet "bar", weil "foo" davor gefunden wurde. Würde der RegEx allerdings

    Code
    (?<=zoo)bar


    heißen, würde die RegExp Maschine nichts finden. Weil dem "bar" kein "zoo" sondern ein "foo" vorangeht.


    Das ganze kann man auch verneinen, und zu einem Negativen Lookahead umwanden. Aus

    Code
    (?<=zoo)bar


    wird dann

    Code
    (?<!zoo)bar


    Mit diesem Pattern finden wir im String "foobar" auch wieder bar, weil dem "bar" kein "zoo" vorangeht.


    Das gleiche gibts natürlich auch als sog. Lookbehind.
    Diesmal suchen wir "foo", wenn "bar" folgt

    Code
    foo(?=bar)


    oder auch "foo", wenn kein "boo" folgt:

    Code
    foo(?!boo)



    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.


    Assertionen understützen den OR-Operator (allerdings nicht in tiefere Klammerebenen).

    Code
    (?<=123|abc)test


    Ist erlaubt. Das hier hingegen nicht:

    Code
    (?<=000(123|abc))test


    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

    Code
    (?(?=If)Then|Else)

    geht also auch

    Code
    (?(?<=If)Then|Else)
    (?(?<!If)Then|Else)
    (?(?!If)Then|Else)


    Je nachdem was man gerade braucht.


    Assertionen sind verschachtelbar und auch hintereinander verwendbar.


    7.2 Backreference
    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:


    Code
    (Rad|Hand) und \1schlag


    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

    Code
    (?(Backreference)Then RegExp|Else RegExp)


    Beispiel:

    #include <Array.au3>
    $sString = "Test dieser Satz ist ein Test"
    $aTmp = StringRegExp($sString,"(e)?(?(1)(r)|in)",3)
    _ArrayDisplay($aTmp)


    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.


    Wer's braucht :rolleyes:


    7.3 Übersicht über Kapitel 7
    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....)


    7.4 Kapitel 7 - Aufgaben
    1. Wir haben ein einfaches Verschlüsselungsprogramm geschrieben, in dem ein Buchstabe in einem Text durch einen anderen ersetzt wird.


    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.)



    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.)




    =======================================


    LG SEuBo

    • Offizieller Beitrag

    Dein Tut finde ich gelungen. Von mir bekommt du eine 1 + :thumbup: SEuBo



    Edit: Ich sehe gerade, du bist ja auch Erleuchtet. :thumbup: Spart viel Strom. :rofl:

  • Sehr gut geschrieben. Werde es mir mal speichern und ich sage ganz klar: DANKE SCHÖN !!!

    ...... Lieben Gruß, ........
    ...........
    Alina ............

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Ich habe die Deutsche Hilfe auf meinem PC und
    OrganizeIncludes ist beim Scripten mein bester
    Freund. Okay?

  • Sehr schön und verständlich geschrieben! :thumbup:

  • Ich find das Tut toll und es hat mir auch schon geholfen aber ich hab noch ne Frage zu den Zeichenklassen... in der Hilfe gibt es ne liste wo zb [:print:] drin steht...
    aber wenn cih das in mein skript einbaue dann funzt nix ....


    hier mal nen testskript
    http://pastebin.com/pAgeUTMt


    EDIT:


    mir wurde geholfen^^
    vllt kannst das ja auch noch kurz erwähnen ;)
    Man muss [:print:] noch mal in [] machen dann gehts... also [[:print:]]^^

  • Hab ich was übersehen oder wird hier nichts über den And Charakter gesagt?


    Edit:Ahh wie ich das sehe müssen für and einfach in der klammer zusammengeschriben werden:

    StringRegExp($test,"(\S\D)",3);liefert alle (sonder)zeichen die keine Zahl und kein trennzeichen sind


    mfg Ubuntu

  • Edit:Ahh wie ich das sehe müssen für and einfach in der klammer zusammengeschriben werden:


    Fast Richtig.
    Für AND benutzt man eigene Zeichenklassen.
    Um zb alle Zeichen anzuzeigen, die weder Zahlen noch WhiteSpaces sind, nutzt man [^\d\s].
    Falsch wäre dagegen [\D\S] (obwohl das ja auf den ersten Blick richtig aussieht), weil das alle zeichen findet, die Keine Zahlen und keine Whitespaces sind. Das ist ein Wiederspruch, weil "Keine Zahl" Whitespaces findet, und "Keine WhiteSpaces" Zahlen findet. :confused:
    [^\d\s] dagegen findet "Keine Zahlen und WhiteSpaces". Besser kann ich's leider nicht erklären. Aber ich kann dir noch ein Beispiel zeigen.


    #include <Array.au3>
    $sText = "abcdefghijklmnopqrstuvwxyz234567890ß´!""§$%&/()=?`+*~#'-_.:,;<>|^°"
    $aRet = StringRegExp($sText,'[\D\S]',3)
    _ArrayDisplay($aRet,"Falsch [\D\S]")


    $aRet = StringRegExp($sText,'[^\d\s]',3)
    _ArrayDisplay($aRet,"Richtig [^\d\s]")


    (\S\D) findet allerdings 2 aufeinanderfolgende Zeichen, von denen das erste Kein WhiteSpace, und das zweite keine Zahl sein darf.

  • Kurze Frage, wie mache ich folgendes:
    Das gibt es:



    Nun sollen alle gehen, ausser 123 und 131...
    Am besten wäre es, wenn es ein Pattern mit ungleich gibt...


    Greetz

  • Es geht sogar ohne RegEx:



    Und für Arrays:


    Ich hoffe ich hab dein Problem richtig aufgenommen ;)

  • Alizame: Dass es anders geht, ist ja klar, aber wie geht es mit RegEx ist hier die Frage oder?


    Apropos Frage: kann man das Resultat umkehren (inventieren) für allgemeine reg. Ausdrücke?


    Z.B:
    14
    124
    AutoIt
    4
    283
    Coding
    macht
    Spaß
    0815


    Suche nach allen Zahlen mit RegEx und inventiere das Ergebnis, so dass man hier nur Wörter erhält!


    Warum suchst du nicht gleich nach Wörtern, wäre vielleicht eine Antwort! Aber wenn ich nicht weiß, dass es Wörter sind, aber ich weiß, dass es Zahlen sind, wäre es einfach nach dem Rest zu suchen!


    Gruß,
    UEZ