Author: Progman, zuletzt bearbeitet von progman @ 2004/06/10 08:59:45
Bitte beachten Sie, dass die Tutorialkapitel zusammenhängen. Wenn sie direkt auf ein Kapitel verlinkt wurden müssen Sie gegebenenfalls die vorherigen Kapitel auch lesen. Achten Sie beim lesen darauf, dass Sie kein Kapitel überspringen.
Reguläre Ausdrücke
- Was sind Reguläre Ausdrücke
- Aufbau von PCRE
- Wofür braucht man Regex?
- Regex in PHP benutzen
- Suche nach einem Teilstring
- Alternative Auswahl
- Zusammenfassen und Teileigenschaften bilden
- Zugriff auf Ergebnisse von subpattern
- Sammelgruppen
- Wiederholungen von Regulären Ausdrücken
- Regex haben hunger
- String Anfang und Ende
- Die Kunst des Regex
1. Was sind Reguläre Ausdrücke
Wenn wir uns die 2 Wörter Reguläre Ausdrücke angucken können wir uns erstmal nix darunter vorstellen. Nachdem wir im Duden das Wort regulär nachgeschlagen haben stellen wir fest, dass regulär für der Regel entsprechend steht. Ein Regulärer Ausdruck ist also ein Regel entsprechender Ausdruck. In PHP muss man es aber von der anderen Seite sehen, ein Ausdruck der eine Regel definiert.
Es gibt 2 Regex-Engines (Regex ist die Kurzform von "Regulärer Ausdruck"). Einmal ist es die POSIX-Engine und die Perl-Kompatible-Regex-Engine, kurz PCRE, die ca. 200mal schneller ist als die POSIX-Engine. Deswegen werden wir auch nur die PCRE-Engine benutzen. POSIX ist aber so ähnlich aufgebaut wie PCRE.
2. Aufbau von PCRE
Ein Perl-Regex ist wie folgt aufgebaut.
Delimiter + Regex + Delimiter [+ Modifiers]
Der Delimiter gibt das Trennzeichen an von der der Regex von den Modifiers getrennt werden. Dieses Trennzeichen muss ein nicht Alphanumerisches-Zeichen sein, darf also kein Buchstabe und keine Zahl sein. Man benutzt z.B. die Zeichen # und die in Perl üblichen Zeichen /. Man sollte ein Delimiter wählen, den man nicht im Regex benutzt, da man sonst dieses Zeichen im eigentlichem Regex escapen muss.
Nach dem Delimiter kommt dann der eigentliche Regex. Dieser Regex bestimmt dann wie der übergeben Ausdruck aussehen soll (üblichweise ein String). Dies kann ein sehr komplexer Ausdruck sein, kann aber auch nur ein Zeichen sein.
Nach dem Regex kommt wieder der vorher gewählte Delimiter. Das symbolisiert das Ende des gesamten Regex.
Nach dem 2. Delimiter kommen die Modifier. Mit diesen Modifier kann man noch den Regex ein wenig einstellen. Modifier werden dabei in einfache Buchstaben angegeben. Jeder Buchstabe steht für eine Eigenschaft für den Regex. Diese Angabe ist Optional.
3. Wofür braucht man Regex?
Hier sind ein paar Anwendungsbeispiele wo man Regex gebrauchen könnte.
-
Regex kann man zum Beispiel benutzen um ein Eingabefeld auf Gültigkeit zu prüfen. Dies macht man z.B. bei einer Eingabe einer Emailadresse oder Spielernamens.
-
Manchmal möchte man aus einem fertigen String, z.B. von einer anderen Seite, bestimmte Informationen auslesen. Diese kann man dann in seinem Script weiter verarbeiten. Wenn möglich sollte man aber sscanf benutzen. Man sollte nicht immer die Regex-Engine anwerfen.
-
Mit Regex kann man auch Teilstrings in einem String ersetzen lassen die man nicht ohne weiteres mit str_replace ersetzen lassen kann. Für Smilies braucht man z.B. kein Regex, die kann man einfach mit str_replace in die Bilder umwandeln.
4. Regex in PHP benutzen
Um auf einen String ein Regex anzuwenden benutzt man die Funktion preg_match. Als 1. Parameter gibt man den Regex an und als 2. Parameter den String den man untersuchen möchte. Den Regex sollte man in einfachen Anführungszeichen schreiben, damit man nicht wild escapen muss. Ein Beispiel könnte so aussehen.
<?php
error_reporting(E_ALL);
if(preg_match('/foobar/', $_POST['text'])) {
echo "Regex matched";
} else {
echo"Regex hat nicht gepasst";
}
?>
Unter Linux kann man das Programm pcretest benutzen um die hier besprochenden Regexe zu testen. Wenn man keine Linuxkiste hat, kann man auch folgendes PHP-Script verwenden.
<?php
error_reporting(E_ALL);
$regex = 'hier der regex';
if(isset($_POST['eingabe'])) {
echo "Eingabe:<br />\n";
echo "<pre>\n";
echo $_POST['eingabe'];
echo "</pre>\n";
if(preg_match($regex, $_POST['eingabe'])) {
echo "<span style=\"color: #008000\">passt</span><br />\n";
} else {
echo "<span style=\"color: #FF0000\">passt nicht</span><br />\n";
}
}
echo "Regex:<br />\n";
echo "<pre>\n";
echo $regex;
echo "</pre>\n";
echo "<form action=\"regex.php\" method=\"POST\">\n";
echo " <input type=\"text\" name=\"eingabe\" />\n";
echo " <input type=\"submit\" name=\"testen\" />\n";
echo "</form>\n";
?>
5. Suche nach einem Teilstring
Wir fangen jetzt erstmal ganz klein an. Wir suchen erstmal einen Sring im String. Wir haben folgenden Regex.
/foobar/
Hier sieht man das der Delimiter / benutzt wurde. Dieser Regex passt auf jeden Text wo der String foobar vorhanden ist. Dieses 'passen' nennt man auch matchen, ein eingedeutsches Wort von match, daher auch der Funktionsname preg_match. Hier einige Beispiele, als Testprogramm wurde pcretest verwendet.
re> /foobar/ data> ein String No match data> foobar 0: foobar data> ein String mit foobar 0: foobar data> rfunrufneifufoobaroevrneurvn 0: foobar data> foobaR No match data> FOOBAR No match
Dieses re> ist Teil von pcretest. Dort gibt man dann den Regex ein, hier /foobar/. Bei data> kann man dann eingeben, was man prüfen möchte. Was diese 0 erstmal bedeutet lassen wir außen vor. Hier sieht man das im 1. String der Teilstring nicht gefunden wurde. In den letzen beiden Teststrings steht zwar auch foobar, doch der Regex matched nicht. Das liegt daran das Regex ohne Einstellungen Case-Sensitiv sind, dass heißt das Groß- und Kleinschreibung beachtet wird. Damit der Regex darüber hinwegsieht muss man den Modifier i benutzen. Das i kommt dabei von case-insensitiv.
re> /foobar/i data> foobar 0: foobar data> FOOBAR 0: FOOBAR data> FoObAr 0: FoObAr
Dieses i gilt dabei für den kompletten Regex. Wenn man nur einen bestimmten Teil case-insensitiv matchen möchte so muss man diesen Teil in Klammern setzen, falls schon nicht vorhanden, und ein (?i) nach der Klammer auf schreiben. Aber zu dieser Schreibweise kommen wir später.
Wenn man nur nach einem Teilstring suchen möchte muss man nicht ein Regex benutzen. Es reicht auch wenn man Funktionen wie strstr, stristr oder substr_count benutzt. Diese sind um einiges schneller als ein Regex, da diese nicht die Regex-Engine anwerfen müssen, nur um zu gucken ob ein String in einem anderen String ist.
6. Alternative Auswahl
Manchmal wollen wir ein String matchen der nur aus bestimmten Teilstrings bestehen darf. Eine Alternative Auswahl erstellt man mit dem Zeichen |.
/foo|bar/
Dieser Regex passt z.B. nur auf Strings, die foo oder bar enthalten. Die Auswahl kann natürlich erweitert werden.
/foo|bar|bla|blo/
Dieser Regex matched z.B. auf einen String, wo ein Teilstring einer dieser 4 Möglichkeiten sein muss.
re> /foo|bar|bla|blo/ data> roivnwev No match data> foobar 0: foo data> blablo 0: bla data> blobla 0: blo data> rfed No match data> ble No match data> fobablarfgblalroblaoloablrooa 0: bla
Dies ist so als würde man folgenden PHP Code benutzen.
<?php
// ...
if(substr_count($var, "foo") OR
substr_count($var, "bar") OR
substr_count($var, "bla") OR
substr_count($var, "blo")) {
// ...
?>
7. Zusammenfassen und Teileigenschaften bilden
Bei einem etwas komplexeren Regex kann es vorkommen, dass man einen Teilregex eine bestimmte Eigenschaft geben muss die die anderen Teilregexe nicht haben sollen. Ein Modifier könnte man nicht mehr benutzen, denn der wirkt sich ja auf den ganzen Regex aus.
Um einen Teilregex anderen Eigenschaften zu vergeben muss man diesen erstmal in runden Klammern setzen. Am Anfang nach der Klammer kann dann folgender Ausdruck kommen.
(?...)
Da wo jetzt die Punkte stehen können dann bestimme Zeichen wie Modifier stehen, aber auch andere Zeichen, zu denen wir später kommen.
Ein Beispielregex könnte so aussehen.
/((?i)foo)bar/
Dieser Regex matched auf folgenden Ausdruck: Ein String foo, der Case-insensitiv ist, und einem danach kommenden String bar welcher normal, also Case-sensitiv geschrieben werden muss. Hier sind einige Beispiel Teststrings.
re> /((?i)foo)bar/ data> foobar 0: foobar 1: foo data> FOObar 0: FOObar 1: FOO data> fooBAR No match data> FoObar 0: FoObar 1: FoO data> blaBLAFOObarbla 0: FOObar 1: FOO
Hier sieht man erstmal, dass in der Variante, wo bar großgeschrieben wurde, der gesammte Regex nicht gematched hat. In allen anderen Versuchen passte der Regex. Desweiteren ist diese Ausgabe noch etwas anders. Es gibt nun eine neue Zeile, die mit der Zahl 1 startet. Diese Zahlen, die 0 und die 1, sind Rückreferenzen zu den gefundenen Teilstrings. Teilregexe die in Klammern stehen nennt man auch subpattern.
8. Zugriff auf Ergebnisse von subpattern
Wenn wir ein Teilregex in Klammern schreiben, so können wir in PHP, und auch noch im Regex selber, auf den Wert welcher dort gematched wurde, zugreifen. Mit diesen Linux-Testprogramm pcretest können wir uns die Werte ausgeben lassen, die der Teilregex dort gematched hat. Je nach dem wieviele Klammern und Teilregexe wir benutzt haben steigt auch die Zahl, die bei 1 startet. Die Referenz zur Zahl 0 enthält den Wert des ganzen Regex. Dieser ist auch immer vorhanden, und kann auch in PHP ausgelesen werden. Zu Testzwecken sollten wir deshalb unser PHP-Script (für die Leute die kein pcretest haben) etwas umschreiben.
Der Funktion preg_match kann man einem 3. Parameter übergeben, welche eine Variable sein muss. Nach dem matchen stehen dann die Ergebnisse des Regex in Form von einem Array zu Verfügung. Arrayindex 0 ist dabei der gesammte gematchte Ausdruck und die anderen Arrayindize sind die entsprechende Teilausdrücke. Das Script passen wir wie folgt an:
<?php
error_reporting(E_ALL);
$regex = 'hier der regex';
if(isset($_POST['eingabe'])) {
echo "Eingabe:<br />\n";
echo "<pre>\n";
echo $_POST['eingabe'];
echo "</pre>\n";
if(preg_match($regex, $_POST['eingabe'], $matches)) {
echo "<span style=\"color: #008000\">passt</span><br />\n";
echo "<pre>\n";
foreach($matches as $key => $value) {
echo $key." :".$value."\n";
// Kein <br />, da wir im <pre>-Element schreiben
}
echo "</pre>\n";
} else {
echo "<span style=\"color: #FF0000\">passt nicht</span><br />\n";
}
}
echo "Regex:<br />\n";
echo "<pre>\n";
echo $regex;
echo "</pre>\n";
echo "<form action=\"regex.php\" method=\"POST\">\n";
echo " <input type=\"text\" name=\"eingabe\" />\n";
echo " <input type=\"submit\" name=\"testen\" />\n";
echo "</form>\n";
?>
Wenn wir nicht wollen, dass ein Teilregex durchnummeriert wird, wir aber auf die Klammern nicht verzichten können, müssen wir nach der Klammer auf ein ?: schreiben. Dann kann wie üblich eine Teileigenschaft, z.B. (?i), kommen und dann muss wie üblich der Teilregex folgen. Ein Beispiel könnte so aussehen.
re> /(?:foo)bar/ data> foobar 0: foobar re> /(foo)bar/ data> foobar 0: foobar 1: foo
Im Regex kann man auf so einen Wert noch im ganzen Regex zugreifen. Auf ein Ergebnis von einem vorherstehenden Teilregex greift man mit \x zu, wobei das x die Nummer des Teilregex ist. 0 kann man nicht benutzen. Wie denn auch, denn der Wert ist bei der Abarbeitung noch nicht gesetzt. So eine Referenz auf ein Teilregex braucht man z.B. bei preg_replace. Hier erstmal ein normales Beispiel für eine Teilreferenz.
re> /((?i)foo)bar\1/ data> foobar No match data> foobarfoo 0: foobarfoo 1: foo data> fOObarfOO 0: fOObarfOO 1: fOO
Dieser, in der Tat schwachsinnige, Regex passt nur auf ein String, wo ein Teilstring mit foo startet (case-insensitiv) darauf dann bar folgt und dann wieder der selbe erste Ausdruck folgen muss, der in der Klammer gematched wurde. In PHP ist \ normalerweise das Escape-Zeichen. Da wir aber den Regex immer in ' schreiben, wird dieses Zeichen nicht als Escape-Zeichen verwendet (außer bei \\) sondern wird in den String als solches gespeichert. Somit bekommt die Regex-Engine dieses Zeichen und kann entsprechend darauf reagieren.
9. Sammelgruppen
Wir haben nur nach festen Stringketten gesucht. Wenn wir aber z.B. nach einer Zahl suchen wollen müssten wir folgenden Regex benutzen.
/0|1|2|3|4|5|6|7|8|9/
Oder wenn wir nach Vokalen suchen möchten, müssten wir diesen Regex benutzen.
/(?i)a|e|i|o|u/
Es ist aber möglich, Zeichenklassen zu erstellen. Diese erstellt man mit den eckicken Klammern. Innerhalb der eckicken Klammern kommen die Zeichen rein, die diese Zeichenklasse matchen soll.
/[aeiou]/
Dieser regex matched auf ein kleines Vokal in einem String.
re> /[aeiou]/ data> foobar 0: o data> bla 0: a data> BLA No match
Wenn man ein Zeichen matchen möchte, welches nicht in dieser Zeichenklasse vorhanden ist, so muss man nach der eckicken Klammer-Auf ein ^ schreiben.
re> /[^aeiou]/ data> foobar 0: f data> bla 0: b data> BLA 0: B data> a No match
Wenn man ^ in einer Zeichenklasse benutzen möchte, muss man entweder dieses Zeichen escapen oder es nicht an erster Stelle schreiben. Das gleiche gilt auch für die Zeichen [ und ], da diese sonst zu früh die Zeichenklasse beenden würden.
In den Zeichenklassen kann man auch sowas wie von-bis benutzen. Dies könnte dann so aussehen:
re> /[1-3][5-6]/ data> 15 0: 15 data> 20 No match data> 36 0: 36 data> 24 No match re> /[a-e][F-J]/ data> aF 0: aF data> dH 0: dH data> er No match
Beachten sie das solche von-bis Konstruktionen Zeichenweise geht. Ein Ausdruck wie [0-24], um ein Uhrzeit zu erfassen, wird nicht gehen. Diese Zeichenklasse würde die Zahlen 0,1,2 oder 4 erfassen, aber nicht solche Zahlen von 3 und 5 bis 24.
PCRE kennt eine Menge von vordefinierten Zeichenklassen. Diese müssen nicht unbedingt extra in eine Zeichenklasse mit [] geschrieben werden, es sei denn man möchte solche vordefinierten Zeichenklassen zusammenpacken.
-
\d - Das d von \d steht dabei für digit und bedeutet Ziffer/Zahl. Ein \d im Regex matched genau eine Ziffer von 0-9. Auf allen anderen Zeichen matched es nicht.
re> /\d/ data> foo No match data> 123 0: 1
Dies ist gleichbedeutent mit dem Regex [0-9].
-
\D - Das D steht auch wieder für digit aber dieser Regex matched auf alle Zeichen, die keine Ziffer sind, inklusive einem Zeilenumbruch \n.
re> /\D/ data> \n 0: \x0a data> foo 0: f data> 123 No match data> 123foo 0: f
Dies ist gleichbedeutent mit dem Regex [^0-9].
-
\w - Das w steht für word und hat in Perl eine bestimmte Bedeutung. Es matched auf Buchstaben (Groß- und Kleinschreibung), auf Ziffern und den Unterstrich _.
re> /\w/ data> foo 0: f data> 123 0: 1 data> $_GET 0: _ data> \n No match
Dies ist gleichbedeutent mit dem Regex [0-9a-zA-Z_] (Zahlen von 0-9, Buchstaben von a-z, Buchstaben von A-Z und den Unterstrich).
-
\W - Dies matched genau auf die Zeichen, die in Perl nicht ein word entsprechen.
re> /\W/ data> foo No match data> 123 No match data> _- 0: - data> $_GET 0: $
Dies ist gleichbedeutent mit dem Regex [^0-9a-zA-Z_].
-
\s - Das s kommt vom Wort whitespace und matched auf Zeichen, die man auf den ersten Blick nicht sieht. Das sind das Leerzeichen (space, 0x20), der Zeilenumbruch (new line, \n = 0x0A), der Wagenrücklauf (carriage return, \r = 0x0D), der Seitenvorschub (form feed, \f = 0x0C), der Tabulator (\t = 0x09) und den Vertikalen Tabulator (vertical tabulator, \v = 0x0B). Alle anderen Zeichen werden nicht gematched.
re> /\s/ data> foo No match data> foo bla 0: data> foo\nbla 0: \x0a data> foo\tbla 0: \x09
Beim 2. Test wurde das Leerzeichen gematched, welches hier natürlich nicht sichbar ist.
-
\S - Das S kommt auch wieder von whitespace, matched aber all die Zeichen die keine whitespaces sind.
re> /\S/ data> foo 0: f data> \n No match data> \n\t\v\nfoo 0: f
Dies ist gleichbedeutent mit [^\s] bzw [^\n\r\f\t\v ].
-
. - Der einfache Punkt steht für ein beliebiges Zeichen außgenommen der Zeilenumbruch \n. Damit der Punkt auch auf den Zeilenumbruch passt muss der Modifier s benutzt werden, entweder global im ganzen Regex hinter dem 2. Delimiter oder nur bezogen auf den Punkt mit der Angabe (?s). Den Punkt braucht man um ein beliebigen String zu catchen.
re> /./ data> foo 0: f data> \nf 0: f data> \n No match re> /./s data> foo 0: f data> \nf 0: \x0a data> \n 0: \x0a
Da das \n nicht dargestellt werden kann, erzeugt das Programm pcretest die Hex-Schreibweise des Zeichens, hier 0x0A.
Anders als bei den anderen Zeichenklassen kann der Punkt nicht in [] verwendet werden. Dort verliert er dann seine Funktion und gilt als normalen Punkt.
re> /./ data> foo 0: f re> /[.]/ data> foo No match data> foo. 0: .
Hier nochmal in Kürze was welche Zeichenklasse matched.
\d = [0-9] \D = [^0-9] = [^\d] \w = [0-9a-zA-Z_] = [\da-zA-Z_] \W = [^0-9a-zA-Z_] = [^\da-zA-Z_] = [^\w] \s = [ \n\t\r\v\f] \S = [^ \n\t\r\v\f] = [^\s] . = [^\n] (ohne Modifier s) . = [\x00-\xFF] ,also alle Zeichen (mit Modifier s)
10. Wiederholungen von Regulären Ausdrücken
Bestimmte Teile eines Regex kann man wiederholen lassen. Dies macht man um z.B. eine Zahl aus 10 Ziffern zu matchen. Normalerweise würden wir das so machen.
/\d\d\d\d\d\d\d\d\d\d/
Dies kann man natürlich zusammenfassen. Um ein Regex-Teil um eine bestimmte Anzahl wiederholen zu lassen, muss man diesen ggf. erstmal in Klammern schreiben. Danach kommt dann ein Ausdruck in geschweiften Klammern. Innerhalb dieser geschweiften Klammern kommt dann die Zahl hin, wie oft dieser Teil-Regex wiederholt werden soll, damit der Regex passt. In diesem Fall eine 10.
re> /\d{10}/ data> 48957 No match data> 4373648735683467 0: 4373648735 data> foo134frrf324234bla1234567890jonv 0: 1234567890
Versucht nicht eine 0 dort reinzuschreiben, also {0}. Dies wird nicht so gehen wie man es vielleicht möchte. Dazu gibt es andere Techniken.
Statt einer festen Anzahl von Wiederholungen kann man auch einen Teilregex variable wiederholen lassen. Dazu gibt man in den geschweiften Klammern 2 Zahlen mit Komma getrennt an. Die linke Zahl steht für das Minimum der Wiederholungen und die rechte Zahl für das Maximum der Wiederholungen. Ein Beispiel könnte so aussehen.
re> /\d{3,7}/ data> foo324berv 0: 324 data> 23 No match data> 1234535 0: 1234535 data> 12345678 0: 1234567
Wenn man die Maximumangabe weglässt, also sowas wie {4,} schreibt, dann gibt es keine obere Grenze.
re> /f{5,}/ data> ffff No match data> fffff 0: fffff data> 4ffff4ffff4 No match data> ffffffffffffffffffffffffffffffff 0: ffffffffffffffffffffffffffffffff
Es gibt auch bestimmte Zeichen, die für bestimmte Minimum/Maximum-Grenzen stehen. Diese Zeichen wird auch in anderen Bereichen im Internet finden, wie bei den DTDs und RFC-Seiten.
? = {0,1} * = {0,} + = {1,}
Hier ein Beispiel.
re> /\d+[a-z]*/ data> 45245 0: 45245 data> 324uiu345bniu325 0: 324uiu data> uon No match
Dieser Regex matched mindesdens eine Zahl. Danach können dann Buchstaben kommen
11. Regex haben hunger
Nehmen wir an wir wollen in einem Text alle URLs auslesen, und ggf. dann auch noch ersetzen, die in [url] [/url] stehen. Sowas kennt man von diversen Foren. Der dort stehende Text soll dann in einen HTML-Link umgewandelt werden. Um den Text aus diesen beiden Elementen zu erfassen benutzen man normalerweise folgenden Regex.
=\[url\](.*)\[/url\]=
Hier wurde das Gleichheitszeichen als Delimiter gewählt. Das habe ich deshalb gemacht, weil ich / selber als Regexteil verwende. Damit der Regex die Zeichen [ und ] nicht als Zeichenklassen erkennen müssen diese Zeichen escaped werden. Die Funktion preg_quote dient dazu, einen übergeben String entsprechend zu escapen, damit PCRE sie nicht als PCRE-Steuerzeichen erkennt.
Wir nehmen nun mal ein Beispiel Text zum matchen.
re> =\[url\](.*)\[/url\]= data> Weitere Infos auf [url]http://tut.php-q.net/[/url]. 0: [url]http://tut.php-q.net/[/url] 1: http://tut.php-q.net/
Da wir hier das .* in Klammern geschrieben haben, können wir dann später in PHP und im Regex darauf zugreifen (Index 1). Wir werden dann aber Probleme bei einem längeren Text mit 2 URLs haben.
re> =\[url\](.*)\[/url\]= data> Infos zu PHP auf [url]http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/[/url]. 0: [url]http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/[/url] 1: http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/
Da wo die 1 steht sieht man, dass nicht nur die URL gematched wurde, sondern auch noch viel mehr Text. Dabei hat der Regex das letze [/url] als Ende erkannt und matched somit alle anderen [url]-Element mit, obwohl uns das später stören würde. Denn dann würde ein Link wie <a href="http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/">http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/</a> erzeugt werden, der natürlich nicht funktionieren kann. Der Teil .* matched nicht so viel wie er braucht, sondern so viel wie er kann. Diesen Effekt nennt man gierig bzw. gefräßig und heißt greedy. Regexe wie * oder + versuchen so viel zu erfassen wie nur möglich. Dies kann in manchen Situationen, wie diese hier, unerwünscht sein. Es gibt diverse Techniken um diesen Effekt gegenzutreten.
-
Wir benutzen den Modifier U, von Ungreedy. Damit matched ein Teilregex wie .* nur so viel, wie er braucht, damit der ganze Regex gültig ist. Wenn wir diesen Modifier setzen gilt er für den ganzen Regex. Hier ein Beispiel:
re> /.+/ data> matched alles 0: matched alles re> /.+/U data> matched so wenig wie möglich 0: m
Im ersten Regex, ohne U, matched er soviel er kann. Er versucht, das maximalste rauszuholen. Im zweiten Regex hingegen erfasst er so wenig wie möglich, bei einem .+ ist das ein Zeichen, hier der erste Buchstabe m.
Der Regex mit der URL könnte nun so aussehen:
re> =\[url\](.*)\[/url\]=U data> Infos zu PHP auf [url]http://tut.php-q.net/[/url] und natürlich im PHP Manual [url]http://www.php.net/[/url]. 0: [url]http://tut.php-q.net/[/url] 1: http://tut.php-q.net/
-
Man kann auch das Suchmuster des Regex etwas einschränken. Das Metazeichen Punkt akzeptiert ja beliebige Zeichen (von \n abgesehen). Wir könnten also sagen, dass er nur nicht-Whitespaces benutzen soll (\S*), da eine URL aus keinem Leerzeichen oder so besteht. Wir können aber auch sagen, dass er alle Zeichen außer [ nehmen soll ([^\[]+), denn dann würde er vor dem schliessenden [/url] aufhören. Oder man kombiniert beides, damit auch kein Leerzeichen und so in der URL stehen darf ([^\s\[]*). Beispiel:
re> /.*/ data> ein text mit Leerzeichen 0: ein text mit Leerzeichen re> /\S+/ data> ein text mit Leerzeichen 0: ein data>
Wir können aber auch ganz anders daran gehen und nicht sagen, was er nicht matchen soll, sondern sagen ihm, was er matchen darf, um etwas mehr Kontrolle zu haben. Der fragliche Regex-Teil könnte dann so aussehen.
([\w$-.+!*'()@:?=&/;]*)
Das darf alles in eine URL stehen. Nachlesbar in RFC 1738. Damit sagen wir welche Zeichen er nehmen darf. Die Zeichen " und § z.B. darf er nicht nehmen und die Whitespaces wie Leerzeichen und Tabulator schon garnicht. Beispiel:
re> /.*/ data> 123abc456 0: 123abc456 re> /[a-z3]+/ data> 123abc456 0: 3abc re> §\[url\]([\w$-.+!*'()@:?=&/;]*)\[/url\]§ data> foo[url]http://foobar/[/url]bar 0: [url]http://foobar/[/url] 1: http://foobar/ data> [url]url[/url]bla[url]foobar[/url] 0: [url]url[/url] 1: url
Das § ist zwar ein ungewöhnlicher Delimiter, doch ich konnte die üblichen nicht nehmen, ohne die Zeichen in der Zeichenklasse zu escapen.
-
Eine andere Möglichkeit, einen Teilregex Ungreedy zu machen ist es, hinter dem Ausdruck ein ? zu schreiben, also sowas wie (.*?). Damit wird die aktuelle Einstellung von Greedy/Ungreedy umgestellt. Wenn der gesammte Regex ungreedy ist, so wird dieser Regexteil wieder greedy und umgekehrt. Beispiel:
re> /.+/ data> alles rein was geht 0: alles rein was geht re> /.+?/ data> alles rein was geht 0: a
In diesem Fall:
re> =\[url\](.*?)\[/url\]= data> [url]http[/url]andere[url]url[/url] 0: [url]http[/url] 1: http
12. String Anfang und Ende
Regex können wir auch benutzen um Benutzereingaben zu validieren, also um zu gucken ob sie einen sinnvollen und gültigen Inhalt haben. Da haben wir aber im moment ein Problem. Die Regexe, die wir momentan schreiben, passen nur auf ein Teiltext, nicht auf den gesammten Text. Dies zeigt folgender Regex.
re> /[a-z]+/ data> 7823467823grfre345245 0: grfre
Wir wollten eigentlich ein Regex haben, der prüft, dass die Eingabe nur Buchstaben enthält. Doch dieser Regex findet zwar den Text, aber das davor und danach ist ihm scheiss egal. Im Regex gibt es aber die Möglichkeit dass der Regex von Anfang an und/oder bis zum ende gelten muss. Das Zeichen für den Anfang der Eingabe ist das Hochzeichen ^.
re> /^foo/ data> blafoo No match data> foo 0: foo data> bla\nfoo No match
Wenn man den Modifier m (von multiline) benutzt matched das Hochzeichen auf die Stelle nach einem Zeilenumbruch. Um genauer zu sein zwischen den Zeilenumbruch und den folgenden Zeichen.
re> /^foo/m data> blafoo No match data> foo 0: foo data> bla\nfoo 0: foo
Um ein Ende zu erfassen benutzt man das Zeichen $.
re> /bar$/ data> foo No match data> foobar 0: bar data> bar\nfoo No match
Mit den Modifier m kann dieses Zeichen auch ein Zeilenumbruch erkennen, und nicht nur ein Stringende.
re> /bar$/m data> foo No match data> foobar 0: bar data> bar\nfoo 0: bar data>
Mit diesen Wissen können wir nun eine richtige Eingabe-Überprüfung schreiben. Wir benutzen beide Zeichen um den Regex mehr einzuschränken.
re> /^[a-z]+$/ data> foobar 0: foobar data> 123 No match data> FOOBAR No match
Hier sollte man vielleicht noch den Modifier i verwenden.
13. Die Kunst des Regex
Dies ist nur ein Tutorialeintrag zu der Programmiersprache PHP. Es ist kein Ersatz für das lernen von Regex. Im PHP Manual liegt eine Mirror der PCRE-Manpage. Weitere Infos zu PCRE findet man unter http://www.php.net/pcre.
Fragen zum aktuellen Thema
- Was bedeutet gierig im Zusammenhang mit Regex?
-
Der Ausdruck, der mit * oder + auf eine unbestimmte länge erweitert wird, frist soviele Zeichen wie er nur kann. Dies kann in manchen Situationen unerwünscht sein. Um diese Effekt zu unterbinden kann man den Modifier U benutzen.
- Was macht der Modifier s?
-
Dieser Modifier matched bei dem Punkt auch noch einen Zeilenumbruch \n. Dies braucht man um einen mehrzeiligen Text mit ((?U).*) zu erfassen.
- Was matched /(bb|[^b]{2})/ ?
-
Dies ist keine ernsthafte Frage. Dieser Regex ist ein Scherz bezogen auf Shakespeare. Er ist die Regex schreibweise von "To be or not to be".
bb To be | or [^ not {2} to b] be