Vorsicht bei my-Variablen in Codemustern

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

Wenn Sie eine my-Variable außerhalb einer Regex deklariert haben und diese innerhalb der Regex benutzen, müssen Sie sich über die heiklen Punkte beim Binden von Variablen in Perl im Klaren sein. Ich hatte früher empfohlen, im Zweifelsfall in Codemustern globale Variablen zu benutzen; diesen Rat lassen wir für die folgenden Überlegungen beiseite. Dennoch ist der folgende Abschnitt keine leichte Kost.

Das folgende konstruierte Beispiel soll das Problem veranschaulichen:

sub OptimiererTesten
{
  
    my $text  = shift; # Das erste Argument ist der Suchtext.
    my $start = undef; # Hier speichern wir, ab welcher Position die Suche beginnt.
  
    my $match = $text =~ m{
      (?{ $start = $-[0] if not defined $start}) # Erste Startposition abspeichern.
      \d # Die eigentliche Regex.
    }x;
    if (not defined $start) {
        print "Die ganze Mustersuche wurde wegoptimiert.\n";
        if ($match) {
            # Kann nicht passieren!
            print "Uh! Dennoch ein Treffer gefunden! Das kann nicht passieren!\n";
        }
    } elsif ($start == 0) {
        print "Der Match-Beginn wurde nicht optimiert.\n";
    } else {
        print "Der Optimierer hat die Regex bei Position $start angesetzt.\n"
    }
}

Das Programm enthält drei my-Variablen, aber hier interessiert nur $start, die anderen werden nicht innerhalb eines Codemusters verwendet. Zunächst wird $start auf den undefinierten Wert gesetzt, dann wird die Mustersuche ausgeführt. Die erste Komponente in der Regex ist ein Codemuster, das sich die Anfangsposition der Suche in $start merkt, aber nur, wenn diese Variable noch nicht definiert ist. Die »Startposition des Versuchs« wird dem ersten Element des Arrays $-[0] entnommen (siehe bei Durch das Matching gesteuerte Spezialvariablen).

Wenn die Funktion mit

OptimiererTesten("test 123");

aufgerufen wird, erhalten wir das erwartete Resultat:

Der Optimierer hat die Regex bei Position 5 angesetzt.

Wenn wir genau den gleichen Aufruf noch einmal machen, ergibt sich beim zweiten Mal:

Die ganze Mustersuche wurde wegoptimiert.
Uh! Dennoch ein Treffer gefunden! Das kann nicht passieren!

Obwohl Suchtext und Regex identisch sind, erhalten wir ein anderes Resultat, und im zweiten Fall ist die Antwort falsch. Warum ist das so? Im zweiten Durchgang bezieht sich das $start im Codemuster auf eine Variable aus dem ersten Durchgang, als die Regex kompiliert wurde. Das $start im Rest der Subroutine ist allerdings eine neue Variable, die beim Ausführen der my-Anweisung am Anfang der Funktion bei jedem Aufruf erzeugt wird.

Das Problem wird durch das Variablen-Binding von my-Variablen verursacht. my-Variablen in Codemustern bleiben an die spezifische Speicherstelle gebunden, an der sie zur Kompilationszeit der Regex erzeugt wurden. (Das Kompilieren von regulären Ausdrücken wird unter Regex-Kompilierung, der /o-Modifikator, qr/.../ und Effizienz behandelt.) Bei jedem Aufruf von OptimiererTesten wird eine neue Variable $start (oder eine neue »Instanz« dieser Variablen) angelegt, aber das $start innerhalb des Codemusters bezieht sich aus undurchsichtigen Gründen immer noch auf die Version, die zur Kompilationszeit gültig war. Die Variable im Rest der Funktion erhält also nicht den Wert, der scheinbar innerhalb der Regex darin abgespeichert wurde.

Diese Art von Variablen-Binding nennt man Closure. In Programmieren mit Perl oder Object Oriented Perl werden Closures und nützliche Anwendungen davon beschrieben. In diesem Fall besteht aber auch unter den Perl-Entwicklern Uneinigkeit, ob hier eine Closure wirklich nützlich oder nur einfach für viele Leute verwirrend ist.

Als Lösung kann ich nur empfehlen, in Codemustern keine außerhalb der Regex deklarierten my-Variablen zu benutzen, es sei denn, das Regex-Literal wird mindestens so oft neu kompiliert, wie die Variable außerhalb der Regex neu erzeugt wird. Im Programmbeispiel Benannte Unterausdrücke imitieren wird die my-Variable $VerschachtelRegex in der Subroutine RegexUmbauen verwendet. Das ist dort aber unproblematisch, weil die Variable $VerschachtelRegex nur ein einziges Mal erzeugt wird; es gibt nur eine Instanz dieser Variablen. Das my tritt nicht in einer Schleife auf und wird daher beim Start des Moduls genau einmal ausgeführt. Diese »Instanz« bleibt über die gesamte Laufzeit des Programms erhalten.

  

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