Lösungen

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Lösung 1

Wie liest man ˹^ding$˼, ˹^$˼ und ˹^˼?

Auflösung zum Problem aus Zeilenanfang und Zeilenende:

˹^ding$˼

Wörtlich: Passt, wenn die Zeile einen Anfang hat (was natürlich alle Zeilen haben), gefolgt von d‧i‧n‧g, und wenn dann unmittelbar das Zeilenende folgt.

Effekt: Zeilen werden erkannt, wenn sie nur ding und nichts anderes enthalten, also keine zusätzlichen Wörter,Wortzwischenräume, Interpunktion usw.

˹^$˼

Wörtlich: Passt, wenn die Zeile einen Anfang hat, auf den unmittelbar das Zeilenende folgt.

Effekt: Leerzeilen werden erkannt (nur wirkliche Leerzeilen, auch Leerzeichen auf der Zeile sind nicht erlaubt).

˹^˼

Wörtlich: Passt, wenn die Zeile einen Anfang hat.

Kein Effekt! Da jede Zeile einen Zeilenbeginn hat, wird dieser reguläre Ausdruck immer passen -– auch bei Leerzeilen!

 

Lösung 2

 

Warum findet ˹q[^u]˼ ››Qantas‹‹ und ››Iraq‹‹ nicht?

Antwort zum Problem aus Negierte Zeichenklassen:
 
Qantas wird nicht gefunden, weil der reguläre Ausdruck nach einem Kleinbuchstaben q verlangt, das Q in Qantas ist aber ein Großbuchstabe. Hätten wir ˹Q[^u]˼ verwendet, wäre Qantas wohl gefunden worden, dafür alle anderen Wörter nicht, weil sie kein großes Q enthalten. Der reguläre Ausdruck ˹[Qq][^u]˼ dagegen hätte alle gefunden.
 
Das Problem mit Iraq ist schon fast eine Fangfrage. Der reguläre Ausdruck verlangt nach einem q, gefolgt von einem Zeichen, das kein u sein darf. Nun entfernt aber egrep alle Zeilenende-Zeichen, bevor es reguläre Ausdrücke prüft (ein Detail, das ich verschwiegen habe –- sorry!), und so gibt es nach dem q überhaupt kein Zeichen, das irgendwie in den regulären Ausdruck passen könnte. Sicher kommt nach dem q kein u, aber es kommt auch kein Nicht-u!
 
Grämen Sie sich nicht wegen der Fangfrage. (Annekdote: In der vierten Klasse sollte ich einmal in einem mündlichen Buchstabierwettbewerb das Wort »miss« (verfehlen) buchstabieren. Ich antwortete m‧i‧s‧s. Aber die Lehrerin, Miss Smith, sagte, dass M‧i‧s‧s mit einem großen M gemeint wäre und dass ich nach einem Beispielsatz hätte fragen müssen. Ein traumatischer Moment im Leben eines Knaben. Nach diesem Erlebnis mochte ich Miss Smith nicht mehr und stehe seiddem mit der Ortografie auf Krigsfus.)
 
Wenn nun egrep das Zeilenende-Zeichen nicht abschneiden würde (andere Programme tun das nicht) oder wenn auf das Wort Iraq ein anderes Wort oder auch nur ein Leerzeichen folgen würde, dann wäre das Wort gefunden worden. Manchmal ist es wichtig, die letzten Feinheiten eines Programms zu kennen, aber bei diesem Beispiel geht es mir nur darum zu zeigen, dass eine Zeichenklasse, auch wenn sie negiert ist, ein Zeichen braucht, damit sie passt.
 

 

Lösung 3

Einen Unterausdruck optional machen

Antwort zum Problem aus Andere Quantoren: Repetition:
 
In diesem Fall ist mit »optional« Folgendes gemeint: einmal erlaubt, aber nicht Bedingung. Daher werden wir ˹?˼ benutzen.Weil das optionale Element mehr als ein einzelnes Zeichen umfasst, werden wir es in Klammern setzen müssen: ˹(...)?˼. In unseren regulären Ausdruck eingesetzt, ergibt das:
 
      ˹<HR(+ SIZE*=*[0­-9]+)?*>˼
 
Beachten Sie, dass das letzte ˹außerhalb von ˹(...)?˼ steht. Damit erreichen wir, dass auch Texte wie <HR> erkannt werden. Stünde es innerhalb der Klammern, würden Leerzeichen vor dem > nur dann erkannt, wenn eine explizite Dickenangabe vorhanden ist.
 
Beachten Sie außerdem, dass im Gegensatz dazu das ˹ vor dem SIZE in den Klammern enthalten ist. Wäre das nicht so, dann müsste mindestens ein Leerzeichen nach dem HR vorkommen, auch wenn kein SIZE-Attribut im Tag vorkommt. Dann würde <HR> nicht erkannt.
 

 

Lösung 4

Die Uhrzeit-Regex für das 24-Stunden-Schema erweitern

Antwort zum Problem aus Amerikanische Uhrzeiten wie »9:17 am« oder »12:30 pm«:
 
Es gibt natürlich viele korrekte Antworten; bei dieser hier wird die gleiche Logik wie vorhin verfolgt. Diesmal teile ich die Aufgabe in drei Teile auf: einen für die Morgenstunden von 00 bis 09 (die führenden Nullen sind dabei optional), einen für die Tagesstunden von 10 bis 19 und einen für die Nachtstunden von 20 bis 23. Schritt für Schritt in die Sprache der regulären Ausdrücke übersetzt: ˹0?[0­-9]|1[0-­9]|2[0-­3]˼.
 
Die ersten zwei Alternativen können wir zu einer einzigen zusammenfassen. Dadurch erhalten wir das kürzere ˹[01]|?[0­9]|2[0-­3]˼. Warum das so ist, ist vielleicht nicht auf den ersten Blick klar. Die Abbildung mag helfen, und sie zeigt auch eine weitere Möglichkeit, das Problem anzugehen. Die schattierten Gruppen umfassen jeweils Zahlen, die sich mit einem Teil einer Alternation (einer Alternative) beschreiben lassen.
Die Uhrzeit-Regex für das 24-Stunden-Schema erweitern
 

 

Lösung 5

Nicht-einfangende Klammern: ˹(?:...)˼

Antwort zum Problem aus Verschachtelte reguläre Ausdrücke:
 
In der Abbildung Verschachtelte Klammern wurden die Klammern in ˹(\.[0-­9]*)?˼ nur zum Gruppieren benutzt, damit sich das Fragezeichen auf den ganzen Unterausdruck ˹\.[0­-9]*˼ bezieht und ihn so optional macht. Dabei wurde der Text, auf den der Unterausdruck passte, automatisch eingefangen und in der Variablen $2 abgespeichert, aber diese Variable haben wir nie benutzt. Wäre es nicht besser, wenn es eine Art von Klammern gäbe, die nur zum Gruppieren diente und dieses unnötige Abspeichern vermiede?
 
In Perl und neuerdings auch bei anderen Regex-Dialekten gibt es solche Klammern. Statt ˹(...)˼, mit dem man gruppiert und den gefundenen Text »einfängt«, gibt es auch die besondere Notation ˹(?:...)˼, mit der man gruppiert, die aber nichts einfängt. Bei dieser Notation besteht die »öffnende Klammer« aus den drei Zeichen (?:, was sicher gewöhnungsbedürftig aussieht. Das Fragezeichen hier hat keinen Bezug zum Metazeichen ˹?˼, das einen Unterausdruck optional macht. (Unter Die Ursprünge regulärer Ausdrücke wird erklärt, warum diese merkwürdige Notation gewählt wurde.)
 
So wird aus dem ganzen Ausdruck von Abbildung Verschachtelte Klammern:

if ($input =~ m/^([­+]?[0­9]+(?:\.[0­9]*)?)([CF])$/)

Obwohl das Klammerpaar um ˹[CF]˼ offensichtlich das dritte ist, wird der darin aufgefangene Text dennoch in $2 abgespeichert, weil beim Abzählen von Klammerpaaren diejenigen mit ˹(?:...)˼ eben nicht »zählen«.

 
Das hat zwei Vorteile. Zum einen muss nichts abgespeichert werden, das ist efzienter (mehr zu Fragen der Effizienz finden Sie unter Die Kunst, reguläre Ausdrucke zu schreiben). Zum anderen ist es ein klarer Programmierstil, immer die genau richtige Art von Klammern zu benutzen: Jemand, der den Code liest, weiß, dass bei Verwendung von ˹(?:...)˼ der erkannte Text später nicht verwendet wird.
 
Andererseits sieht die Klammerungmit ˹(?:...)˼ ziemlich unschön aus, und das wiederum verschlechtert die Lesbarkeit, zumindest auf den ersten Blick. Sind es die Vorteile wert? Ich persönlich verwende meist die Art von Klammern, die der Aufgabe angemessen ist, aber in diesem konkreten Fall verwirrt das ˹(?:...)˼ vielleicht mehr, als es nützt. Efzienz spielt hier eine untergeordnete Rolle, weil der reguläre Ausdruck nur einmal verwendet wird (in einer Schleife sähe das anders aus).
 
Im Rest dieses Kapitels verwende ich aus ästhetischen Gründen die einfachen Klammern ˹(...)˼, auch wenn der damit aufgefangene Text gar nicht verwendet wird.
 

 

Lösung 6

Was unterscheidet ˹[TAB]*˼ von ˹(*|TAB*)˼?

Antwort zum Problem aus Verschachtelte reguläre Ausdrücke: Kurze Frage:
 
˹(*|TAB*)˼ erlaubt entweder ˹*˼ oder ˹TAB*˼, damit ein Treffer erzielt wird, also entweder Leerzeichen (oder gar nichts) oder Tabs (oder gar nichts). Es erlaubt aber keine Kombination von Leerzeichen und Tabs.
 
˹[TAB]*˼ bedeutet eine beliebige Anzahl von ˹[TAB]˼. Ein String wie ›TAB‹ passt darauf dreimal, einmal mit dem Tab und zweimal mit dem Leerzeichen.
 
Dagegen hat ˹[TAB]*˼ in der Tat dieselbe Wirkung wie ˹(*|TAB*)˼, aber Sie werden unter Wie Regex-Maschinen arbeiten sehen, warum die Zeichenklasse meist wesentlich effizienter ist.
 

 

Lösung 7

Was bewirkt $var =~ s/\bJeff\b/Jeff/i?

Antwort zum Problem aus Mit regulären Ausdrücken Text verändern:
 
Die Frage erscheint nur schwierig wegen der Art, wie sie gestellt wurde. Hätte ich ˹\bJEFF\b˼ oder ˹\bjeff\b˼ oder vielleicht ˹\bjEfF\b˼ als regulären Ausdruck angegeben, wären Sie sofort auf die Lösung gekommen. Wegen dem /i wird dasWort »Jeff« ohne Rücksicht auf die Groß-/Kleinschreibung gefunden. Es wird dann durch ›Jeff‹ ersetzt, in genau dieser Schreibweise. /i hat keinerlei Auswirkung auf den Ersatztext, andere Modikatoren, die Sie unter Perl kennenlernen, schon.
 
Der Nettoeffekt ist der, dass »jeff« in jeder Kombination von Groß- und Kleinbuchstaben durch »Jeff« ersetzt wird.
 

 

Lösung 8

Was bewirkt s/(?=s\b)(?<=\bJeff)/'/g?

Antwort zum Problem von Weitere Beispiele zum Lookahead:
 
In diesem Fall spielt es keine Rolle, in welcher Reihenfolge die Unterausdrücke ˹(?=s\b)˼ und ˹(?<=\bJeff)˼ angeordnet werden. Es ist egal, ob wir von einer bestimmten Position aus erst die »Zeichen links, dann die rechts« überprüfen oder umgekehrt; entscheidend ist, dass die beiden Teile von der gleichen Position aus geprüft werden. Beispielsweise passen die zwei Unterausdrücke ˹(?=s\b)˼ und ˹(?<=\bJeff)˼ im Suchstring Thoma▵sJeff▵erson bei den markierten Stellen, aber es gibt keine Position im String, bei der die Kombination beider Unterausdrücke passen könnte.
 
Ich verwende hier die etwas ungenaue Formulierung »die Kombination beider Unterausdrücke«, und in diesem Fall ist intuitiv klar, was gemeint ist. Es gibt aber auch Fälle, in denen es sehr darauf ankommt, wie die Regex-Maschine bei der Mustersuche genau vorgeht, und das ist nicht immer sehr intuitiv. Es hat aber unmittelbare Auswirkungen darauf, was ein bestimmter regulärer Ausdruck bedeutet. Unter Wie Regex-Maschinen arbeiten wird das bis ins Detail untersucht.
 

 

Lösung 9

Welche »Jeff«-Lösungen verändern mit /i die Groß-/Kleinschreibung nicht?

Antwort zum Problem von Zusammenfassung »Jeffs«:
 
Die Groß-/Kleinschreibung bleibt erhalten, wenn entweder genau die »konsumierten« Zeichen wieder eingesetzt werden (und nicht nur einfach ››Jeff's‹‹) oder wenn gar nichts konsumiert wird. Die zweite Lösung aus Tabelle Ansätze für das »Jeffs«-Problem benutzt die erste Möglichkeit: Alle konsumierten Zeichen werden abgespeichert und können mit $1 und $2 in den Ersatztext eingefügt werden. Die zwei letzten Lösungen in der Tabelle verfolgen den Ansatz »gar nichts konsumieren«. Weil sie das nicht tun, gibt es auch nichts zu ersetzen.
 
Bei der ersten und der dritten Lösung wird der Ersatztext explizit in einer ganz bestimmten Schreibweise eingesetzt. JEFFS würde fälschlicherweise in Jeff's bzw. Jeff'S verwandelt.
 

 

Lösung 10

Kann man mit s/(\d)((\d\d\d)+\b)/$1.$2/g Zahlen in Dreiergruppen aufteilen?

Antwort zum Problem von Dreiergruppen ohne Lookbehind:

Die Substitution bewirkt etwas, aber nicht das, was wir wollen. Beim Einwohnerzahlbeispiel erhalten wir »82.499213«. Das liegt daran, dass jetzt die einmal erkannte erste Dreiergruppe Teil des gefundenen Treffers ist und nicht mehr wie vorhin für die nächste Runde des /g »zurückgegeben« werden kann.

Bei jeder neuen Runde wird mit der Mustersuche an der Stelle begonnen, wo der Treffer der vorherigen Runde geendet hat. Wir möchten, dass dies die Position gerade nach dem eben eingesetzten Tausenderpunkt ist, so dass wir die nächste Dreiergruppe abarbeiten können – aber leider hat das ˹(\d\d\d)+˼ schon alle Ziffern konsumiert, und in der zweiten Runde wird am Ende der ganzen Ziffernfolge mit der Mustersuche begonnen. Der springende Punkt beim Lookahead ist ja gerade, dass man den Text rechts der aktuellen Position überprüfen kann, ohne dass dieser zum Treffer beiträgt.

Wir können diesen Ansatz dennoch retten. Wenn wir mit den Mitteln der Programmiersprache die Substitution in einer Schleife wiederholen, wird der gerade veränderte Text immer wieder ganz von vorn bearbeitet und nicht da, wo die letzte Iteration aufgehört hat. Bei jeder Iteration wird ein weiterer Tausenderpunkt eingefügt (und zwar, wegen des /g-Modifikators, bei jeder Zahl im Suchstring). Hier das Beispiel in Perl:

while ( $text =~ s/(\d)((\d\d\d)+\b)/$1.$2/g ) {
     # Leere Schleife -- wir starten nur die Regex immer wieder neu, bis sie nichts mehr findet.
}

 

Lösung 11

Ist ›eine▵1234Zahl‹ Teil eines gespeicherten Zustands, wenn die Regex ˹[0-9]*˼ auf
›eine1234Zahl‹ angewendet wird?

Antwort zum Problem von Backtracking beim Stern und beim Plus:

Die Antwort ist nein. Ich habe die Frage gestellt, weil es sich um einen häufigen Anfängerfehler handelt. Eine Komponente mit einem Stern dahinter kann immer passen. Wenn diese Komponente die gesamte Regex ausmacht, kann auch die gesamte Regex immer und auf jede Stelle im String passen. Darin ist auch die allererste Position enthalten, auf die das Getriebe die Regex ansetzt: der Anfang des Strings. Im vorliegenden Fall passt die gesamte Regex an der Stelle ›▵eine1234Zahl‹ – und das war’s dann; die Maschine stößt nie bis zu den Ziffern vor.

Wenn Ihre Antwort falsch war, gibt es noch immer die Chance für einen halben Punkt. Wäre in der Regex nach dem ˹[0-9]*˼ etwas vorhanden, das einen globalen Treffer bis zum Zustand

im Text: ›eine▵1234...‹ in der Regex: ˹▵[0-9]*...˼

unmöglich machen würde, dann hätte der Versuch bei ›1‹ tatsächlich diesen Zustand abgespeichert:

im Text: ›eine▵1234...‹ in der Regex: ˹[0-9]*▵...˼
 

  

Lösung 12

Was erkennt ˹(?>.*?)˼?

Antwort zum Problem von Atomare Gruppen: Die Essenz.

Dieses Konstrukt kann nie auf ein Zeichen passen. Es ist einfach eine ziemlich komplizierte Art, gar nichts zu erreichen. ˹*?˼ ist die nicht-gierige Version von ˹*˼ und quantifiziert den Punkt. Der erste Weg, der ausprobiert wird, besteht darin, die Möglichkeit, den Punkt anzuwenden, zu überspringen – die andere Möglichkeit wird als gespeicherter Zustand abgelegt. Aber damit ist die Klammer schon beendet, und alle während der Klammer gespeicherten Zustände werden weggeworfen. Der einzige Weg, der beschritten wird, ist der »Punkt überspringen«-Weg, die »Punkt ausprobieren«-Möglichkeiten werden abgespeichert. Sobald die Klammer verlassen wird, werden die »Punkt ausprobieren«-Zustände entfernt, und der Weg »Punkt überspringen« ist der einzige, der je begangen wird. Wenn aber etwas immer übersprungen wird, könnte man es auch gleich weglassen.

  

Lösung 13

Auswirkungen einer einfachen Änderung

Auflösung zum Problem von Eine einfache Änderung – die Schokoladenseite zuerst:

Auf welchen Maschinentyp wirkt sich die Änderung aus?
Die Änderung hat keinerlei Auswirkungen auf eine POSIX-NFA-Maschine. Diese muss ohnehin alle Permutationen der Regex testen, und dabei ist die Reihenfolge dieser Tests irrelevant. Bei einem traditionellen NFA bringt das Prinzip »häufigste Alternative zuerst« sehr wohl etwas, weil die Maschine nach dem ersten gefundenen lokalen Treffer die weiteren Alternativen nicht mehr berücksichtigen muss.
Bei welcher Art des Resultats wirkt sich die Änderung aus?
Diese Änderung bewirkt nur eine Verbesserung, wenn tatsächlich ein Treffer gefunden wird. Ein NFA gibt erst auf, wenn alle Permutationen durchgespielt sind und sich keine als Treffer entpuppt hat (nochmals, ein POSIX-NFA probiert ohnehin alle durch). Wenn der Text nicht passt, müssen alle Permutationen geprüft werden, und die Reihenfolge spielt dann keine Rolle.
 

Die Tabelle zeigt die Anzahl der Vergleiche (»Tests«) und die der Backtracking-Vorgänge (»BT«). Kleinere Zahlen bedeuten bessere Resultate.

   Traditioneller NF POSIX-NFA
   ˹"(\\.|[^"\\])*"˼ ˹"([^"\\]|\\.)*"˼ beide Ausdrücke
Beispieltext Tests BT Tests BT Tests BT
"2\"x3\" likeness" 32 14 22 4 48 30
"makudonarudo" 28 14 16 2 40 26
"sehr...99 Zeichen...lang" 218 109 111 2 325 216
"No \"match\" here 124 86 124 86 124 86

Die Resultate sind offensichtlich für beide Ausdrücke bei einem POSIX-NFA identisch. Bei traditionellen NFAs sind die Resultate für die neue Regex besser, weil weniger Backtrackings vollzogen werden müssen. Beim letzten Beispiel (kein Treffer) sind die Resultate überall gleich, weil in allen Fällen sämtliche Permutationen geprüft werden.

  

Lösung 14

Was gibt das folgende Programmstück aus?

Antwort zum Problem von Was ist das für ein Kontext?:

Das Programmbeispiel erzeugt folgende Ausgabe:

WHILE raus ist Eene.
WHILE raus ist Meene.
WHILE raus ist Muh.
  
IF raus ist Eene.
  
FOREACH raus ist Muh.
FOREACH raus ist Muh.
FOREACH raus ist Muh.

Wenn in der print-Anweisung der foreach-Schleife $_ statt $& ausgegeben würde, wäre das Resultat das Gleiche wie bei der while-Schleife. In der gegebenen foreach-Schleife wird jedoch der Rückgabewert von m/.../g, die Liste ('Eene', 'Meene', 'Muh'), nicht benutzt. Nur der Nebeneffekt $& wird ausgegeben – das weist auf einen Programmierfehler hin, denn die Nebeneffekte von m/.../g im Listenkontext sind meist zu nichts nütze.

  

Lösung 15

Mehrfache Muster und die Ein-Argument-Version von find()

Antwort zum Problem von Ein Matcher zum Prüfen von HTML mit mehreren Pattern:

Wegen des erwähnten Fehlers verschiebt sich die »aktuelle Position« der Regex-Maschine um ein Zeichen, und so beginnt das nächste find am falschen Ort. Durch die Verwendung der Ein-Argument-Version von find und mit explizitem Nachführen eines Zeigers lässt sich das Problem umgehen. Änderungen im Vergleich zum Programmbeispiel von Ein Matcher zum Prüfen von HTML mit mehreren Pattern sind hervorgehoben.

Pattern pAmEnde   = Pattern.compile("\\G\\z");
Pattern pWort     = Pattern.compile("\\G\\w+");
Pattern pKeinHtml = Pattern.compile("\\G[^\\w<>&]+");
Pattern pImgTag   = Pattern.compile("\\G(?i)<img\\s+([^>]+)>");
Pattern pLink     = Pattern.compile("\\G(?i)<A\\s+([^>]+)>");
Pattern pLinkX    = Pattern.compile("\\G(?i)</A>");
Pattern pEntity   = Pattern.compile("\\G&(#\\d+|\\w+);");
boolean needClose = false;
Matcher m = pAmEnde.matcher(html); // Matcher kann man mit jedem Pattern erzeugen.
Integer aktuellePos = 0;           // Suche beginnt am Anfang des Strings.
while ( aktuellePos < html.length()) {
   if (m.usePattern(pWort).find(aktuellePos)) {
      // Ein Wort oder eine Zahl in m.group() -- auf Obszönitäten usw. prüfen ...
   } else if (m.usePattern(pKeinHtml).find(aktuellePos)) {
      // Weder Wort noch Zahl noch HTML -- zulassen.
   } else if (m.usePattern(pImgTag).find(aktuellePos)) {
      // Ein IMG-Tag -- Inhalt auf Zulässigkeit prüfen ...
   } else if (! needClose && m.usePattern(pLink).find(aktuellePos)) {
      // Ein Link -- den wir hier überprüfen können ...
      needClose = true;
   } else if (needClose && m.usePattern(pLinkX).find(aktuellePos)) {
      System.out.println("/LINK [" + m.group() + "]");
      needClose = false;
   } else if (m.usePattern(pEntity).find(aktuellePos)) {
      // Entities wie &gt; oder &#123; zulassen.
   } else {
      // Bis hier nichts erkannt; also Fehler. Wir holen ein Dutzend Zeichen aus dem
      // HTML-Text ab der aktuellen Position und geben damit eine Fehlermeldung aus.
      m.usePattern(Pattern.compile("\\G(?s).{1,12}")).find(aktuellePos);
      System.out.println("Unerwartetes HTML bei '" + m.group() + "'");
      System.exit(1);
   }
    aktuellePos = m.end(); // Die neue aktuelle Position ist das Ende des letzten Treffers.
}
if (needClose) {
   System.out.println("Kein </A> am Ende.");
   System.exit(1);
}

Die hier verwendete Version von find setzt allerdings die Region zurück, was das Original nicht tut. Wenn das ein Problem sein sollte, kann man die Region selbst nachführen und bei jedem find die region-Methode verwenden:

m.usePattern(pWort).region(anfang, ende).find(aktuellePos)

 

  

Lösung 16

Ändert sich dadurch die Ausgabe?

Antwort zum Problem von preg_replace: Reihenfolge der Elemente im Array

Das gezeigte Programmstück ergibt die folgende Ausgabe (auf einer Zeile; hier umbrochen):

Wort<Dieser> Wort<Satz> Wort<hat> Wort<Zahl><8> Wort<Wörter> Wort<und> Wort<Zahl><39> Wort<Zeichen>

Wenn man bei der Funktion preg_replace mehrere reguläre Ausdrücke (als pattern-Array) angibt, werden diese nicht parallel, sondern der Reihe nach verarbeitet. Jede Regex wird auf das Resultat des vorherigen Suchen-und-Ersetzen-Durchgangs angewandt.

In diesem Beispiel werden im ersten Durchgang zwei Zahl<...>-Sequenzen eingefügt. Aber diese ›Zahl‹-Strings werden im nächsten Zyklus wieder als Wörter erkannt und durch ›Wort<Zahl>‹ ersetzt, was die obige Ausgabe ergibt, die Sie vielleicht überrascht.

Die Moral von der Geschicht’ ist, dass die Reihenfolge der regulären Ausdrücke in einem pattern-Array bei preg_replace sehr wohl eine Rolle spielt.

  

Lösung 17

Was wird von den Klammern eingefangen, wenn ˹^.*([0-9]+)˼ auf ›Copyright 2003.‹ angewendet wird?

Antwort zum Problem von Wer zuerst kommt, mahlt zuerst.

Die Absicht war, die letzte ganze Zahl im String zu erwischen, aber das geht schief. Wie vorher muss ˹.*˼ Zeichen zurückgeben, weil das folgende ˹[0-9]+˼ Zeichen erzwingt. In diesem Fall bedeutet das ein »Un-Matching« des Satzpunktes und der Ziffer 3, dann kann das ˹[0-9]˼ passen. Dieses wird vom ˹+˼ quantisiert; ein Zeichen (das Minimum) wurde bereits erkannt, das nächste Zeichen im String ist der Satzpunkt ›.‹, der passt aber nicht.

Im Gegensatz zu vorher gibt es nun aber keine Elemente mehr, die passen müssen, also ist ˹.*˼ nicht gezwungen, weitere Zeichen (die 0) abzugeben. Wenn es das täte, wäre das nachfolgende ˹[0-9]+˼ sicher ein dankbarer Abnehmer, aber hier wird nach dem Prinzip »Wer zuerst kommt, mahlt zuerst« verfahren. Gierige Elemente sind auch geizig: Wenn sie einmal etwas genommen haben, geben sie es nur unter Druck zurück, nicht aber, um nur nett zu anderen zu sein. Letztendlich wird in $1 nur gerade die 3 am Ende abgespeichert.

Falls das schwierig zu begreifen ist, vergleichen Sie ˹[0-9]+˼ mit ˹[0-9]*˼, von dem es nur einen Schritt entfernt ist. ˹[0-9]*˼ gehört zum selben Verein wie ˹.*˼. Wenn wir das in unserem Ausdruck ˹^.*([0-9]+)˼ ersetzen, erhalten wir ˹^.*(.*, das dem Beispiel ˹^Subject:(.*).*˼ verdächtig ähnlich sieht, da bekam das ˹.*˼ am Ende überhaupt nichts ab.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln