Reguläre Ausdrücke.

09.10.2009

AnreißerbildWie im Post „Eigenes Kontaktformular mit PHP“ versprochen, gibt es hier ein Tutorial und eine Übersicht zu Regulären Ausdrücken.

Der Aufbau:

Am Beispiel eines URL-Testers sehen wir uns den Aufbau und die Eigenheiten von Regulären Ausdrücken an:

@^https?://([a-z0-9\.-]*\.)?[a-z0-9-]{2,255}\.[a-z]{2,4}(/.*/?)?$@i

Jetzt zerlegen wir ihn in die einzelnen Teile:

  • Die Delimiter: Sie sind am Anfang und am Ende (zu mindest vor den Flags) zu finden. In unserem Fall sind es die At-Zeichen. Da sie das Ende des Regulären Ausdruckes und den Beginn seiner Flags markieren, können wir hier keinen Schrägstrich – wie es sonst üblich ist – verwenden, da es sont hieße, dass nach dem http: schon Schluss wäre.
  • Die Anfangs- und Endzeichen sagen, dass wir gerne die ganze Zeichenkette überprüfen wollen, und nicht nur einen Teil. Diese Zeichen sind das Zirkumflex (^) und das Dollarzeichen ($). Der Ausdruck /^hund$/i findet mit Anfangs- und Endzeichen nur das Wort „Hund“, wenn es alleine in einer Zeichenkette steht. Der Grund, warum das kleine h auch auf das große H zutrifft, wird weiter unten bei den Flags erklärt. Zurück zum Thema. Lässt man jetzt das Zirkumflex weg, so dass der Ausdruck /hund$/i lautet, bedeutet das, dass wir nur darauf überprüfen wollen, ob das „hund“ am Ende der Zeichenkette steht. Damit trifft der Ausdruck zum Beispiel auch auf "Schweinehund" zu. Schreibt man allerdings kein Dollarzeichen (/^hund/i), heißt das, dass wir wissen wollen, ob der Hund am Anfang der Zeichenkette steht. Deshalb findet man mit /^hund/i unter Anderem auch "Hundemüde".
  • Der eigentliche Reguläre Ausdruck, wie wir ihn später genauer behandeln werden.
  • Die Flags: Sie stehen hinter dem hinteren Delimiter und sagen der Engine (das tolle Programm, das die Drecksarbeit für uns erledigt), ob sie zum Beispiel Groß- und Kleinschreibung ignorieren soll (mit dem i-Flag, siehe Beispiel), oder (in Javascript), dass alle Vorkommen gefunden werden sollen (denn sonst ist die Engine faul und hört nach dem ersten Ergebnis auf).

Der „richtige“ Reguläre Ausdruck:

Jetzt wird noch einmal zerlegt, in logische Abschnitte:

  • https?://: Das Protokoll wird überprüft, dabei sind http:// und https:// zulässig, weil das Fragezeichen (welches ein Metazeichen ist) aussagt, dass der Buchstabe oder die Gruppe (in diesem Fall das s), die unmittelbar vor ihm steht, entweder null (http://) oder ein Mal (https://) vorkommen darf.
  • ([a-z0-9\.-]*\.)?: Dieses Stück trifft auf die Subdomain zu, denn diese darf die Buchstaben a bis (-) z und die Ziffern 0 bis 9, sowie einen Punkt (\., zu der Sache mit dem Backslash kommen wir später) für Subsubdomains und den Bindestrich beinhalten. Die eckigen Klammern ([ und ]) — schon wieder Metazeichen — bedeuten, dass eines der Zeichen, das in ihnen steht, an dieser Stelle vorkommen muss. Der Bindestrich muss in diesen Klammern am Ende stehen, weil er (wie man davor sieht) auch dazu verwendet wird, Zeichenbereiche anzugeben (z.B. [a-z]). Das Sternchen ist auch ein Metazeichen und bedeutet, dass der vorhergehende Buchstabe oder die Gruppe beliebig oft vorkommen darf — also auch null Mal. Danach kommt ein weiterer Punkt mit Backslash davor (denn der Punkt ist auch ein Metazeichen, und muss seiner Funktion mit Hilfe des Backslashes enthoben werden), der letztendlich die Subdomain von der Domain trennt. Wie wir wissen braucht es aber keine Subdomain bei gültigen URLs, weshalb wir dieses Stück mit normalen Klammern zu einer Gruppe zusammenfassen und diese mit dem altbekannten Fragezeichen optional machen.
  • [a-z0-9-]{2,255}\.[a-z]{2,4}: Die Domain wird mitsamt der TLD überprüft, das geschieht indem wir zwei bis 255 Mal einen Kleinbuchstaben, eine Zahl, oder den Bindestrich, gefolgt von einem Punkt und einer zwei bis vier Zeichen langen TLD (also von .de über .com und .net bis .name und .mobi) zulassen.
  • (/.*/?)?: Hiermit überprüfen wir — wenn auch etwas schlampig — ob noch ein Verzeichnis an der Domain hängt. Damit sagen wir auch, dass noch ein Slash hinten an der URL hängen darf. Mit dem Pünktchen lassen wir wieder jedes beliebige Zeichen zu. Wenn jemand sich die Mühe machen will, dort eine [Gruppe] mit allen erlaubten Zeichen einzusetzen, dann wünsche ich demjenigen viel Spaß dabei.

PHP-Funktionen:

Es gibt eine Reihe von PHP-Funktionen, die für den Umgang mit regulären Ausdrücken gedacht sind. Diese sind im PCRE-Modul enthalten. PCRE steht für Perl Compatible Regular Expressions, also für Reguläre Ausdrücke, die mit denen von Perl kompatibel sind. Für die Unterschiede zwischen Perl- und POSIX-kompatiblen Regulären Ausdrücken, siehe Reguläre Ausdrücke in der englischen Wikipedia. Natürlich gibt es auch die ereg-Funktionen, die werden allerdings ab PHP 6 entfernt. Die Funktionen, die wir brauchen sind preg_match, preg_match_all und preg_replace. Für die letzte Funktion gibt es übrigens ein zusätzliches Flag.

  • preg_match(string $ausdruck, string $zu_pruefen[, array &$gefunden[, int $flags[, int $anfang]]]):
    $zu_pruefen wird nach $ausdruck durchsucht. Die Funktion bricht nach der ersten Übereinstimmung ab und gibt true zurück, ansonsten false. Wenn angegeben, werden in $gefunden der gefundene Text und die gefundenen Teilsuchmuster (bzw. Gruppen) gespeichert, in $gefunden[0] befindet sich die ganze gefundene Zeichenkette, in $gefunden[1] das erste gefundene Teilsuchmuster und so weiter. Für $flags gibt es im Moment nur den Wert PREG_OFFSET_CAPTURE, mit dem das $gefunden-Array insofern zweidimensional gemacht wird, dass an $gefunden[$n][0] die gefundene Zeichenkette liegt und an $gefunden[$n][1] der Anfang der Zeichenkette. Mit $anfang wird festgelegt, ab welcher Position mit dem Suchen angefangen werden soll.
  • preg_match_all(string $ausdruck, string $zu_pruefen, array &$gefunden[, int $flags[, int $anfang]]):
    Im Grunde das selbe wie preg_match, nur dass $gefunden angegeben werden muss. Diese Funktion gibt jedoch die Anzahl der gefundenen Zeichenketten zurück, und false im Fehlerfall.
  • preg_replace(mixed $ausdruck, mixed $ersatz, mixed $zu_pruefen[, int $maximum[, int &$anzahl]]):
    Diese Funktion dient zum ersetzen von Zeichenketten nach dem Muster eines Regulären Ausdruckes. Dabei kann $ersatz Referenzen auf Teilsuchmuster von $ausdruck in der Form von \\n oder $n enthalten. $maximum legt fest, wie oft maximal ersetzt werden darf, und in $anzahl wird gespeichert, wie viele Ersetzungen vorgenommen wurden. Wird das Flag e gesetzt, so wird $ersatz als PHP-Code interpretiert.

Übersicht:

Im Folgenden eine tabellarische Übersicht über die Metazeichen:

Metazeichen Endzeichen Funktion Beispiel
. Ein beliebiges Zeichen. Häufig mit * verwendet. /.*/: Trifft auf alles zu.
\ Hebt die Metafunktion des nachstehenden Zeichens auf. /.* \.\.\./: Überprüft auf eine Zeichenkette, die mit ... endet.
[ ] Trifft auf ein einzelnes Zeichen, das in den Klammern angegeben wird, zu. Mit - wird ein Zeichenbereich angegeben, z.B. /[a-z]/, weshalb ein Minus, wenn man darauf überprüfen will, auch am Ende der Klammer angegeben werden muss. /0x[A-F0-9]+/i: Sucht nach Hexadezimalzahlen.
^ (für das Endzeichen nicht benötigt) $ (für das Anfangszeichen nicht benötigt) Markiert, dass die gesuchte Zeichenkette am Anfang (/^…/) oder am Ende (/…$/) stehen soll, oder dass die ganze Zeichenkette geprüft werden soll (/^…$/). ^ muss nach dem ersten Delimiter stehen, $ vor dem letzten. /^-?[1-9][0-9]*$/: Prüft auf eine gültige Ganzzahl.
( ) Erzeugt ein neues Teilsuchmuster. /^(Aller )?Anfang(ist schwer)?\.$/: Keine wirklich sinnvolle Funktion.
{ } Syntax: {m,n}. Das Zeichen oder die Gruppe davor muss minimal m Mal und darf maximal n Mal vorkommen. Beide sind optional. / [a-z]{,5} /i: Findet ein maximal fünf Zeichen langes Wort.
? Macht das vorherige Zeichen/die vorherige Gruppe optional. Entspricht {0,1}. /(www\.)?[a-z0-9-]{2,}\.com/: Macht das www am Anfang der zu überprüfenden Internetadresse optional.
+ Das vorhergehende Zeichen oder Teilsuchmuster muss mindestens einmal vorkommen. Entspricht {1,}. /ab+/: Findet ab, abb und so weiter, aber nicht a.
* Das vorhergehende Zeichen oder Teilsuchmuster kann beliebig oft vorkommen. Entspricht {0,}. /.*/: Trifft auf Alles zu.
| Entweder die Zeichen davor oder die danach müssen vorkommen. /^Hund|Katze$/: Findet Hunde und Katzen.

Es gibt nicht viele Flags, aber trotzdem hier noch eine kleine Tabelle:

Flag Verwendbar in Wirkung
i PHP und JavaScript Groß- und Kleinschreibung im Regulären Ausdruck werden ignoriert.
e PHP In der Funktion preg_replace wird der zweite Parameter als PHP-Code interpretiert und ausgeführt.
g JavaScript Die Engine sucht nicht nur nach dem ersten, sondern allen vorkommen des Patterns.
Johаnnes

TopOfBlogs