handcode.de Logo
« Einbinden mit include() toc Regex2 Tips zu RegEx »

Reguläre Ausdrücke Teil 1 (regular expression)

So, hier kommt jetzt also ein Artikel zu einem der kryptischsten Bereiche von PHP und auch einiger andere Sprachen:

  • Reguläre Ausdrücke
  • bzw. english: regular expression
  • oder in der Kurzform: RegEx

Wenn man in einem Script etwas sieht, das so aussieht, als wenn der Programmierer eingeschlafen und mit dem Kopf auf die Tastatur gefallen ist, dann hat man wahrscheinlich RegEx gefunden :-)

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

Wir kommen bei unserem Wiki aber nicht um RegEx drumrum, wenn ich ganz ehrlich bin: das ist einer der Gründe warum ich mich für ein Wiki als Projekt im Seminar entschieden habe :-)

Im Wiki müssen wir mit RegEx in den Texten die Links und Text-Auszeichnungen suchen und verarbeiten.

Ok, aber was sind den nun RegEx?

Ganz allgemein gesagt, repräsentiert ein regulärer Ausdruck ein Zeichen-Muster.

Die Funktionen für diese regulären Ausdrücke vergleichen dieses Muster mit einem String und erkennen dabei, ob ein Teil oder die gesamte Länge des Strings mit dem Muster übereinstimmt. Es gibt Funktionen die angeben, ob es einen Treffer gab, während andere Änderungen am String vornehmen.

PHP unterstützt 2 Arten von RegEx: 
POSIX und Perl-kompatible.

Manche behaupten, RegEx nach POSIX seien einfacher zu lesen. Sie sind aber weniger leistungsfähig und deutlich langsamer als die entprechenden Perl-kompatiblen Funktionen.

Wir werden hier mit den Perl-kompatiblen Funktionen und deren Syntax arbeiten. Wer RegEx also schon von Perl her kennt wird trotz kleiner Unterschiede zwischen PHP und Perl mit der Syntax keine Probleme haben.

Die POSIX-Funktionen findet man auf www.php.net unter 
http://de.php.net/manual/de/ref.regex.php 

Die Perl-kompatiblen (PCRE) unter 
http://de.php.net/manual/de/ref.pcre.php 

Es gibt 3 Anwendungsgebiete für RegEx:

  • Das Matching, also die Erkennung von Mustern. Wird auch zur Extrahierung von Informationen aus einem String genutzt.
  • Die Ersetzung eines erkannten Text-Teils durch einen neuen.
  • Das Zerlegen eines Strings in kleinere Teile.

PHP hat fur alle 3 Bereiche Funktionen sowohl für POSIX als auch für die Perl-kompatible Variante.

Die meisten Zeichen bei RegEx stehen für sich selbst, d.h. sie werden als literale Zeichen betrachtet.

Man kann mit RegEx aber auch Zeichenklassen definieren, z.B.

  • alle alphanumerischen Zeichen
  • alle numerischen Zeichen
  • alle Grossbuchstaben
  • alle Kleinbuchstaben
  • alle Wortzeichen
  • alle Zahlen von 0-5
  • selbsdefinierte Muster-Klassen
  • ... usw.

Einige Zeichen haben auch eine besondere Bedeutung innerhalb von RegEx, mit diesen "Sonderzeichen" kann man Muster qualifizieren, etwa:

  • alle Zahlen die am Anfang einer Zeile stehen
  • das n-fache vorkommen des Zeichen y
  • alle Leerzeichen die am Ende einer Zeile stehen
  • ...usw.

Das ist es, was RegEx so mächtig macht und sie von "normalen" Such- oder Teil-String Funktionen wie strstr() oder substr() untertscheidet. Allerdings sollte man immer wenn man diese Machtigkeit der RegEx nicht benötigt die "normalen" String Funktionen verwenden, da diese sehr viel schneller als RegEx Funktionen sind.

Ein Perl-kompatibler Regex ist wie folgt aufgebaut.

Delimiter + Regex + Delimiter [+ Modifiers]

Der Delimiter (Begrenzer) gibt das Trennzeichen an, durch das der RegEx eingeschlossen und von den Modifiers getrennt wird. Dieses Trennzeichen muss ein nicht Alphanumerisches-Zeichen sein, darf also kein Buchstabe und keine Zahl sein. Man benutzt z.B. die Zeichen # oder das in Perl übliche Zeichen /. Man sollte ein Delimiter wählen, den man nicht im Regex selbst benutzt, da man sonst dieses Zeichen im eigentlichem Regex immer escapen muss.

Zwischen den Delimiter Zeichen kommt dann die eigentliche Regex. Dies kann ein sehr komplexer Ausdruck, aber auch nur ein Zeichen sein.

Nach dem 2. Delimiter kommen die optionalen Modifier. Mit diesen Modifiern kann man noch die Eigenschaften der RegEx "einstellen", z.B. auf caseinsensitiv (Gross und Kleinbuchstaben werden nicht unterschieden).

Aber fangen wir erst mal ganz klein an.

preg_match("/@/","test@example.org");

würde ein WAHR zurückgeben, da die RegEx "@" (das Zeichen "@") in der Zeichenkette "test@example.org" enthalten ist.

$email="test@example.org";
preg_match("/@/",$email);

und kann auch direkt als Bedingung in einem if-Konstrukt verwendet werden:

if (preg_match("/@/","test@example.org")) {

   echo "es scheint sich um eine Mail-Adresse zu handeln";

}

Für raffinierteres Suchen und komplexere Ausdrücke stehen aber noch die oben angesprochenen Sonderzeichen zur Verfügung. [1]

.  Der Punkt steht im Normal Fall für irgend ein Zeichen ausser
   Zeilenumbrueche, also nicht für \n und/oder \r, dies kann man mit einem
   Modifier ändern, doch dazu später mehr.
^  Anfang des Strings
$  Ende des Strings

^Stuttgart    ... true wenn der String mit "Stuttgart" beginnt.
Stuttgart$    ... true wenn der String mit "Stuttgart" endet.

| Oder Verknüpfung

jg|jens       ... true wenn der String "jg" ODER "jens" enthält.

Häufigkeitsangaben:

"ab{2}"   nach dem a müssen genau 2 b's vorkommen ("abb")
"ab{2,}"  nach dem a müssen mindestens 2 b's vorkommen ("abb", "abbb", ...)
"ab{3,5}" nach dem a müssen mindestens 3 aber maximal 5 b's vorkommen ("abbb",
          "abbbb", oder "abbbbb")


"ab*"  * bedeutet 0 oder mehrmals ("a", "ab", "abbb", ...)
"ab+"  + bedeutet 1 oder mehrmals ("ab", "abb", "abbb",...)
"ab?"  ? bedeutet 0 oder 1 mal ("a" oder "ab")

Mit runden Klammern kann man Sub-Gruppen bilden, die man dann einzeln mit Eigenschaften auszeichenen kann.

"a(bc)*"      passt also auf "a", "abc", "abcbc", "abcbcbc", ...
"a(bc){1,2}"  passt auf "abc" oder "abcbc"

diese Sub-Gruppen kann man beliebig schachteln (was die Lesbarkeit natürlich enorm erhöht! :-)

"a((bc)+d*)+" matcht auf "abc" oder "abcd" oder "abcdbc", ...

Dann gibt es noch die Escape Sequenzen für die üblichen Sonderzeichen in Strings :

\n  newline
\r  carriage return
\t  tab
...

Für immer wieder kehrende Zeichenklassen gibt es auch noch spezielle Escape Sequenzen:

\d  jede Zahl
\D  jedes Zeichen das keine Zahl ist
\s  Jedes Whitespace Zeichen (newline, carriage return, tab)
\S  Jedes NICHT Whitespace Zeichen
...

Des Weiteren kann man auch Klassen von Zeichen selber definieren in dem man diese in eckige Klammern einschliesst:

[a-z]  alle Kleinbuchstaben
[A-Z]  alle Grossbuchstaben
[0-9]  alle ganzen Zahlen
[+_-=] ein + oder ein _ oder ein - oder ein =
...

Könnt ihr noch? Ein wenig müssen wir noch weiter.

Und dann gibt es noch die weiter oben schon angesprochenen Modifier. [2]

Mit diesen kann man die Eigenschaften der RegEx "einstellen".

i  macht den Ausdruck caseinsensitiv
s  bewirkt, dass der Punkt . auch newlines einschliesst, das benötigt
   man, wenn man mal mehrzeiligen Text nicht zeilenweise mit RegEx
   bearbeiten will.
...usw.

Beispiel:

"/jens/"    matcht auf "jens" aber nicht auf "Jens"
"/jens/i"   matcht auf beides

Und zu guter letzt nochmal das etwas komplexere Beispiel vom Anfang, dass so in der Art immer wieder vorkommt: 
die Prüfung ob eine Eingabe dem Syntax einer gültigen Mail-Adresse entspricht.

$input = 'test@example.org';

if (preg_match("/^[a-zA-Z0-9]+((\.|-|_)[a-zA-Z0-9]+)*@([a-zA-Z0-9-]+\.)+([a-zA-Z]{2,4})$/", $input)) {
   echo "ja, das ist eine Mail-Adresse";
}
else {
   echo "sieht nicht nach einer Mail-Adresse aus";
}

Alles klar :-)

Ich denke als Einstieg reicht das hier mal...

Ich werde in einem weiteren Artikel das Beispiel zur Prüfung der Mail-Adresse genauer in seinen Einzelteilen besprechen.

$Id: regex1.xml,v 1.4 2005/02/24 12:42:44 jg Exp $

Hinweise:

  • Die Artikel sind hier noch aus historischen Gründen online.
  • Die Artikel sind teilweise nicht mehr aktuell, da auf PHP4 basierend.
  • In den Artikel werden die Sicherheits-Gesichtspunkte zu den einzelnen Themen nicht behandelt.
  • In den Seminaren bekamen die Teilnehmer zu den Themen jeweils weitere Informationen die nicht online verfügbar sind.

« Einbinden mit include() toc Regex2 Tips zu RegEx »