local in einem Codemuster

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

In einem Codemuster bekommt local eine neue Bedeutung. Um der folgenden Beschreibung folgen zu können, müssen Sie dynamische Geltungsbereiche (siehe Werte mit dynamischem Geltungsbereich) und die »Brotbröcklein«-Analogie zum Vorgehen einer Regex-gesteuerten NFA-Maschine aus Wie Regex-Maschinen arbeiten wirklich verstanden haben. Das folgende, ziemlich konstruierte – und wie Sie sehen werden: fehlerhafte – Beispiel illustriert das Konzept ohne viel Drumherum noch einmal. Die Regex überprüft, ob eine Zeile nur aus ˹\w+˼ und ˹\s+˼ besteht, und zählt gleichzeitig, wie viele von den ˹\w+˼ in Wirklichkeit ˹\d+\b˼ sind.

my $Anzahl = 0;

$text =~ m{
   ^ (?> \d+ (?{ $Anzahl++ }) \b | \w+ | \s+ )* $
}x;

Wenn die Regex auf einen String wie ›123abc739271xyz‹ angewendet wird, werden in $Anzahl drei Zahlen gezählt. Bei ›123abc73xyz‹ dagegen werden statt nur einer zwei Zahlen gefunden. Die Variable $Anzahl wird inkrementiert, sobald ›73‹ erkannt wurde, aber das ˹\b˼ danach kann nicht passen. Die Alternative wird durch Backtracking verworfen, aber der Zähler wird nicht zurückgesetzt. Das Problem entsteht, weil einmal ausgeführter Code in einem Codemuster nicht »unausgeführt« werden kann, wenn der entsprechende Teil in der Regex durch Backtracking zurückgegeben wird.

Falls Sie durch die atomare Klammer ˹(?>...)˼ (siehe Atomare Klammern) verwirrt werden – diese ist nur dazu da, ein ewiges Matching zu verhindern (siehe Ewiges Matching mit atomaren Klammern vermeiden), und beeinflusst das Backtracking innerhalb der atomaren Klammer nicht, nur das Backtracking über diese Klammer hinaus. Das ˹\d+˼ kann also durch Backtracking sehr wohl zurückgegeben werden, wenn das darauffolgende ˹\b˼ nicht passt.

In diesem konstruierten Beispiel ist die Lösung ganz einfach: Das ˹\b˼ wird vor den Code gesetzt, der den Zähler $Anzahl enthält, so wird dieser nur inkrementiert, wenn eine wirkliche Zahl gefunden wurde, die nicht später zurückgegeben wird. Ich möchte dennoch eine andere Lösung zeigen, die die besonderen Eigenschaften von local ausnutzt, und damit das Verhalten von local in einer Regex illustrieren:

our $Anzahl = 0;

$text =~ m{
   ^ (?> \d+ (?{ local($Anzahl) = $Anzahl + 1 }) \b | \w+ | \s+ )* $
}x;

Zunächst wurde $Anzahl von einer my-Variablen zu einer globalen Variablen gemacht (unter use strict dürfen nicht vollständig qualifizierte globale Variablen nur dann verwendet werden, wenn sie mit our »deklariert« werden).

Der andere Unterschied ist, dass die Variable $Anzahl durch die local-Anweisung einen dynamischen Geltungsbereich bekommt. Und dies ist das besondere Verhalten von local in einem Codemuster: Wenn eine Variable in einer Regex lokalisiert wird, erscheint wieder der alte Wert, wenn das local wegen eines Backtrackings zurückgegeben wird. Also wird zwar local($Anzahl) = $Anzahl + 1 sofort ausgeführt ($Anzahl wird zu 2), wenn das ˹\d+˼ die zwei Ziffern ›73‹ erkannt hat, aber diese Änderung ist nur lokal für den gerade verfolgten Versuch wirksam. Wenn sich der Versuch als Fehlschlag erweist und die Maschine mittels Backtracking vor das local zurückgeht, wird damit auch der dynamische Geltungsbereich verlassen und $Anzahl erhält wieder seinen alten Wert von 1. Dieser Wert bleibt unverändert, bis ein Treffer gefunden wird.

Codemuster interpolieren

Aus Sicherheitsgründen lässt Perl normalerweise nicht zu, dass eine String-Variable in eine Regex interpoliert wird, wenn diese Variable ein Codemuster ˹(?{...})˼ oder ein dynamisches Regex-Konstrukt ˹(??{...})˼ enthält. (In einem Regex-Objekt wie in unserem $LaengstenTrefferSpeichern von Den längsten Treffer finden ist das allerdings erlaubt.)

m{ (?{ print "Suche beginnt\n" }) irgendeine Regex... }x;

ist also erlaubt, nicht aber so etwas wie:

my $Suchbeginn = '(?{ print "Suche beginnt\n" })';
     ...
m{ $Suchbeginn irgendeine Regex... }x;

Diese Einschränkung wurde mit Absicht eingeführt, weil in vielen existierenden Skripten Benutzereingaben zu regulären Ausdrücken umgeformt werden. Wenn mit diesen neuen Konstrukten plötzlich beliebiger Perl-Code ausgeführt werden könnte, würde sich ein riesiges Sicherheitsloch auftun. Normalerweise ist das also nicht erlaubt.

Wenn Sie diese Interpolation erlauben möchten, können Sie die Einschränkung mit

use re 'eval';

aufheben. (Mit anderen Argumenten kann man das Pragma use re sehr schön zum Debuggen von regulären Ausdrücken verwenden; siehe Debugging-Informationen zu regulären Ausdrücken.)

Benutzereingaben für die Interpolation desinfizieren

Wenn Sie die Interpolation zulassen und dennoch Benutzereingaben in der Regex haben möchten, müssen Sie sicherstellen, dass diese Benutzereingaben weder Codemuster noch dynamische Regex-Konstrukte enthalten. Dazu prüfen Sie die Eingabe mit ˹\(\s*\?+[p{]˼. Wenn ein Treffer gefunden wird, ist es gefährlich, diesen String in einer Regex zu verwenden. ˹\s*˼ ist wichtig, weil die Regex unter Umständen unter dem /x-Modifikator läuft. (Meiner Meinung nach sollten an dieser Stelle auch im Modus »Freie Form« keine Leerzeichen erlaubt sein, aber so ist es nicht.) Mit dem Plus nach dem Fragezeichen ˹\?˼ finden wir beide Konstrukte. Mit dem p erwischen wir auch das alte ˹(?p{...})˼-Konstrukt, den Vorläufer von ˹(??{...})˼, das nicht mehr verwendet werden sollte.

Ich denke, es wäre ganz nützlich, wenn Perl einen Modifikator hätte, mit dem man dieses Verhalten je nach Regex oder sogar für bestimmte Unterausdrücke ein- und ausschalten könnte. Im Moment müssen Sie das Pragma use re verwenden.

Wir benötigen also local, wenn wir die $Anzahl über die ganze Mustersuche konsistent halten wollen. Wenn wir ganz am Ende ein ˹(?{ print "Anzahl am Ende ist $Anzahl.\n" })˼ einfügten, würde die richtige Anzahl ausgegeben. Weil wir den Wert von $Anzahl auch nach der Mustersuche verwenden wollen, müssen wir ihn in einer nicht-lokalisierten Variablen abspeichern, weil alle während der Mustersuche erzeugten dynamischen Geltungsbereiche danach verlorengehen.

Hier ein Beispiel:

my $Anzahl = undef;
our $TmpAnzahl = 0;

$text =~ m{
   ^ (?> \d+ (?{ local($TmpAnzahl) = $TmpAnzahl + 1 }) \b | \w+ | \s+ )* $
   (?{ $Anzahl = $TmpAnzahl }) # End-$Anzahl in einer Nicht-"local"-Variablen speichern.
}x;
     
if (defined $Anzahl) {
   print "Anzahl ist $Anzahl.\n";
} else {
   print "Kein Treffer\n";
}

Das ist viel Arbeit für etwas so Einfaches, aber dies ist nur ein konstruiertes Beispiel, das lediglich demonstriert, wie sich local-Variablen in einer Regex verhalten. Eine praktische Anwendung werden Sie unter Benannte Unterausdrücke imitieren kennenlernen.

  

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