Bei O'Reilly gibt es das ausgezeichnete Buch Mastering Regular Expressions von Jeffrey E. F. Friedl, welches auch als deutsche Übersetzung unter dem Titel Reguläre Ausrücke vertrieben wird. Es enthält eine ausgezeichnete Übersicht über die verschiedenen Formen von regulären Ausdrücken in Unix mit Beispielen.
Online gibt es z.B. das Tutorial Rx (englisch) über Reguläre Ausdrücke nach POSIX-Standard (das sind die ereg()-Funktionen in PHP).
Die preg()-Funktionen von PHP sind praktisch 100% kompatibel zu den Regulären Ausdrücken in Perl - die ausführenden Funktionen heißen allerdings anders; insofern eignet sich jede Anleitung zu Perl Regular Expressions, wenn es nur um die Bedeutung der einzelnen Zeichen geht.
Wenn die verwendete Version von PHP ausreichend neu ist und das Modul PCRE aktiviert ist (dies kann man mit einem Aufruf von phpinfo() leicht feststellen), dann sollte man wo immer es geht die preg-Funktionen verwenden. Sie sind nicht nur schneller, sondern auch flexibler und leistungsfähiger als die alten ereg-Funktionen.
Es gibt keinen Grund mehr, die ereg-Funktionen noch zu verwenden außer Rücksicht auf veraltete Installationen.
Die preg()-Funktionen sind im Abschnitt Perl-compatible regular expressions des Online-Handbuches beschrieben. Es handelt sich um die Funktionen
Für alle Funktionen gilt das in den Abschnitten Pattern Modifiers und Pattern Syntax gesagte.
Reguläre Ausdrücke sind Suchmuster, die sich auf Strings anwenden lassen und für die entscheidbar ist, ob sie auf den String passen ( match ) oder nicht passen. So paßt das Suchmuster ei auf den String Weichei , weil darin die Zeichenfolge ei enthalten ist, aber nicht auf den String Warmduscher . Wendet man ein Suchmuster auf eine Menge von Strings an, dann bekommt man zwei Teilmengen, nämlich die Menge aller Strings, auf die das Muster paßt und die Menge aller Strings, auf die das Muster nicht paßt. Meistens interessiert man sich für eine der beiden Teilmengen ("Finde alle Namen, die mit einem einem A beginnen.", "Finde alle Zeichen, die nicht rechts von einem Kommentarzeichen stehen.")
Reguläre Ausdrücke werden meistens durch einen endlichen Automaten realisiert. Die Informatik kennt Verfahren, mit denen man automatisch einen Automaten generieren kann, der für ein bestimmtes Suchmuster entscheidet, ob es auf einen String paßt oder nicht. Auch die regulären Ausdrücke in PHP funktionieren so: Bei der ersten Benutzung eines regulären Ausdruckes wird ein solcher Automat intern generiert (das Muster wird "compiliert") und dann angewendet. Bei späteren Benutzungen desselben Suchmusters kann dieser Automat dann unter Umständen wieder verwendet werden, was deutlich schneller ist.
Einige Suchmuster und Bedingungen sind zu komplex, als dass man sie mit Hilfe von regulären Ausdrücken und Automaten formulieren kann. Typische Beispiele dafür sind Dinge, die Abzählungen erforderlich machen ("Finde alle Worte, die aus genausovielen b 's bestehen, wie sie a 's enthalten") und Dinge, die Vorbedingungen notwendig machen ("Finde alle Worte print , aber nur, wenn sie nicht in Anführungszeichen stehen oder Bestandteil eines Kommentares sind."). In diesem Fällen braucht man leistungsfähigere Konzepte und Werkzeuge, kontextfreie oder kontextsensitive Grammatiken und dazu passende Parser.
In der Praxis verwendet man reguläre Ausdrücke, um zu entscheiden, ob ein String bestimmten formalen Kritierien genügt ("Akzeptiere den Formularwert nur dann, wenn er ausschließlichlich Ziffern enthält.") oder um bestimmte Teilstücke aus Strings herauszuschneiden ("Liefere mit den Text zwischen dem begin und end aus dem gegebenen String.").
Suchmuster in regulären Ausdrücken bestehen aus gewöhnlichen Zeichen und Zeichen mit einer Sonderbedeutung. Gewöhnliche Zeichen in Suchausdrücken stehen für die entsprechenden Zeichen in einem String. Der Suchausdruck hallo paßt also auf alle Strings, die irgendwo genau diese Zeichenfolge enthalten. Zeichen mit Sonderbedeutung stehen als Platzhalter für ein oder mehrere andere Zeichen, für Zeilenanfänge oder -enden oder für andere Sonderfunktionen. Sie sind es, die reguläre Ausdrücke eigentlich mächtig und sinnvoll machen.
In regulären Ausdrücken gibt es Zeichenmengen (wird weiter unten erklärt), die durch eckige Klammern [ und ] eingeschlossen werden. Bei Zeichen mit Sonderbedeutung gelten leicht unterschiedliche Regeln, je nachdem ob man gerade eine außerhalb einer Zeichenmenge arbeitet oder innerhalb.
Außerhalb von Zeichenmengen gibt es die folgenden besonderen Regeln:
Der Backslash \ wird als Escape-Zeichen verwendet, mit unterschiedlichen Anwendungen:
Für Sonderzeichen (also alle nicht-alphanumerischen Zeichen) nimmt es den Zeichen ihre besondere Bedeutung, sodass man sie "wörtlich" in das Suchmuster einfügen kann. Das Suchmuster \* paßt also auf genau ein Sternchen.
Für alphabethische Zeichen hat es eine besondere Bedeutung ähnlich wie in der Programmiersprache C: \r steht für ein Return (ASCII 13), \n steht für ein Linefeed (ASCII 10) und so weiter. Die Zeichenfolgen \d , \D , \w , \W , \s und \S beschreiben bestimmte vordefinierte Zeichenmengen (siehe unten).
In Zeichenfolgen \xYY definiert es das Zeichen mit dem hexadezimalen Zeichencode YY . Ein \x0A steht also für ein Zeichen mit einem Zeichencode ASCII 10, Linefeed.
In Zeichenfolgen \YYY definiert es das Zeichen mit dem oktalen Zeichencode YYY . Ein \\012 steht also für ein Zeichen mit dem Zeichencode ASCII 10, Linefeed. Diese Schreibweise ist jedoch nicht empfohlen, weil sie sich mit einer anderen Schreibweise für sogenannte Backreferences (siehe unten) überlappt.
Eckige Klammern leiten eine Zeichenmenge ein. Eine Zeichenmenge steht immer für genau ein Zeichen und zwar für ein Zeichen, das in der Menge enthalten ist. Also paßt der reguläre Ausdruck [0123456789] auf genau eine beliebige Ziffer. Innerhalb der eckigen Klammern einer Zeichenmenge haben die folgenden Zeichen eine besondere Bedeutung:
Der Backslash \ gilt als Escapezeichen: Das ihm folgende Zeichen hat keine besondere Bedeutung, sondern steht nur für sich selbst.
Das Dach ^ als erstes Zeichen einer Zeichenmenge (aber nur als erstes Zeichen!) negiert die Zeichenmenge. Die Zeichenmenge [^0123456789] steht also für genau ein Zeichen, das keine Ziffer ist.
Das Minuszeichen - definiert einen Bereich. Die Zeichenmenge [0-9] ist also eine kürzere Schreibweise als [0123456789] für dieselbe Menge.
Die besonderen Zeichenmengen \d (eine dezimale Ziffer), \D (die Negation von \d ), \w (ein "Wort"-Zeichen), \W (die Negation von \w ), \s (ein "Whitespace"-Zeichen) und \S (die Negation von \s ) stehen für die jeweils angegebenen Zeichen.
Verwendet man einen Suchausdruck mit den preg*-Funktionen, dann ist der Suchausdruck in sogenannte Begrenzerzeichen ( Delimiter ) einzuschließen, hinter denen noch Optionen mit angegeben werden können. Meistens verwendet man entweder die Schrägstriche / oder Gleichheitszeichen = .
Aus diesen Komponenten kann man sich mit einiger Übung Suchausdrücke zusammenbauen, die nicht nur weitgehend unlesbar sind, sondern die außerdem schnell und schmerzlos die gewünschten Suchfunktionen oder Such- und Ersetzefunktionen durchführen.
Der einfachste Anwendungsfall von preg_match() ist zu testen, ob ein Suchmuster auf einen gegebenen String paßt. Beispiele:
# Kommt "Wort" in $eingabe vor?
preg_match("/Wort/", $eingabe);
# Kommt "Wort", "wort" oder "wOrT" etc. in $eingabe vor?
preg_match("/wort/i", $eingabe);
# Kommt "Wort" am Anfang oder am Ende von $eingabe vor?
preg_match("/^Wort|Wort$/", $eingabe);
# Kommt "Wort", "Wart", "Wirt" oder "Wert" in $eingabe vor?
preg_match("/W[aeio]rt/", $eingabe);
# Kommt "Wort" oder "Word" in $eingabe vor?
preg_match("/Wor(t|d)/", $eingabe);
# Kommt "DM " oder "TDM " mit einer zwei- bis
# dreistelligen Zahl in $eingabe vor?
preg_match("/T?DM \d{2,3}/", $eingabe);
# Kommt "Word " mit einer Versionsnummer (z.B. 7.0 oder 97)
# in $eingabe vor? (\d+ paßt auf "eine oder mehr" Ziffern,
# \.? KANN ein Punkt sein, \d* sind "Null oder mehr" Ziffern)
preg_match("/Word \d+\.?\d*/", $eingabe);
Das folgende vollständige Beispiel zeigt, wie man den Inhalt des Body-Tags aus einer HTML-Datei isolieren kann.
$str = "lalalala
<body bgcolor=#cccccc>lang und weilig
noch eine zeile
<h1>Bla</h1>
</body>
tralalal";
preg_match_all("=<body[^>]*>(.*)</body>=siU", $str, $a);
print $a[1][0];
Das Beispiel macht von den Optionen i , s und U der Perl Regular Expressions Gebrauch: Die Option i sorgt dafür, dass Groß- und Kleinschreibung keine Rolle spielen, die Option U sorgt dafür, dass Ungreedy gematched wird, d.h. der kürzest mögliche Match verwendet wird. Die Option s bewirkt, dass der Punktoperator auch Newlines mit matched. Dadurch ist es möglich, den regulären Ausdruck auf auf einen mehrzeiligen String anzuwenden.
$zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Diese Variable muss innerhalb der While-Schleife neu zusammengebaut werden, sonst läuft man hier in eine Endlosschleife.
$pattern = '=^(.*)<a(.*)href\="?(\S+)"([^>]*)>(.*)</a>(.*)$=msi';
while (preg_match($pattern, $zeile, $txt))
{
/* $txt[3] enthält die gewünschte URL. */
echo $txt[3]."\n";
/* $zeile neu bauen */
$zeile = $txt[1]." hier war mal ein Link ".$txt[6];
}
/* $zeile zur Kontrolle ausgeben */
print "<br>".nl2br($zeile);
$txt enthält als Array alle Tokens, die in der Regexp in Klammern angegeben sind. $txt[0] als Sonderstellung enthält den ganzen Text.
$zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Im folgenden Beispiel werden alle relativen Links durch das Konstrukt <?php echo $sess->purl("relativerlink"); ?> ersetzt. relativerlink sei hierbei der relative Link, der gefunden wurde.
$pattern = ',<a([^>]+)href="php/php-faq/static/%28%3F%21https%3F%3A//%7Cftp%3A//%7Cmailto%3A%7Cnews%3A%29%28%5B%5E%26gt%3B.html"\s]+)",i';
$replacement = '<a\1href="php/php-faq/static/%26lt%3B%3Fphp%20echo%20%24sess-%3Epurl%28.html"\2"); ?>"';
$newtext = preg_replace($pattern, $replacement, $text);
header('Content-type: text/plain');
echo $newtext;
Häufig ist es nötig, festzustellen, ob ein String nur Ziffern bzw. nur Buchstaben enthält.
$string sei die Zeichenkette, die überprüft werden soll. Die Regular Expression im ersten Beispiel überprüft, ob nur Ziffern in $string enthalten sind. Ist dies der Fall, gibt sie "Zeichenkette OK" aus, ansonsten lautet die Ausgabe "Ungültiges Zeichen in der Zeichenkette".
/* Regex zur Ueberpruefung des Strings */
if (!preg_match("/^\d+$/",$string)) {
echo "Ungültiges Zeichen in der Zeichenkette";
} else {
echo "Zeichenkette OK";
}
Um zu überprüfen, ob in der Zeichenkette nur Buchstaben stehen, kann man folgende Regex verwenden, die auf dem gleichen Prinzip beruht:
if (!preg_match("=^[a-zäöüß]+$=i",$string)) {
echo "Ungültiges Zeichen in der Zeichenkette";
} else {
echo "Zeichenkette OK";
}
Mit Regulären Ausdrücken kann man zwar wunderbar "positive Treffer" formulieren, aber das Gegenteil davon geht nur sehr schwer (abgesehen von negierten Zeichenklassen und Lookaheads/-behinds geht es nicht ). Die Entscheidung, ob ein Treffer innerhalb eines HTML-Tags (also zwischen < und > ) liegt oder nicht, muss man von PHP treffen lassen. Hierzu gibt es den Modifier e , der PHP das zweite Argument von preg_replace() als PHP-Code auswerten läßt.
Mit folgender Konstruktion kann man in $t alle Vorkommen von $s außerhalb von < und > durch $r ersetzen; die zweite Version ist besonders mit dem Modifier i interessant, um Wörter unabhängig von ihrer Groß-/Kleinschreibung unter Beibehaltung der Schreibweise hervorzuheben:
// $s in $t durch $r ersetzen:
preg_replace("/((<[^>]*)|$s)/e", '"\2"=="\1"? "\1":"$r"', $t);
// $s case-insensitive in $t hervorheben:
preg_replace("/((<[^>]*)|$s)/ie", '"\2"=="\1"? "\1":"<b>\1</b>"', $t);
Besten Dank an Thomas Weinert , von dem die ursprüngliche RegExp stammt.
Folgender regulärer Ausdruck ersetzt alle normalen URIs, das heißt zum Beispiel http://www.phpcenter.de/, news:de.comp.lang.php, mailto:bjoern@thinkphp.de oder ftp://ftp.suse.com/ durch HTML-Code, damit diese URIs für den Benutzer klickbar werden.
/**
* replace URIs with appropriate HTML code to be clickable.
*/
function replace_uri($str) {
$pattern = '#(^|[^\"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$)#sm';
return preg_replace($pattern,"\\1<a href=\"\\2\\3\"><u>\\2\\3</u></a>\\4",$str);
}
Setzt man in einem Regulären Ausdruck sogenannte Quantifier ( ?, *, + und {n,m} ) ein, weil ein Teil des Ausdrucks in variabler Anzahl vorkommen darf, so versuchen diese grundsätzlich, so viele Zeichen wie nur möglich zu "fressen" (ohne dabei den Ausdruck scheitern zu lassen). Diese Eigenschaft wird "gierig" (engl. "greedy") genannt. Da dieses Verhalten nicht immer gewünscht ist, läßt sich die Gierigkeit (Greediness) umschalten - ein Quantifier frißt dann nur so viele Zeichen wie nötig, jedoch so wenig wie möglich.
Diese Umschaltung läßt sich entweder durch den Modifier U (wie ungreedy) für alle Quantifier im gesamten Ausdruck, oder durch ein nachgestelltes ? (Fragezeichen) für einen einzelnen Quantifier realisieren. Auch eine Kombination ist möglich - d.h. erst mittels U die Quantifier normalerweise ungreedy zu machen, einzelne davon durch ein ? jedoch wieder greedy. Anhand folgender Beispiele ist zu sehen, wieviele Zeichen der Ausdruck .* je nach Greediness verschlingt - mal ist er mit dem erstbesten r zufrieden, mal nimmt er alles bis zum letztmöglichen r mit:
$string = 'Dieser Satz wird fast gefressen';
// Normalzustand:
preg_match('/D.*r/', $string, $matches);
-> Dieser Satz wird fast gefr
// Einen Quantifier ungreedy gemacht:
preg_match('/D.*?r/', $string, $matches);
-> Dieser
// Greediness aller Quantifier umgeschaltet:
preg_match('/D.*r/U', $string, $matches);
-> Dieser
// Doppelt gemoppelt: Greediness umgeschaltet,
// einen Quantifier wieder zurückgeschaltet:
preg_match('/D.*?r/U', $string, $matches);
-> Dieser Satz wird fast gefr
// Bei dieser Schreibweise spielt die Greediness keine Rolle:
preg_match('/D[^r]*r/', $string, $matches);
-> Dieser
Die ereg()-Funktionen kennen übrigens keine umschaltbare Gierigkeit (d.h. Quantifier sind immer gierig).
In diesem Beispiel wird die HTML-Datei über den file() -Befehl vom entfernten Server gezogen. Die Funktion absolute() erwartet als ersten Parameter einen Link und als zweiten Parameter die volle URL des Dokuments, in dem sich der Link befindet. Sie kann auch in einem anderen Kontext verwendet werden. Obwohl viele der Anweisungen (z.B. substr() oder strrpos() ) auch über reguläre Ausdrücke gelöst werden können, wurde aus Performance-Gründen bewusst darauf verzichtet.
<?php
// Datei über HTTP aufrufen
$url = 'http://www.server.de/test.html';
$old = implode('', file($url));
// Links suchen und an absolute() weiterleiten
$new = preg_replace(',<a([^>]+)href="php/php-faq/static/%28%5B%5E%26gt%3B.html"\s]+)",ie',
'"<a\1href=\"" . absolute("\2", $url) . "\""',
$old);
// HTML-Code ausgeben
header('Content-type: text/plain');
echo $new;
// Funktion, die relative in absolute Links umschreibt
function absolute ($relative, $absolute) {
// Link ist schon absolut
if (preg_match(',^(https?://|ftp://|mailto:|news:),i', $relative))
return $relative;
// parse_url() nimmt die URL auseinander
$url = parse_url($absolute);
// dirname() erkennt auf / endende URLs nicht
if ($url['path']{strlen($url['path']) - 1} == '/')
$dir = substr($url['path'], 0, strlen($url['path']) - 1);
else
$dir = dirname($url['path']);
// absoluter Link auf dem gleichen Server
if ($relative{0} == '/') {
$relative = substr($relative, 1);
$dir = '';
}
// Link fängt mit ./ an
elseif (substr($relative, 0, 2) == './')
$relative = substr($relative, 2);
// Referenzen auf höher liegende Verzeichnisse auflösen
else while (substr($relative, 0, 3) == '../') {
$relative = substr($relative, 3);
$dir = substr($dir, 0, strrpos($dir, '/'));
}
// volle URL zurückgeben
return sprintf('%s://%s%s/%s', $url['scheme'], $url['host'], $dir, $relative);
}
?>