PHP/Reguläre Ausdrücke

Aus Mikiwiki
Zur Navigation springen Zur Suche springen

Reguläre Ausdrücke

In einem regulären Ausdruck (engl. regular expression) steckt bei genauer Betrachtung ein vollständiges Programm. Intern arbeitet nicht einfacher Mustervergleich, sondern die Regex-Maschine - ein Programm, das die Ausdrücke auflöst und ausführt. Die Ausführung einer umfangreichen Suche mit einem regulären Ausdruck kann spürbar viel Rechenzeit beanspruchen; es ist durchaus möglich, dass solche Gebilde zu Programmschleifen führen, Abstürze verursachen und sich auch sonst wie andere Programme daneben benehmen. Komplexe Aussagen lassen sich oft auf mehr als einem Weg ausdrücken - der erste Weg ist vielleicht schneller, der zweite kompakter, der dritte verständlicher.

Reguläre Ausdrücke beschreiben Suchmuster, die so komplex sein können, dass sich ganze Filter in einer Zeile beschreiben lassen. Durch Schalter und Funktionen werden diese Muster dann zu einem mächtigen Werkzeug. Die einfachste Form, ein Suchmuster anzugeben, besteht in der Klammerung einer Zeichenkette mit Anführungszeichen.

"Suchmuster"

Die Steuerung innerhalb des Suchmusters wird durch besondere Schalter vorgenommen, mit deren Hilfe die Suchfunktionen erkennen kann, wo die zu suchende oder zu ersetzende Zeichenkette beginnt und wo sie endet.

Schalter Bedeutung
^ Kennzeichnet den Beginn einer Zeichenkette. Innerhalb eckiger Klammern ("[^]") negiert dieses zeichen die Auswahl.
$ Kennzeichnet das Ende einer Zeichenkette.
* Keines oder beliebig viele Zeichen. Entspricht "{0,}".
+ Ein oder mehr Zeichen. Entspricht "{1,}".
? Keines oder genau ein Zeichen. Entspricht "{0,1}".
{}
{,}
Feinere Steuerung der Wiederholung. Bei Verwendung des Kommas, setzt der zweite Parameter die obere Grenze.
() Gruppierung.
(|) Gruppierung mit oder-Operator.
. Der Punkt entspricht genau einem beliebigen Zeichen.
[] Auswahl aus einer Gruppe von Zeichen. Die Zeichen in den eckigen Klammern stehen immer für genau ein Zeichen. Innerhalb der eckigen Klammern verlieren die Sonderzeichen ihre Bedeutung, es wird dann also immer nach dem zeichen selbst gesucht.
[^] Auswahl aus einer Gruppe von Zeichen, die nicht vorkommen dürfen. Die Zeichen in den eckigen Klammern stehen immer für genau ein Zeichen.

Alle diese Sonderzeichen können natürlich auch in normalen Zeichenketten auftreten. Um dort in ihrer normalen Bedeutung nach ihnen zu suchen, müssen sie mit dem Backslash ("\") gekennzeichnet werden. Der Backslash selbst etwa wird also als "\\" geschrieben. Hier eine Zusammenfassung aller Sonderzeichen.

^   .   [   ]   $   (   )   |   *   +   ?   {   }   \

Reguläre Ausdrücke in PHP arbeiten nach dem POSIX-Standard 1003.2, der auch die Angabe generischer Zeichenklassen innerhalb der KLassendefinition "[]" erlaubt.

POSIX-
Ersatzsymbol
Bedeutung
[:alnum:] Alphanumerische Zeichen. "alnum:" entspricht "[a-zA-Z0-9]".
[:alpha:] Zeichen des Alphabets.
[:digit:] Numerische Zeichen (Ziffern, Plus, Minus).
[:blank:] Leerzeichen und Tabulator.
[:space:] Alle "weissen" Zeichen.
[:punct:] Satzzeichen.
[:lower:] Kleinbuchstaben.
[:upper:] Grossbuchstaben.
[:cntrl:] Steuerzeichen.
[:graph:] Druckbare und sichtbare Zeichen.
[:print:] Alphanumerische Zeichen.
[:xdigit:] Hexadezimale Zeichen (0-9, a-f, A-F).

Neben dem Suchen dienen reguläre Ausdrücke oft auch zum Ersetzen von Teilen von Zeichenketten. Auch bei der Angabe der Ersatzzeichen sind einige Sonderzeichen möglich.

Sonderzeichen Bedeutung
\n Zeilenumbruch (engl. newline).
\t Tabulator.

Einfache Beispiele

Einige Beispiele veranschaulichen die Nutzung dieser Schalter.

Beispiel Entsprechung Beschreibung
"^Skript" Skript
Skripts
Skriptsprache
Skriptprogrammierer usw.
nicht: PHP-Skript
Erkennung aller Zeichenketten, die mit den Zeichen "Skript" beginnen.
"sprache$" Skriptsprache
Programmiersprache usw.
nicht: Die Sprachschule
nicht: Sprachenschatz
Erkennung aller Zeichenketten, die auf die Zeichen "sprache" enden.
"^PHP$" PHP Erkennung aller Zeichenketten, die genau dem Suchmuster entsprechen.
"ript" Skript
Skriptsparche
script
Skriptprogrammierer usw.
Erkennung der Zeichen "ript" an einer beliebigen Stelle im Suchwort.
"12*" 1
12
122
1222 usw.
Erkennung von Zeichenketten, die eine "1" und daran anschliessend eine beliebige Anzahl (also auch keine) "2" enthalten.
"12+" 12
122
1222 usw.
Erkennung von Zeichenketten, die eine "1" und daran anschliessend mindestens eine "2" enthalten.
"12?" 1
12
Erkennung von Zeichenketten, die eine "1" und daran anschliessend keine oder eine "2" enthalten.
"1?2+$" 12
2
122
2222 usw.
Erkennung aller Zeichenketten, die keine oder eine "1" enthalten, gefolgt von mindestens einer "2", wobei alle diese Zeichen jeweils am Ende der Zeichenkette stehen müssen.
"DL{2}" DLL Entspricht einer Zeichenfolge von "D", gefolgt von genau zwei aufeinanderfolgenden "L".
"DL{2,}" DLL
DLLL
DLLLL usw.
Entspricht einer Zeichenfolge von "D", gefolgt von mindestens zwei aufeinanderfolgenden "L".
"DL{2,3}" DLL
DLLL
Entspricht einer Zeichenfolge von "D", gefolgt von genau zwei oder drei aufeinanderfolgenden "L". Ein gültiger Ausdrück wäre auch "DL{0,3}" - nicht gültig wäre jedoch "DL{,3}".
"{0,}" *
"{1,}" +
"{0,1}" ?
"1(34)*" 1
134
13434 usw.
Erkennung von Zeichenketten, die eine "1" und eine beliebige Folge von "34" enthalten.
"1(34){1,2} 134
13434
Erkennung von Zeichenketten, die eine "1" und einmal oder zweimal die Folge von "34" enthalten.
"(Dr|Prof)" Dreher
Professur usw.
Erkennung von Zeichenketten, die an beliebiger Stelle die Folge von "Dr" oder "Prof" enthalten.
"^(Dr|Prof)\." Dr.
Prof.
Erkennung von Zeichenketten, die mit der Folge von "Dr." oder "Prof." beginnen. Der sonst als Sonderzeichen behandelte Punkt wird hier mit dem Backslash entwertet und zum normalen Zeichen gemacht.
"(1|2|3)*0" 1230
10
2220
3210 usw.
Erkennung aller Zeichenfolgen, die mit einer beliebigen Anzahl der Zeichen "1", "2" oder "3" beginnen und mit einer "0" enden.
"^.{5}" Sechs
Hausboot usw.
Erkennung einer Folge von fünf beliebigen Zeichen am Anfang einer Zeichenkette.
".[0-9]" A0
23
x7
R9 usw.
Erkennung eines beliebigen Zeichens, gefolgt von einer beliebigen Ziffer (zwischen "0" und "9").
"([a-z]|[A-Z])[0-9]"
"([a-zA-Z])[0-9]"
A0
x7
R9 usw.
Erkennung eines beliebigen Buchstabens (zwischen "a" und "z" bzw. "A" und "Z"), gefolgt von einer beliebigen Ziffer (zwischen "0" und "9").
"[a-f0-9]" a0
c7 usw.
Erkennung eines Buchstabens in Kleinschreibung, gefolgt von einer beliebigen Ziffer. Eignet sich z. B. zur Erkennung hexadezimaler Zahlen.
"^[a-zA-Z]" Anton
sauer usw.
Erkennung von Zeichenfolgen, die mit einem Buchstaben beginnen.
"[0-9]%" 17%
6%%
Erkennung von Zeichenfolgen, in denen eine beliebige Ziffer vor einem Prozentzeichen steht.
"[0-9],[0-9]" 3,4
A3,89 usw.
Erkennung von Zeichenfolgen, in denen eine beliebige Ziffer vor einem Komma steht, gefolgt von einer beliebigen Ziffer.
"[^a-zA-Z]" Ausschluss aller Buchstaben.
"[?+\|]" Erkennt ein beliebiges der Zeichen "?", "+", "\" und "|".

Komplexere Beispiele

Eingabefelder in HTML-Formularen akzeptieren nur Zeichenketten. Es obliegt dem Entwickler, die eingetragenen Werte auf richtige Schreibweise hin zu untersuchen. Reguläre Ausdrücke können das in einer einzigen Programmzeile erledigen.

Werden etwa Geldwerte erwartet, so können diese unterschiedlich eingegeben werden; 1522 Franken können auch "1522", "1'522", "1522.00" oder "1'522.00" sein. Die Entwicklung des passenden regulären Ausdrucks wird im folgenden schrittweise vorgestellt.

Folgender Ausdruck akzeptiert nur Ziffern und Ziffernfolgen. Das erste Zeichen darf nicht 0 sein.

^[1-9][0-9]*$

Folgender Ausdruck akzeptiert nur Ziffern und Ziffernfolgen. Das erste Zeichen darf nicht 0 sein, eine einzelne 0 wird aber akzeptiert.

^(0|[1-9][0-9]*)$

Folgender Ausdruck akzeptiert nur Ziffern und Ziffernfolgen. Das erste Zeichen darf nicht 0 sein, eine einzelne 0 wird aber akzeptiert. Zusätzlich wird ein Minuszeichen erlaubt, wenn der Wert nicht 0 ist.

^(0|-?[1-9][0-9]*)$

Folgender Ausdruck akzeptiert mehrere Ziffern vor und nach dem Punkt.

^[0-9]+(\.[0-9]+)$

Folgender Ausdruck akzeptiert mehrere Ziffern vor und genau zwei Ziffern vor und nach dem Punkt.

^[0-9]+(\.[0-9]{2})$

Folgender Ausdruck akzeptiert mehrere Ziffern vor und eine oder zwei Ziffern nach dem Punkt. Der Ausdruck akzeptiert Zahlen ohne und mit Punkt, dafür sorgt das Fragezeichen nach der Klammer (ein- oder keinmal).

^[0-9]+(\.[0-9]{1,2})?$

Folgender Ausdruck akzeptiert mehrere Ziffern vor und eine oder zwei Ziffern nach dem Punkt. Der Ausdruck akzeptiert Zahlen ohne und mit Punkt, dafür sorgt das Fragezeichen nach der Klammer (ein- oder keinmal). Zusätzlich wird das Tausendertrennzeichen "'" akzeptiert. Zahlen wie "1'555" werden ebenso akzeptiert wie "34.55".

^[0-9]{1,3}(\.[0-9]{3})*(\.[0-9]{1,2})?$

Folgender Ausdruck akzeptiert eine einzelne Null, ebenso aber mehrere Ziffern vor und eine oder zwei Ziffern nach dem Punkt. Der Ausdruck akzeptiert Zahlen ohne und mit Punkt, dafür sorgt das Fragezeichen nach der Klammer (ein- oder keinmal). Zusätzlich wird das Tausendertrennzeichen "'" akzeptiert. Zahlen wie "1'555" werden ebenso akzeptiert wie "34.55".

^[0-9]+|[0-9]{1,3}(\.[0-9]{3})*(\.[0-9]{1,2})?$

Bei der weiteren Auswertung ist zu beachten, dass die Untersuchung einer Zeichenkette mit Hilfe regulärer Ausdrücke noch keine Bewertung darstellt.

Bevor eine Zeichenkette für mathematische Operationen nutzbar wird, müssen die Tausendertrennzeichen entfernt werden. Falls als Dezimaltrennzeichen (wie in Deutschland) das Komma eingesetzt wird, muss es durch einen Punkt ersetzt werden, da PHP intern mit US-amerikanischen Zahlenformaten rechnet.

Die E-Mail-Adresse taucht häufig in Eingaben auf. Hier ist es möglich, mit Hilfe von Anfragen an Nameserver das Vorhandensein einer Domain zu testen und andere Massnahmen zu ergreifen, um richtige von falschen E-Mail-Adressen zu unterscheiden. Ein POP3-Mailname kann aus Gross- oder Kleinbuchstaben (die keine Rolle spielen) sowie aus Minuszeichen, Unterstrichen und Punkten bestehen. Nach dem Namen folgt das "@"-Zeichen und der Domainname. Hier dürfen keine Unterstriche auftreten, sonst entspricht sie dem E-Mail-Namen.

Der E-Mail-Name wird auf gültige Zeichen untersucht. Der Punkt fehlt noch.

^[_a-zA-Z0-9-]+$

Der E-Mail-Name wird auf gültige Zeichen untersucht, wobei auch der Punkt akzeptiert wird - allerdings nicht am Anfang oder Ende des Namens.

^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$

Hier wird nur der Domainname untersucht. Unterstriche sind nicht erlaubt, der Punkt ist zwingend und wird von zwei bis vier Zeichen gefolgt, die wiederum nur Buchstaben sein können.

^[a-zA-Z0-9-]+\.([a-zA-Z]{2,4})$

Eine Kombination aus dem vorderen und hinteren Teil einer E-Mail-Adresse, verbunden durch das "@"-Zeichen.

^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+\.([a-zA-Z]{2,4})$

Eine Kombination aus dem vorderen und hinteren Teil einer E-Mail-Adresse, verbunden durch das "@"-Zeichen. Zusätzlich ist nun auch die Angabe mehrfacher Domainnamen (z. B. "mailadresse@mail.subdomain.domain.xx" möglich. Dabei darf der erste Teil des zweiten Ausdrucks mehrfach vorkommen, muss aber mindestens einmal erscheinen.

^[_a-zA-Z0-9-]+\.([_a-zA-Z0-9-]+)*@([a-zA-Z0-9-]\.)+([a-zA-Z]{2,4})$

Dasselbe Beispiel könnte auch in folgender Schreibweise dargestellt werden.

^
  [_a-zA-Z0-9-]+
  (\.
    [_a-zA-Z0-9-]+
  )*
  @
  (
    [a-zA-Z0-9-]
    \.
  )+
  (
    [a-zA-Z]{2,4}
  )
$

Funktionen

Funktion Beschreibung
preg_match Suche nach einem (dem ersten) Vorkommen des Musters.
preg_match_all Suche nach allen Vorkommen des Musters.
preg_replace Suchen und Ersetzen aller Vorkommen.
preg_replace_callback Suchen und Ersetzen mit Hilfe einer eigenen Funktion, die für jede Fundstelle aufgerufen wird.
preg_split Zerlegen einer Zeichenkette in ein Array anhand eines Suchmusters.
preg_grep Durchsuchen aller Elemente eines Arrays mit einem regulären Ausdruck. Es werden die Elemente als neues Array zurückgegeben, bei denen die Suchbedingung erfüllt war.
preg_quote Markiert Sonderzeichen so, dass sie in regulären Ausdrücken verwendet werden können.

Die mit PHP 3 eingeführten Funktionen "ereg", "ereg_replace" sollten nicht mehr verwendet werden.

Die Funktionen verlangen den Einbau des Suchmusters in zwei Begrenzungszeichen. Das kann jedes Zeichen sein - jedoch keine Buchstaben, Zahlen oder bereits im regulären Ausdruck selbst vorkommende Zeichen.

Bei der Suche nach Übereinstimmungen können mehrere Treffer zurückgegeben werden. In den folgenden Beispielen wird als Argument deshalb ein Array übergeben, in dem dann mehrere Ergebnisse liegen können.

Die Funktion "preg_match" untersucht eine Zeichenkette anhand eines regulären Ausdrucks und füllt ein übergebenes Array mit Fundstellen. Falls die Suche erfolgreich war, wird "TRUE" zurückgegeben. Im folgenden Beispiel wird die Umwandlung des US-amerikanischen Datumsformats in das deutsche gezeigt.

<?php
$date = "2008-09-16";
echo "<p>Datum zu prüfen: $date</p>\n";
## Plazierung der drei Werte in das Array wird durch Klammerung der
## Ausdrucksteile erreicht.
$preg = "([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})";
if (preg_match( "/$preg/", $date, $regs ) ) {
  echo "$regs[3].$regs[2].$regs[1]";
}
else
{
  echo "Falsches Datumsformat: $date";
}
?>

Datum zu prüfen: 2008-09-16

16.09.2008

Die Funktionen können durch Schalter ergänzt werden, die das Verhalten verändern. Die Schalter stehen nach dem letzten Begrenzungszeichen. Interessant ist etwa "i" - damit wird die Unterscheidung von Gross- und Kleinschreibung ausgeschaltet.

"/muster/i"

Mit der Funktion "preg_replace" können die gefundenen Zeichenketten durch andere Zeichenketten ersetzt werden. Im folgenden Beispiel wird die neue (ersetzte) Zeichenkette zurückgegeben. Ergab der reguläre Ausdruck keine Übereinstimmung, so wird die originale Zeichenkette übergeben. "pre_replace" entspricht dem, nur wird die Gross- und Kleinschreibung allgemein nicht beachtet. Die Umwandlung wird auch für Umlaute richtig durchgeführt.

<?php
$email = "test#domain.xx";
echo preg_replace("/#/", "@", $email);
?>

test@domain.xx

Das vorige Beispiel legt die Nutzung der Textersetzungsfunktion "strstr" nahe. Zudem sind Funktionen mit regulären Ausdrücken verhältnismässig langsam. Tatsächlich ist die Anwendung regulärer Ausdrücke zum Ersetzen aber noch weitaus komplexer als bisher beschrieben.

Die Funktion "preg_split" teilt eine Zeichenkette in Segmente und legt jedes Segment als Element in einem Array ab. Als Suchwort wird dabei ein regulärer Ausdruck akzeptiert. Wird keine Übereinstimmung gefunden, so steht die gesamte Zeichenkette danach im Array im Index 0.

<?php
$email = "name@domain.xx";
$test  = preg_split("/@/",$email);
echo "Name: $test[0]<br />\n";
echo "Domain: $test[1]<br />\n";
?>

Name: name
Domain: domain.xx