Reguläre Ausdrücke - Sammelthread

  • PHP

  • Broken Sword
  • 5490 Aufrufe 5 Antworten

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Reguläre Ausdrücke - Sammelthread

    Hi,
    da immer mal wieder kleinere Fragen bezüglich regulären Ausdrücken (RegEx Regular Expressions) auftauchen oder per PN geschickt werden (:D) hier mal ein Sammelthread. Fragen und Tipps einfach hier rein, damit eine Art Lexikon entsteht.





    Gruß
    Broken Sword
    Auf dem Abstellgleis sah man ihn liegen,
    Auf dem Abstellgleis zwischen Schwelle und Gestein,
    Auf dem Abstellgleis im strömenden Regen,
    Auf dem Abstellgleis allein.

    Dieser Beitrag wurde bereits 11 mal editiert, zuletzt von Broken Sword ()

  • Reguläre Ausdrücke in PHP

    Am Anfang steht natürlich die Frage, welche Art man beherrschen/erlernen sollte. POSIX-erweiterte RegEx (ereg_match) oder Perl-kompatibles/PCRE RegEx (preg_match). Die Frage wird ab PHP5.3 schnell gelöst sein, da POSIX-Funktionen ausgemustert sind. Unabhängig davon, dass man seinen Code ab PHP5.3 nicht updaten will, sollten man vor PHP5.3 auch Perl RegEx benutzen, da POSIX immer den gesamten Code durchsucht, Perl hingegen optional beim ersten Auftauchen des Musters stoppt.

    PCRE-Funktionen



    preg_match
    preg_match führt eine Suche mit einem regulären Ausdruck aus.

    Quellcode

    1. if(preg_match('![a-z0-9\-_\.]+@[a-z0-9\-.]+\.[a-z]{2,4}!i',$source)) die('mindestens eine Email-Adresse gefunden!');


    preg_match_all
    preg_match_all unterscheidet sich von preg_match nur dahingehend, dass alle Vorkommnisse gesucht und gespeichert werden.

    Quellcode

    1. if($result = preg_match_all('![a-z0-9\-_\.]+@[a-z0-9\-.]+\.[a-z]{2,4}!i',$source,$matches)) die($result.' Email-Adressen gefunden!');


    preg-grep
    preg_grep durchsucht alle Elemente eines Arrays und gibt die Schlüssel zurück, die auf den regulären Ausdruck passen.

    Quellcode

    1. var_dump(preg_grep('!^[a-c]+$!',$source));


    preg_replace
    Die wohl bekannteste RegEx-Funktion im PHP-Land. (Wer erinnert sich nicht an seine ersten „BB-Code parsing-Versuche“ :D)
    preg_replace sucht und ersetzt einen Text mit regulären Ausdrücken.

    Quellcode

    1. echo 'Diebstahl!!! '.preg_replace('!peter hat ([0-9]+) eier!is','Hans hat $1 Eier',$source);


    preg_replace_callback
    preg_replace_callback ist die sichere Variante ein Muster im Text zu suchen und mit einer Funktion zu ersetzen. (Wenn man nicht ordentlich „Slashes escaped“ kann preg_repalce mit dem eval-Modifikator ein Sicherheitsleck darstellen)

    Quellcode

    1. echo 'Vervielfachung!!! '.preg_replace_callback('!peter hat ([0-9]+) eier!is',create_function('$current','return \'peter hat \'.($current[1]*5).\' eier\';'),$source);


    preg_filter (Ab PHP 5.3)
    preg_filter unterscheidet sich von preg_replace nur dahingehend, dass sie anstatt die modifizierte Zeichenkette/Array (wenn $subject ein Array ist) auszugeben, die Übereinstimmungen zurück gibt.

    Quellcode

    1. var_dump(preg_filter('!peter hat ([0-9]+) eier!is','Hans hat $1 Eier',$source));


    preg_quote
    preg_quote setzt einen Backslash vor Zeichen, die zur Syntax regulären Ausdrücken gehören.

    Quellcode

    1. echo preg_replace('!'.preg_quote($uname).' hat ([0-9]+) eier!is','Hans hat $1 Eier',$source);


    preg_last_error
    preg_last_error gibt den Fehlercode der letzten PCRE RegEx-Auswertung aus.

    Quellcode

    1. switch(preg_last_error()) {
    2. case PREG_NO_ERROR:
    3. echo 'Keine Fehler gefunden';
    4. break;
    5. case PREG_BACKTRACK_LIMIT_ERROR:
    6. echo 'Das Backtrack-Limit wurde erreicht';
    7. break;
    8. ...


    Gruß
    Broken Sword
    Auf dem Abstellgleis sah man ihn liegen,
    Auf dem Abstellgleis zwischen Schwelle und Gestein,
    Auf dem Abstellgleis im strömenden Regen,
    Auf dem Abstellgleis allein.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von Broken Sword ()

  • Einleitung zur RegEx-Syntax

    Die Syntax von PCRE RegEx sieht in der Regel so aus:
    !^peter ((pan)+|[0-z]{5})$!i


    Um dem regulären Ausdruck befinden sich Begrenzer (Engl. Delimiters). Diese können beliebige Zeichen sein, außer alphanumerische Zeichen, der Backslash und Leerzeichen. Da der Begrenzer im eigentlichen Ausdruck maskiert werden muss, ist es sinnvoll ein Zeichen zu verwenden, welches nicht im Ausdruck vorkommt.

    ^ markiert den Anfang eines Textes und $ das Ende. (Standardmäßig werden Zeilenumbrüche vor dem Ende ignoriert)

    Einfacher Text wird normal geschrieben, wie gesagt sollten die Zeichen \ + * ? [ ] ^ $ ( ) { } = ! < > | : - und der Begrenzer mit einem Backslash maskiert werden.

    Klammern in reguläre Ausdrücke dienen zum Eingrenzen von Unter-Mustern. preg_match(_all) und preg_replace bewerten jedes Unter-Muster einzeln. Wichtig ist, dass der gesamte Ausdruck auch als Unter-Muster gesehen wird. (Ergo, gibt es ein $0 und $match[0])

    Gewichtungen geben die Anzahl der Vorkommnisse (des Unter-Musters oder Zeichens) an.
    • ? 0 oder 1 mal vorhanden
    • * >= 0 mal vorhanden
    • + > 0 mal vorhanden
    • {a} a mal vorhanden
    • {a,b} a bis b mal vorhanden
    • {a,} >= a mal vorhanden
    • {,b} <= b mal vorhanden


    Alternation – der „Oder-Operand“

    Charakter-Klassen werden durch eckige Klammern definiert. Jedes Zeichen, welches in der Charakter-Klasse eingefügt wurde, darf als Text vorkommen.
    Benutzt man den Bindestrich in der Definition werden alle Zeichen, die zwischen dem Zeichen vor und nach dem Bindestrich stehen in die Klasse mit einbezogen (Vom Gewicht sind die ASCII-Codes der Zeichen) Im Beispiel darf also alles von 0 bis z (Da die Großbuchstaben einen kleineren ASCII-Wert haben, werden sie mit einbezogen), der Bindestrich, Raute und das „Wellenzeichen“ im Text stehen. Wem das mit den ASCII-Codes zu umständlich ist, kann auch einfach [a-z0-9A-Z\-#~] benutzen. Das Dach bedeutet, dass es eine negative Klasse ist, sprich das Gegenteil beachtet wird.

    Es gibt vordefinierte Charakter-Klassen, die mit einem Doppelpunkt aufgerufen werden [:vordefinierteKlasse:]
    Folgende sind definiert:

    • alnum Buchstaben und Zahlen
    • alpha Buchstaben
    • ascii ASCII-Codes 0 - 127
    • blank Leerzeichen oder Tabulator
    • cntrl Kontrollzeichen
    • digit Dezimalzahlen
    • graph druckbare Zeichen, ausgenommen Leerzeichen
    • lower Kleinbuchstaben
    • print druckbare Zeichen mit Leerzeichen
    • punct druckbare Zeichen, ausgenommen Buchstaben und Zahlen
    • space Leerzeichen(-Taste)
    • upper Großbuchstaben
    • word „Wort“-Zeichen
    • xdigit Hexadezimal Zahlen


    Außer den vordefinierten Klassen, gibt es noch vordefinierte Charakter-Typen. Sie werden, wie normale Zeichen behandelt.
    • . Der Punkt steht für ein beliebiges Zeichen, außer für den Zeilenumbruch (wenn nicht anders deklariert)
    • \C Byte
    • \d Dezimalzahl
    • \D Keine Dezimalzahl
    • \h Horizontales Leerzeichen
    • \H Kein horizontales Leerzeichen
    • \p{xx} Zeichen mit xx-Eigenschaft
    • \P{xx} Zeichen ohne xx-Eigenschaft
    • \R Neue Zeile-Sequenz
    • \s Leerzeichen
    • \S Kein Leerzeichen
    • \v Vertikales Leerzeichen
    • \V Kein vertikales Leerzeichen
    • \w „Wort“-Zeichen
    • \W Kein „Wort“-Zeichen
    • \X Erweiterte Unicode Sequenz


    \p{xx} bzw. \P{xx} kann folgende Eigenschaften haben:


    Hinter dem zweiten Begrenzer stehen die Modifikatoren. Das sind Optionen, die für den regulären Ausdruck gesetzt sind.
    Es gibt folgende (andere Zeichen führen zu einer Fehlermeldung):

    • Anchored – Muster wird verankert. Heißt, es kann nur auf den Anfang des Textes passen.
    • Dollar_Endonly – Dollar-Zeichen passt nur für das Ende des Textes, Zeilenumbrüche werden nicht ignoriert.
    • eval – preg_replace behandelt $replace als eval-Rückreferenz.
    • JChanged – Erlaubt doppelte Namen für Teilsuchmuster.
    • multiline - ^ und $ passen neben Anfang/Ende der Zeichenkette, auch auf einzelne Zeilen.
    • S – Analysiert ein Suchmuster zusätzlich.
    • s DotAll - . (der Punkt) passt auch auf Zeilenumbrüche.
    • Ungreedy – Kehrt die Gier von PCRE RegEx um.
    • utf8 – Suchmuster werden als UTF-8 behandelt.
    • Xtra – Backslash vor einem Buchstaben, der keine Bedeutung hat, verursacht eine Fehlermeldung.
    • xtended – Leerzeichen werden im Suchmuster ignoriert, falls nicht maskiert oder in einer Zeichenklasse befindlich.


    Ergo:
    Beispiel-Muster passt auf alle Texte, die mit "peter " beginnen und entweder mit mindestens ein "pan" folgen oder einer fünfstellige Zeichenkette, die aus ASCII-Zeichen vom Wert 48 (0) bis Wert 122 (z) besteht.

    Mehr mehr mehr


    Rückreferenzen (Engl. Backreferences) sind Referenzen im Suchmuster, die auf Unter-Muster im vorangegangenen Teil des Ausdrucks zeigen. Eine Referenz wird mit dem Backslash und der Nummer des Unter-Suchmuster bestimmt.
    Bsp.

    Quellcode

    1. $text = 'Jetzt kommt`s: peter hat 5 Eier und peter hat 25 Hasen, Friedirch hat 42 Eier und Friedrich hat 2 Hasen - wie macht er das?';
    2. echo preg_replace('!(.+?) hat ([0-9]+) eier und \1 hat ([0-9]+) hasen!i','$1 hat $2 Hasen und $1 hat $3 Eier',$text);

    Ausgabe ist [noparse]“Jetzt kommt`s: peter hat 5 Hasen und peter hat 25 Eier, Friedirch hat 42 Eier und Friedrich hat 2 Hasen - wie macht er das?“[/noparse].

    Gruß
    Broken Sword
    Auf dem Abstellgleis sah man ihn liegen,
    Auf dem Abstellgleis zwischen Schwelle und Gestein,
    Auf dem Abstellgleis im strömenden Regen,
    Auf dem Abstellgleis allein.

    Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von Broken Sword () aus folgendem Grund: backtraces added

  • Die Gier der RegEx

    Reguläre Ausdrücke sind dem Menschen ähnlicher als man denkt. Es gibt unterschiedliche Neigungen der RegEx Engine und zwar:
    Gierig (Engl. greedy)
    [indent]Sie verschlinge so viel Text, wie sie nur können. Das ist bei Mehrvorkommen der Suchmuster ausschlaggebend, denn RegEx macht aus diesen Mehrvorkommen, wenn man unvorsichtig ist, ein einziges.
    Bsp. 1

    Quellcode

    1. $text = 'Jetzt kommt`s: peter hat 5 Eier und hans hat 8 Eier - das ist interessant ;)';
    2. echo preg_replace('!(.+) hat ([0-9]+) eier!i','$1 hat $2 Hasen',$text);

    Ausgabe wird nicht sein [noparse]„Jetzt kommt`s: peter hat 5 Hasen und hans hat 8 Hasen - das ist interessant ;)“ sondern „Jetzt kommt`s: peter hat 5 Eier und hans hat 8 Hasen - das ist interessant ;)“
    $1 ist nämlich nicht einfach „peter“, sondern „peter hat 5 Eier und hans“.[/noparse]

    Btw. Von wegen „dem Menschen ähnlich“... gierig ist der Standard bei RegEx ;)[/indent]

    Faul / „Ungierig“ ? (Engl. lazy / ungreedy)
    [indent]Man kann die Gier mittels Modifikator Ungreedy umkehren. So wird beim Beispiel 1 auch das anfangs Erwartete ausgegeben. Um nicht den gesamten Ausdruck auf ungreedy zu setzen, benutzt man einfach das Fragezeichen hinter einer Gewichtung.
    Bsp 1 faul ohne Modifikator

    Quellcode

    1. $text = 'Jetzt kommt`s: peter hat 5 Eier und hans hat 8 Eier - das ist interessant ;)';
    2. echo preg_replace('!(.+?) hat ([0-9]+) eier!i','$1 hat $2 Hasen',$text);

    [/indent]

    Habgierig + (Engl. possessive)
    [indent]Habgierige Ausdrücke unterscheiden sich von gierigen in soweit, dass sie keine Zurückverfolgung unternehmen.
    Nehmen wir das Beispiel 1 – der gierige Ausdruck versucht in (.+) so viel rein zu packen, wie es nur geht. Kommt also bis zum Ende des Strings. Allerdings wird er dort nicht mit „hat“ fündig, deshalb verfolgt er sein Ergebnis zurück, bis er den Rest des Suchmusters auch unterbringen kann. Habgierige Ausdrücken würden den letzten Schritt nicht tun und einfach sagen, sie haben nichts gefunden – sie geben nichts, was sie als Suchmuster erkannt haben, wieder her. Daher gibt es jedoch einen Parformance-Gewinn, der das Rumärgern mit diese Ausdrücken honoriert. Man kann sie Benutzen, indem man hinter der Gewichtung das Plus-Zeichen benutzt.
    Bsp 1 habgierig

    Quellcode

    1. $text = 'Jetzt kommt`s: peter hat 5 Eier und hans hat 8 Eier - das ist interessant ;)';
    2. echo preg_replace('!(.++) hat ([0-9]+) eier!i','$1 hat $2 Hasen',$text);

    $text wird unverändert ausgegeben. Denn der Punkt passt überall hin und *Schwupps!* ist die habgierige Engine beim String-Ende und kann nicht mehr das restliche Suchmuster unterbringen.[/indent]

    Gruß
    Broken Sword
    Auf dem Abstellgleis sah man ihn liegen,
    Auf dem Abstellgleis zwischen Schwelle und Gestein,
    Auf dem Abstellgleis im strömenden Regen,
    Auf dem Abstellgleis allein.

    Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von Broken Sword ()

  • Broken Sword schrieb:

    Fragen und Tipps einfach hier rein
    Ich glaub dann mach ich hier mal den Anfang.

    Wenn ich Beispielsweise einen Vor-/Nachnamen prüfen möchte, könnte ich den Charakter-Typ \w verwenden. Damit würden auch Umlaute wie äöüßéè etc. akzeptiert.
    Problem an \w ist, dass es Zahlen und den Unterstrich (_) unterstützt.
    Ich hab in Nachnamen noch nie Zahlen oder Unterstriche gesehen, aber alle möglichen Buchstaben eingeben wäre auch etwas zu viel.
    \w^\d funktioniert leider nicht.

    mfg
    snip3r
    Rechteübersicht * Forenregeln * F.A.Q. * Lexikon
    Suchfunktion * Chat * User helfen User
    Patrioten reden nur davon, dass sie für ihr Land sterben, niemals davon, dass sie für ihr Land töten. (Bertrand Russell)
  • Snip3r schrieb:


    Wenn ich Beispielsweise einen Vor-/Nachnamen prüfen möchte, könnte ich den Charakter-Typ \w verwenden. Damit würden auch Umlaute wie äöüßéè etc. akzeptiert.
    Problem an \w ist, dass es Zahlen und den Unterstrich (_) unterstützt.
    Ich hab in Nachnamen noch nie Zahlen oder Unterstriche gesehen, aber alle möglichen Buchstaben eingeben wäre auch etwas zu viel.
    \w^\d funktioniert leider nicht.


    Warum verwendest du anstatt \w nicht einfach \p{L&} ;)

    Gruß
    Broken Sword
    Auf dem Abstellgleis sah man ihn liegen,
    Auf dem Abstellgleis zwischen Schwelle und Gestein,
    Auf dem Abstellgleis im strömenden Regen,
    Auf dem Abstellgleis allein.