Perl-Code in der Regex

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

Das Codemuster-Konstrukt ist besonders nützlich für das Debugging oder für das Sammeln von Informationen während der Mustersuche. Auf den nächsten Seiten führen einige einführende Beispiele zu einer Methode, mit der man in Perl das Verhalten eines POSIX-NFA nachbilden kann. Der Weg ist hier wie so oft interessanter als das Ziel (außer Sie benötigen aus irgendwelchen Gründen das POSIX-Verhalten).

Wir beginnen mit einfachen Methoden für das Debuggen von regulären Ausdrücken.

Codemuster, die während der Mustersuche Informationen ausgeben

Das kleine Programm

"abcdefgh" =~ m{
  (?{ print "Regex angesetzt bei [$`|$']\n" })
  (?:d|e|f)
}x;

erzeugt die folgende Ausgabe:

Regex angesetzt bei [|abcdefgh]
Regex angesetzt bei [a|bcdefgh]
Regex angesetzt bei [ab|cdefgh]
Regex angesetzt bei [abc|defgh]

Das Codemuster-Konstrukt ist das erste Element der Regex und führt daher jedes Mal

print "Regex angesetzt bei [$`|$']\n"

aus, wenn die Regex für einen neuen Matching-Versuch angesetzt wird. Bei der Ausgabe wird mit $` und $' (siehe Durch das Matching gesteuerte Spezialvariablen) (Anmerkung: Normalerweise rate ich aus Effizienzgründen davon ab, die speziellen Matching-Variablen $`, $& und $' zu benutzen, weil mit ihnen das ganze Programm ineffizienter werden kann (siehe Die Variablen $`, $& und $' sind »unartig«). Bei temporären Debugging-Anweisungen ist das aber bedeutungslos.) der Suchtext ausgegeben, das ›|‹ markiert dabei die aktuelle Position, also die Position, an der die Regex angesetzt wird. Das Getriebe schaltet dreimal weiter (siehe Das »Getriebe« schaltet zum nächsten Zeichen), bis die Regex an der vierten Position passt.

Wenn wir am Ende der Regex ein

(?{ print "Regex passt bei [$`<$&>$']\n" })

anfügten, würde nach den vier Zeilen Folgendes ausgegeben:

Regex passt bei [abc<d>efgh]

Vergleichen wir das mit dem nächsten Programmbeispiel, bei dem nur der Hauptteil ˹(?:d|e|f)˼ der Regex durch ˹[def]˼ ersetzt wurde:

"abcdefgh" =~ m{
  (?{  print "Regex angesetzt bei [$`|$']\n" })
  [def]
}x;

Eigentlich sollten die Resultate identisch sein, aber diesmal erhalten wir nur:

Regex angesetzt bei [abc|defgh]

Warum dieser Unterschied? Perl ist im zweiten Fall schlau genug und kann die »Erstes Zeichen«-Optimierung für die Regex ˹[def]˼ anwenden (siehe »Erstes Zeichen«-Optimierung). Das Getriebe kann damit die Positionen überspringen, bei denen ein Versuch ohnehin fehlschlagen würde. In diesem Fall wurden gleich alle Versuche bis zum letzten, erfolgreichen Versuch übersprungen; mit dem eingebauten Codemuster sehen wir, was während der Mustersuche passiert.

panic: top_env

Wenn Sie mit Codemustern oder dynamischen Konstrukten arbeiten und sich Ihr Programm mit einem abrupten

panic: top_env

verabschiedet, liegt das sehr wahrscheinlich an einem Syntaxfehler irgendwo in einem dieser Code-Konstrukte. Die aktuelle Version von Perl kann mit bestimmten Syntaxfehlern nicht sehr gut umgehen und gibt auf. Sie müssen dann den Fehler beseitigen.

Mit Codemustern alle möglichen Treffer ausgeben

Perl besitzt einen traditionellen NFA und hört daher mit der Suche auf, sobald ein Treffer gefunden wird, auch dann, wenn es vielleicht noch weitere Treffer gibt. Durch den raffinierten Einsatz des Codemuster-Konstrukts können wir Perl dazu überlisten, alle möglichen Treffer auszugeben. Dazu nehmen wir uns noch einmal das ›motorrad‹-Beispiel von Wirklich der längste Treffer vor:

"motorradfahren" =~ m{
    motor(rad)?(radfahren)?
   (?{ print "Treffer bei [$`<$&>$']\n" })
}x;

Das gibt das erwartete

Treffer bei [<motorrad>fahren]

aus. Die Regex passt also auf den unterstrichenen Teil von motorradfahren‹.

Es ist aber wichtig, sich klarzumachen, dass der Text »Treffer bei« eigentlich nicht den absoluten, endgültigen Treffer beschreibt, sondern den Treffer an diesem Punkt der Mustersuche. In diesem Fall ist das das Gleiche, weil das Codemuster-Konstrukt das letzte Element in der Regex ist. Wenn die Regex-Maschine dieses Konstrukt erreicht, hat sie schon alle Zeichen erkannt, die es zu erkennen gibt, und gibt den endgültigen Treffer aus.

Was würde passieren, wenn wir nach dem Codemuster-Konstrukt ein ˹(?!)˼ einfügten? ˹(?!)˼ ist ein negatives Lookahead-Konstrukt, das immer einen lokalen Fehlschlag zurückgibt. Dieser Fehlschlag tritt erst nach dem Codemuster-Konstrukt auf (gerade nachdem eine »Treffer bei«-Zeile ausgegeben wurde) und zwingt die Maschine, mittels Backtracking nach einem anderen Treffer zu suchen. Dieser Fehlschlag wird bei jedem ausgegebenen Treffer ausgelöst, dadurch werden alle möglichen Pfade abgesucht, und es werden alle möglichen Treffer ausgegeben:

Treffer bei [<motorrad>fahren]
Treffer bei [<motorradfahren>]
Treffer bei [<motor>radfahren]

Wir haben sichergestellt, dass jeder eigentlich mögliche, globale Treffer zu einem Fehlschlag wird, und so die Maschine gezwungen, alle Treffer zu finden und sie auszugeben. Ohne das ˹(?!)˼ gibt Perl den ersten Treffer zurück, mit ˹(?!)˼ werden auch die anderen Möglichkeiten ausgegeben.

Was gibt also das folgende Programmstück Ihrer Meinung nach aus?

"123" =~ m{
   \d+
   (?{ print "Treffer bei [$`<$&>$']\n" })
   (?!)
}x;

Das Programm erzeugt die folgende Ausgabe:

Treffer bei [<123>]
Treffer bei [<12>3]
Treffer bei [<1>23]
Treffer bei [1<23>]
Treffer bei [1<2>3]
Treffer bei [12<3>]

Die ersten drei Zeilen werden Sie wohl erwartet haben, aber die folgenden drei nur, wenn Sie wirklich gut aufgepasst haben. Das (?!) erzwingt ein Backtracking und damit die zweite und dritte Zeile. Wenn nach einem weiteren Backtracking-Schritt der Versuch am Anfang der Zeile fehlschlägt, schaltet das Getriebe weiter und setzt in einem zweiten Versuch die Maschine an der Position nach dem ersten Zeichen im Suchstring an. Aus diesem Versuch ergeben sich die Zeilen vier und fünf; die letzte Zeile stammt vom dritten Versuch.

Mit dem (?!) zwingen wir die Regex wirklich dazu, alle möglichen Treffer auszugeben, nicht nur die von einem bestimmten Anfangspunkt aus. Unter Umständen wäre aber gerade das interessant; wie das geht, werden Sie in Kürze sehen.

Den längsten Treffer finden

Statt nur alle möglichen Treffer auszugeben, möchten wir jetzt den längsten Treffer ermitteln und speichern. Wir können den bisher längsten Treffer einfach in einer Variablen abspeichern und jeden neuen möglichen Treffer damit vergleichen. Mit unserem ›motorrad‹-Beispiel bekommen wir das folgende Programm.

$laengster_treffer = undef; # Hier speichern wir den bisher längsten Treffer.

"motorradfahren" =~ m{
   motor(rad)?(radfahren)?
   (?{
      # Überprüfen, ob der aktuelle Treffer ($&) der bisher längste ist.
      if (not defined($laengster_treffer)
          or
          length($&) > length($laengster_treffer))
      {
          $laengster_treffer = $&;
      }
   })
   (?!) # Fehlschlag erzwingen und mit Backtracking andere mögliche »Treffer« finden.
}x;

# Resultat ausgeben, falls ein Treffer gefunden wurde.
if (defined($laengster_treffer)) {
   print "Längster Treffer: [$laengster_treffer]\n";
} else {
   print "Kein Treffer\n";
}

Das ergibt das erwartete ›Längster Treffer: [motorradfahren]‹. Dieses Codemuster ist ziemlich lang, und weil wir es später wieder benötigen, verpacken wir es zusammen mit dem ˹(?!)˼ in ein eigenes Regex-Objekt:

(?{
      # Überprüfen, ob der aktuelle Treffer ($&) der bisher längste ist.
      if (not defined($laengster_treffer)
          or
          length($&) > length($laengster_treffer))
      {
          $laengster_treffer = $&;
      }
   })
   (?!) # Fehlschlag erzwingen und mit Backtracking andere mögliche »Treffer« finden.
}x;

Im folgenden einfachen Beispiel wird ›9938‹, der absolut längstmögliche Treffer, gefunden:

$laengster_treffer = undef; # Hier speichern wir den bisher längsten Treffer.
"800-998-9938" =~ m{  \d+  $LaengstenTrefferSpeichern  }x;

# Resultat ausgeben, falls ein Treffer gefunden wurde.
if (defined($laengster_treffer)) {
   print "Längster Treffer: [$laengster_treffer]\n";
} else {
   print "Kein Treffer\n";
}

Den längsten frühesten Treffer finden

Wir wissen nun, wie wir den absolut längsten Treffer finden, aber wir möchten jetzt den längsten Treffer unter denjenigen finden, die am weitesten links beginnen. Das ist nämlich der Treffer, den ein POSIX-NFA fände (siehe Der »längste früheste Treffer«). Damit das klappt, müssen wir das Weiterschalten des Getriebes verhindern, sobald wir den ersten möglichen Treffer gefunden haben. Dieser erste Treffer ist vielleicht noch nicht der längste früheste Treffer, aber mit dem normalen Backtracking-Mechanismus finden wir alle möglichen Treffer von diesem Anfangspunkt aus und können uns den längsten merken.

In Perl gibt es keine Möglichkeit, das Getriebe direkt zu beeinflussen und das Weiterschalten zu verhindern. Wir können aber dasselbe erreichen, indem wir die Regex an der Stelle verankern, an der wir den ersten Treffer gefunden haben, sobald die Variable $laengster_treffer einen definierten Wert besitzt. Mit ˹(?{ defined $laengster_treffer })˼ können wir das überprüfen, aber ein bloßer Test reicht nicht; wir benötigen eine Regex. Den Schlüssel zu diesem Problem finden wir in dem bedingten ˹(? if then|else-Konstrukt.

Perl-Code in einem bedingten Regex-Konstrukt

Damit die Regex-Maschine auf unseren Variablen-Test reagiert, verwenden wir den Test im Bedingungsteil eines ˹(? if then|else-Konstrukts (siehe Bedingte reguläre Ausdrücke). Wir wollen außerdem, dass die Maschine aufhört, sobald die Variable definiert ist. Daher verwenden wir im then-Teil das »Fehlschlag jetzt!«-Konstrukt (?!). (Den else-Teil brauchen wir nicht, wir lassen ihn einfach weg.) Das bedingte Regex-Konstrukt wird in das folgende Regex-Objekt verpackt:

my $BeimErstenTrefferAufgeben = qr/(?(?{ defined $laengster_treffer})(?!))/;

Der if-Teil ist (?{ defined $laengster_treffer}), und der then-Teil ist (?!). Zusammen mit dem $LaengstenTrefferSpeichern-Objekt erhalten wir:

"800-998-9938" =~ m{ $BeimErstenTrefferAufgeben \d+ $LaengstenTrefferSpeichern }x;

Damit wird mit ›800‹ der POSIX-Treffer gefunden, der »längste Treffer unter denen, die am weitesten links beginnen«.

  

<< 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