Quakenet/#php Tutorial

Hinweis: Wenn sie diese Seite von einer externen URL aufgerufen haben achten sie darauf das alle Kapitel aufeinander aufbauen. Stellen sie daher sicher dass sie alle vorherigen Kapitel gelesen haben, da sie sonst relevante Informationen übersehen.

Reguläre Ausdrücke

  1. Was ist ein Regulärer Ausdruck?
  2. Aufbau von einem Regex
  3. Finden von Strings
  4. Unterschiedliche Strings finden
  5. Erfassen von Teilergebnissen
  6. Zeichenklassen
  7. Vordefinierte Zeichenklassen
  8. Wiederholungen von Teilstrings
  9. Gierige Suchmuster
  10. Regex in PHP
  11. Die Welt der Regex

1. Was ist ein Regulärer Ausdruck?

Ein Regulärer Ausdruck (kurz Regex) ist ein String der angibt wie ein anderer String aussehen soll. Wenn ein String zu einem Regex passt spricht man auch von einem match, daher auch der Funktionsname von preg_match.

2. Aufbau von einem Regex

Unabhängig was ein Regex prüft haben alle Reguläre Ausdrücke der PCRE-Engine (die Engine die für die Regex-Auswertung zuständig ist) den selben Aufbau.

  1. Delimiter - Dieses erste Zeichen im String dient dazu den eigentlichen Regex von einer Liste globaler Modifikatoren (auch modifier genannt) zu trennen. So ein Zeichen ist nötig da alles in ein einzelnen String kodiert werden muss. Jedoch besteht ein PCRE-Regex Grundsätzlich aus eben den eigentlichen Regex und den modifiern. Daher gibt das erste Zeichen im String das Trennzeichen an. Üblicherweise werden Zeichen wie / oder ~ verwendet.

  2. Regex - In diesem Teil steckt die eigentliche Logik drin. Er beschreibt wie der zu prüfende String aussehen soll.

  3. Delimiter - Nun kommt der vorher gewählte Delimiter um die folgende modifier vom Regex zu trennen.

  4. Modifier - Dieser Teil beschreibt Modifikatoren für den ganzen Regex. Die Liste der Modifier kann dabei leer sein wenn keine benötigt werden.

Da in einem Regex oft das Zeichen \ verwendet wird, jedoch \ auch das PHP-Escapezeichen für Strings ist werden für Regex üblicherweise single-quoted-Strings verwendet.

3. Finden von Strings

Die einfachste Variante von einem Regex ist der leere Regex // (mit / als Delimiter). Er matched jeden String da eine leere Zeichenkette gesucht wird und überall eine leere Zeichenkette zu finden ist.

  re> //
data> foobar
 0:
data> 
 0:
data> eiovrjerv
 0:
data> 32 e4234234 324 2
 0:

Das Testprogramm pcretest dient dazu ein Regex zu testen. Hier wurde der Regex // eingegeben. Anhand von Beispieleingaben sieht man dass jeweils etwas gefunden wurde was eine Nummer 0 hat. Dieser Wert liefert immer den gefundenen Text zurück. Da nach einem leeren String gesucht wurde wurde auch stehts ein leerer String gefunden. In PHP können wir dann später auch auf das Ergebnis 0 zugreifen.

Als nächstes Beispiel suchen wir nun einen normalen String. Solange er keine Metazeichen enthält kann man den String den man suchen möchte als solches hinschreiben. Falls man doch Metazeichen hat werden sie wie in PHP mit \ escaped.

  re> /bla/
data> foobar
No match
data> blabli
 0: bla
data> BLABLI
No match
data> xyzblaBLI
 0: bla

Bei der zweiten Eingabe sieht man dass der gesuchte Text auch gefunden wurde. In der dritten Eingabe wurde der Text jedoch nicht gefunden da reguläre Ausdrücke casesensitiv sind und entsprechend Groß- und Kleinschreibung. Falls man möchte das ein regulärer Ausdruck caseinsensitiv arbeitet kann man den modifier i verwenden.

  re> /bla/i
data> foobar
No match
data> blabli
 0: bla
data> BLABLI
 0: BLA
data> xyzblaBLI
 0: bla

Nun wurde auch im dritten Teststring das Suchmuster gefunden.

4. Unterschiedliche Strings finden

Manchmal möchte man nicht nach einen Konkreten String suchen sondern nach einer Auswahl von mehrere Strings. Diese Strings werden in einem Regex mit dem |-Zeichen getrennt. Es können beliebig viele Strings mit | getrennt werden.

  re> /foo|bar/
data> blabarfoo
 0: bar
data> foobar
 0: foo
data> fobarfoarofboaoar
 0: bar
data> fobabfoaoofoa
No match
data> Foobar
 0: bar

Mit unserem Regex suchen wir nach einem String foo oder bar. Im ersten Beispiel wurde bar gefunden, obwohl beide Strings vorhanden sind. Der Regex sucht dabei von Vorne und findet entsprechend zuerst das bar. Im zweiten Beispiel ist es genau umgekehrt, da findet er das foo zuerst und im dritten Beispiel findet er das bar an der Stelle 3-5. Im vierten Beispiel ist keiner der beiden Strings vorhanden. Im fünften Beispiel findet er bar da wir nicht den i-modifier verwendet haben.

5. Erfassen von Teilergebnissen

Manchmal will man mit einem Regex nicht nach einem String suchen sondern nach mehreren Teilstring. Im Moment kriegen wir nur durch diese 0:-Angabe das Suchergebniss des ganzen Strings. Es ist jedoch möglich gleichzeitig neben den kompletten Suchergebnis auch Teilergebnisse abzufragen. Für jedes Teilergebnis welches man abfragen möchte verwendet man die Klammern ( und ). Dadurch entsteht ein neuer Eintrag in der Ergebnisliste.

  re> /News vom (Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)/
data> <p>News vom Montag<br />Am gestrigen Sonntag ist ein Sack Reis umgefallen</p>
 0: News vom Montag
 1: Montag
data> <p class="news Dienstag">News vom Freitag<br />Wieder ein Sack umgefallen.</p>
 0: News vom Freitag
 1: Freitag
data> Weder am Mittwoch noch am Donnerstag ist was passiert
No match

Hier wird erstmal allgemein nach dem Text News vom $WOCHENTAG gesucht. Jedoch wird hier der Wochentag selbst noch abgefragt. Im ersten Fall finden wir somit den Text News vom Montag als Ergebnis 0 und darin speziell den Montag als Ergebnis 1. Im zweiten Beispiel finden wir den Newsheader mit Freitag obwohl vorher der String Dienstag vorhanden ist. Den haben wir jedoch nicht gesucht, gesucht haben wir nach News vom $WOCHENTAG. Dies wird im dritten Beispiel deutlich wo wir zwar die Wochentage finden aber nicht nach unserem gesuchten News vom $WOCHENTAG-Aufbau.

6. Zeichenklassen

Wenn man z.B. nach den Ziffern 0, 2, 4, 6 oder 8 sucht würde man ggf. den Regex /0|2|4|6|8/ verwenden. Dies kann vereinfacht werden wenn man eine Zeichenklasse verwendet. Dabei verwendet man die eckigen Klammern [ und ], die eine Zeichenklasse einleiten bzw. beenden. Innerhalb der Zeichenklasse schreibt man einfach die Zeichen rein die dazugehören sollen. Der Regex passt dann auf ein Zeichen in dieser Liste.

  re> /Es gab [23] Spiele heute/
data> Es gab 2 Spiele heute
 0: Es gab 2 Spiele heute
data> Es gab 5 Spiele heute
No match
data> Es gab 22 Spiele heute
No match

Im ersten Beispiel wird unser Teststring gefunden, im zweiten und dritten Beispiel jedoch nicht da weder die 5 in der Zeichenklasse ist, noch der String Es gab 22 Spiele heute irgendwie passt.

Anstatt jedes einzelne Zeichen aufzulisten können auch Bereiche mit einem Minuszeichen angegeben werden.

  re> /[0-5]/
data> Ich suche eine 4 im Text
 0: 4
data> ich suche nun eine 8
No match
data> und nun eine 99
No match
data> und zu letzt eine 49
 0: 4

Im letzten Beispiel sieht man das die 4 gefunden wurde und nicht die Zahl 49, da es sich bei [...] um Zeichenklassen handeln und nichts anderes. Somit wird man mit einem Regex /[0-255].[0-255].[0-255].[0-255]/ nicht nach IP-Adressen suchen können da die Zeichenklasse [0-255] nur auf die Zeichen 0, 1, 2 und 5 passt (die zweite 5 ist sogar überflüssig).

Für den Fall dass man das Minuszeichen in die Zeichenklasse aufnehmen will muss man es am Anfang oder Ende der Zeichenklasse plazieren damit es nicht als Bereich von Irgendwas interpretiert werden kann.

Wenn das erste Zeichen in der Zeichenklasse das ^-Zeichen wird die Zeichenklasse negiert, dann matched sie auf alle Zeichen die nicht in der Zeichenklasse stehen.

  re> /[0-5]/
data> Ich suche eine 4 im Text
 0: 4
  re> /[^0-5]/
data> nun suche ich eine 8
 0: n

Im zweiten Beispiel wird das n von nun gefunden, das es das erste Zeichen ist das keine Ziffer von 0 bis 5 ist.

7. Vordefinierte Zeichenklassen

Es gibt Suchstrings die häufig vorkommen wie z.B. eine Ziffer oder ein Buchstabe. Anstatt nun alle Ziffern bzw. alle Buchstaben aufzulisten gibt es in PCRE vordefinierte Zeichenklassen.

8. Wiederholungen von Teilstrings

In der Regel sind die zu suchenden Strings länger als ein einzelnes Zeichen. Es kann sogar sein das der Text der gesucht wird unterschiedlich lang sein kann. Daher existiert in Regex die Möglichkeit nach Wiederholungen zu suchen, wie z.B. 20 Buchstaben oder beliebig viele Ziffern.

Um die Wiederholung von einem String festzulegen werden die geschweiften Klammern { und } verwendet. Innerhalb der Klammern geben ein oder zwei Zahlen die Anzahl der Wiederholungen an. Somit bedeutet {5} 5 Wiederholungen und {3,7} 3 bis 7 Wiederholungen. Die Angabe {0} ist Möglich bewirkt jedoch meistens nicht das was gewollt ist denn wie ganz am Anfang des Kapitels kann überall ein leerer String gefunden werden. Wenn ein Bereich angegeben wird, jedoch eine Zahl weggelassen wird entspricht dies einer offenen Grenze. So entspricht {5,} mindestens 5 Wiederholungen und {,8} maximal 8 Wiederholungen.

  re> /\d{2}-\d{2}-\d{2,4}/
data> 12-03-2008
 0: 12-03-2008
data> Ich habe am 1-1-1970 Geburtstag
No match
data> Und ich am 45-23-301.
 0: 45-23-301

Hier wurde nach einem Datum-typischen String gesucht. Man sieht auch das nicht alles was wie ein Datum aussieht auch wirklich ein Datum ist. Daher muss man ggf. die Daten die man ausgelesen hat später noch mit PHP prüfen.

Einige Wiederholungen kommen so oft vor dass es für diese sogar Abkürzungen gibt.

Diese Abkürzungen werden oft mit den Abkürzungen der Zeichenklassen verwendet. So findet man in Regex oft solche Suchmuster wie .* oder \d+.

9. Gierige Suchmuster

Wenn ein Suchmuster unterschiedlich oft wiederholt werden kann versucht die Regex-Engine den größtmöglichen Text zu finden, auch wenn ein kleinerere Text reichen würde. Dieses Verhalten wird greedy genannt. Es kann jedoch sein dass man genau dies nicht möchte bzw. fehl am Platz ist. Als Beispiel wollen wir HTML-Elemente finden und würde da vielleicht den Regex /<.*>/ verwenden.

  re> /<.*>/
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>hervorgehobenen</em>

Im ersten Beispiel wurde zwar das HTML-Element, im zweiten Beispiel jedoch auch Text der nicht erwünscht ist. Eine Möglichkeit wäre es nicht die das .-Zeichen zu verwenden sondern eine Zeichenklasse die kein >-Zeichen erlaubt. Eine weitere Möglichkeit wäre die Regex-Engine von greedy auf ungreedy zu stellen. Dies wird mit dem Modifier U gesetzt.

  re> /<.*>/U
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>

Die Greedy-Eigenschaft lässt sich bei * und + auch umdrehen wenn hinter den Abkürzungen ein weiteres ? folgt.

  re> /<.*?>/
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>

Dies ist nötig wenn man mehrere solche Wiederholungen hat, jedoch nur einige greedy und andere ungreedy sein sollen.

10. Regex in PHP

Mit dem ganzen Vorwissen können wir nun Regex in PHP verwenden. Dabei werden meistens die Funktionen preg_match und preg_replace verwendet. Die Funktion preg_match wird verwendet wenn man gucken möchte ob ein Regex auf ein String passt, preg_replace wird verwendet wenn der gefundene String dann durch einen anderen String ersetzt werden soll.

Der erste Parameter der preg_match ist der Regex, der zweite Parameter ist der Eingabestring der getestet werden soll. Da der Regex in der Regel Backslashes enthällt (\) sollte man hier stehts single-quoted-Strings verwenden, das spart ne Menge escaperei. Die Funktion liefert dann 0 zurück wenn der Regex nicht passt und 1 wenn der Regex passt. Daher kann preg_match gut in einer if-Abfrage verwenden.

<?php
if (!preg_match('~\A\d{2}-\d{2}-\d{4}\z~''40-50-0001')) { // hier mal '~' als Delimiter verwendet
    
die('Bitte geben sie etwas ein was wie ein Datum aussieht.');
}
?>

Falls man Suchmuster einklammert um zu Gruppen zu erfassen kann man als dritten Parameter eine Variable übergeben. Wenn der Regex zum String passt wird in der Variable ein Array gespeichert, welches die erfassten Gruppen erfasst, die wir oben mit 0 und 1 kennengelernt haben. Die Schlüssel entsprechen dann den gefundenen Gruppen.

<?php
if (!preg_match('~Foo(.*)Bar~i''blablifoooooooblabaRblibar'$matches)) {
    echo 
"nicht gefunden";
} else {
    
var_dump($matches);
}
?>

Dies gibt folgendes aus.

array(2) {
  [0]=>
  string(20) "foooooooblabaRblibar"
  [1]=>
  string(14) "oooooblabaRbli"
}

Hier sieht man die Gruppen 0 und 1 wie sie auch pcretest finden würde.

Mit der Funktion preg_replace geht man noch ein Schritt weiter und ersetzt jeden gefundenen Teilstring durch einen anderen String. Insbesondere kann gefundener Text beim ersetzen beibehalten werden.

<?php
$Text 
'Ein sehr Langer Text mit "besonderen" Markierungen, die dann "besonders" behandelt werden.';
$Regex '~"(.*)"~U';
$Ersetzen '<em>\1</em>';
$Text preg_replace($Regex$Ersetzen$Text);
echo 
$Text;
// Gibt folgendes aus
// Ein sehr Langer Text mit <em>besonderen</em> Markierungen, die dann <em>besonders</em> behandelt werden.
?>

Auf die gefundenen Strings kann man mit \$Nummer zugreifen, wobei dann entsprechend die Nummern die Gruppen sind, die man gefunden hat. Beachtet das für einfache Textersetzungen man kein preg_replace braucht. Wenn man z.B. alle e's durch E's ersetzen will reicht es Dicke wenn man die str_replace-Funktion verwendet und nicht die Regex-Engine anwerfen muss.

11. Die Welt der Regex

Dieses Kapitel ist nur ein Grundwissen über Regex. Im Handbuch zu Regular Expressions (Perl-Compatible) sowie auf diversen Internetseiten wie in der Wikipedia zu Regular expression finden sich detailiertere Informationen wie man mit Regex umgeht und was alles noch möglich ist.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Zurück zu Weiter zu
Copyright © bei den OPs von #php.de/QuakeNet Valid XHTML 1.0 Strict Valid CSS!