Die nächste Generation (Linux-Magazin, August 2000)

Mit Perl 5.6.0, das seit einiger Zeit auf www.perl.com bereit steht, zogen einige interessante Neuerungen in den Perlkern und die beiliegenden Module ein.

Während [1] oder [2] einen Überblick über die wichtigsten Änderungen bieten, soll es heute um die praktische Anwendung einiger ausgesuchter neuer Gimmicks gehen.

Reguläre Ausdrücke

Wer nach einem Treffer eines regulären Ausdrucks wissen will, welche Stellen des untersuchten Strings zum Erfolg führten, dem kommen die neuen Arrays @- und @+ gerade recht. Gerüchten zufolge wird nun die Perl-Entwicklung eingestellt, da keinerlei Sonderzeichen mehr für die Namen von Spezialvariablen zur Verfügung stehen ([3]).

Das erste Element von @- schreibt sich traditionsgemäß als $-[0] und gibt die Indexposition des Teilstrings an, auf den der reguläre Ausdruck ansprach. Das erste Element von @+ zeigt auf die Indexposition kurz nach dem Ende des Treffers:

    if( "123" =~ /\d/ ) {
        print "Match $-[0]..$+[0]", "\n";
    }

Der reguläre Ausdruck /\d/ passte schon auf die erste Ziffer von "123", und dementsprechend fällt das Ergebnis aus:

    Match 0..1

An Indexposition 0 (oder auch: Offset 0 ) steht die Ziffer 1, die den Treffer auslöste. Das Ende des Treffers ist direkt dahinter, da der reguläre Ausdruck nur eine einzelne Zahl suchte: Deswegen steht $+[0] auf 1. Hier ein Ausdruck, der sich mit dem Modifizierer /g Stück für Stück durch einen String hangelt:

    my $string = "Abc, die Katze liegt im Schnee.";
                # 0123456789012345678901234567890
    
    while( $string =~ /\w+/g ) {
        printf "%-6s (%2d .. %2d)\n", 
               $&, $-[0], $+[0];
    }

Er sucht mit \w+ zusammenhängende Wörter und gibt Aufschluss über die Einschlagstellen:

    Abc    ( 0 ..  3)
    die    ( 5 ..  8)
    Katze  ( 9 .. 14)
    liegt  (15 .. 20)
    im     (21 .. 23)
    Schnee (24 .. 30)

Das Beispiel-Snippet verwendet die Spezial-Perl-Variable $&, die nach einem Match den gematchten String enthält. Wie in [5] erläutert, hat dies aber Performanceeinbußen zur Folge. Ginge es um Geschwindigkeit, könnten wir dies mit unseren neuerlangten Kenntnissen aber auch mit substr( $string, $-[0], $+[0] - $-[0] ) ausdrücken, denn die Länge des Treffers steht mit $+[0] - $-[0] fest.

Nun sind @- und @+ Arrays und bieten demnach Raum für weitere Informationen. Sie speichern nicht nur die Trefferdaten, sondern auch die Positionen der definierten Untergruppen in dem verwendeten regulären Ausdruck:

    $string = "012";
    
    if( $string =~ /(\d)(\d)(\d)/ ) {
    
        for $i (0..$#-) {
            print "$i: $-[$i]..$+[$i]\n";
        }
    }

Die Längen der Arrays @- und @+ stehen mit $#- und $#+ fest. Jede der einzelnen (\d)-Gruppen passt auf eine der Ziffern in "012" und demnach geben die Werte in @- und @+ folgende Daten bekannt:

    0: 0..3
    1: 0..1
    2: 1..2
    3: 2..3

Der Gesamtmatch fand also im Bereich der Indexpositionen 0 bis 2 statt -- die Einträge in @+ zeigen, wie schon erwähnt, jeweils auf die Position nach dem entsprechenden Match.

Die erste Untergruppe fand sich auf Position <0 >, die zweite Untergruppe auf 1 und die dritte auf 2. Jede der Untergruppen ist genau ein Zeichen lang.

``Unsere'' Variablen

Zusätzlich zu my gibt es nun our, um den Gültigkeitsbereich von Variablen zu definieren. Während my eine lokale Variable im aktuellen Block, der aktuellen Datei oder in einem eval-Konstrukt definiert, legt die Anweisung

   our $variable;    # Globale Variable $variable

fest, dass $variable sich auf eine globale Variable bezieht, die im aktuellen Gültigkeitsbereich (Block, Datei, eval) unter $variable ansprechbar ist. So können sich beispielsweise zwei Funktionen eine globale Variable teilen, die das Hauptprogramm gar nicht kennt:

    use strict;
        
    funktion();
    andere_funktion();
        
    sub funktion {
       our $variable = "ABC";
       print "$variable\n";
    }
        
    sub andere_funktion {
       our $variable;
       print "$variable\n";
    }

Nun bot Perl schon immer globale Variablen an -- lässt man my einfach weg, dehnt dies den Gültigkeitsbereich schlagartig auf das gesamte package aus. our stuft da feiner ab: Die Variable ist zwar global, ansprechen lässt sie sich aber nur im lexikalischen Scope der our-Deklaration -- im Beispiel oben steht $variable zwar innerhalb funktion und andere_funktion zur Verfügung, aber weder eine andere Funktion, die $variable nicht als our deklariert noch das Hauptprogramm können auf $variable zugreifen.

our funktioniert auch mit der Direktive use strict vars, die perl sonst meckern lässt, falls globale Variablen zum Einsatz kommen, ohne dass der jeweilige Package-Name mit erwähnt wird.

Bislang diente die Direktive use vars dazu, globale Variablen so abzusegnen, dass use strict Ruhe gab -- diese Aufgabe übernimmt nun our. Und es kann noch mehr: Während use vars an Package-Grenzen abprallte, springt our auch darüber hinweg, solange sich der Zirkus in der gleichen Datei abspielt:

    package Erstes;
    our $variable = "ABC";   # Deklariert $Erstes::variable
    
    package Zweites;
    print "$variable\n";     # Greift auf $Erstes::variable zu

Genau wie my erfordert our für die Deklaration mehrerer Variablen Klammern:

    our $variable;
    our ( $erste, $zweite );

In der Programmierung mit Modulen kommt our zum Zug, wenn es um Modul-Variablen mit besonderem Auftrag geht: @ISA, @EXPORT, $VERSION und Konsorten sind global, für sie kommt our gerade recht. Das Programm h2xs, das perl beiliegt und mit dem man mit

    h2xs -Axn Module

das Gerüst eines neuen Moduls erzeugen kann, schreibt daher schon our statt dem früher verwendeten use vars:

    package Module;
    
    require 5.005_62;
    use strict;
    use warnings;
    
    require Exporter;
    require DynaLoader;
    
    our @ISA = qw(Exporter DynaLoader);
    
    our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
    our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
    our @EXPORT = qw( );
    our $VERSION = '0.01';

exists für Array-Elemente und Funktionen

exists prüft seit neuestem, ob ein Arrayelement initialisiert wurde oder nicht. Auch wenn ein Element den Wert undef aufweist, existiert es, denn es gilt als gesetzt:

    my @array = (1, undef, 3);
    
        # $array[1] existiert!
    print "Existiert!\n" if exists( $array[1] );

Die define-Funktion hingegen prüft, ob ein Arrayelement initialisiert wurde und einen Wert ungleich undef aufweist:

    my @array = (1, undef, 3);
    
       # $array[1] nicht definiert (undef)!
    print "Nicht definiert!\n" if ! defined( $array[1] );

Erst ein mit delete zerstörtes Arrayelement veranlasst exists, einen falschen Wert zurückgeben:

    my @array = (1, 2, 3);
    delete $array[1];
    
        # $array[1] existiert nicht mehr!
    print "Existiert nicht!\n" if ! exists( $array[1] );

Neben Hash- und Arrayelementen kann Perl auch Funktionen daraufhin überprüfen, ob sie deklariert bzw. definiert wurden. defined liefert zu einer definierten Funktion einen wahren Wert zurück:

        # funktion ist definiert!
    print "Definiert!\n" if defined &funktion;
    
    sub funktion { print "Hey!\n"; };

defined &function ruft hierbei die Funktion funktion nicht auf, sondern sieht lediglich nach, ob sie definiert wurde, ob also ein Aufruf der Funktion erfolgreich verliefe. Perl erlaubt es allerdings auch, Funktionen nur zu deklarieren, ohne auch notwendigerweise zu definieren, was sie denn genau tun sollen. Diesen Fall prüft exists:

    sub funktion;
        # funktion ist deklariert (aber nicht definiert)
    print "funktion deklariert!\n" if exists &funktion;

Im vorliegenden Fall würde der Aufruf von funktion jedoch zu einem Laufzeitfehler führen, da vergessen wurde, den Funktionsrumpf zu definieren. Auch exists ruft eine ihr übergebene Funktion nicht auf, sondern sieht nur nach, ob diese irgendwo deklariert oder definiert wurde. defined hätte im obigen Fall übrigens einen falschen Wert geliefert, da die Funktion keineswegs definiert, sondern nur deklariert wurde.

Maßgeschneiderte Warnungen

Im warnungsempfindlichen Modus gibt Perl nützliche Hinweise aus, falls es meint, typische Fallen im Code zu entdecken. Mit dem Schalter -w beim perl-Aufruf oder auch mit der Spezial-Variable $^W liessen sich Warnungen auch bisher schon ein- oder ausschalten. Doch dies war nur global möglich oder zumindest nur dynamisch ge-scoped mit local $^W.

Lexikalisch begrenzte Warnungseinstellungen, die nur innerhalb des jeweiligen Blocks gelten, erleichtern es, bestimmte Ausnahmeteile des Codes gegen Warnungen unempfindlich zu machen. Auch schwappen gesetzte Warnungen nicht mehr in Nachbarmodule über -- jedes kann sein eigenes Warnungsverhalten definieren. Perl unterscheidet zwischen Compiletime- und Runtime-Warnungen. Compiletime-warnungen meldet Perl noch bevor es den Code ausführt. Manipulationen an $^W für diese Nachrichten mussten deshalb bisher in einem <BEGIN>-Block vorgenommen werden. Das neue Pragma

    use warnings;

schaltet sowohl Compile- wie auch Runtime-Warnungen auf Blockebene ein und ersetzt den Schalter -w. Im Gegensatz dazu nimmt

    no warnings;

alles wieder zurück und unterdrückt die Nachrichten im lokalen Block wieder. Dabei sind Perls Warnungen in Kategorien unterteilt, die sich jeweils einzeln ein- oder ausschalten lassen. Die Top-Kategorie all enthält alle in Tabelle 1 aufgelisteten Unterkategorien, die wiederum teilweise weitere Unterkategorien enthalten. So bezieht sich beispielsweise die Kategorie io auf alle Warnungen, die mit I/O-Operationen zusammenhängen. Die Unterkategorie closed hingegen zielt nur auf diejenigen Warnungen der io-Kategorie, die mit geschlossenen Filehandles in Verbindung stehen.

Tabelle 1: Perls Kategorien und Subkategorien von Warnungen

    chmod 
    closure 
    exiting 
    glob 
    io 
        closed/exec/newline/pipe/unopened
    misc 
    numeric 
    once  
    overflow 
    pack 
    portable 
    recursion
    redefine 
    regexp 
    severe 
        debugging/inplace/internal/malloc
    signal 
    substr
    syntax 
        ambiguous/bareword/deprecated/parenthesis/
        precendence/printf/prototype/qw/reserved/semicolon
    taint 
    umask 
    uninitialized 
    unpack 
    untie 
    utf8 
    void 
    y2k

Das Kommando perldoc perldiag bringt Perls Fehlermeldungen und Warnungen mitsamt schlüssiger Erklärungen und deren Kategorisierung zutage. Wie man weiss, gibt perl diese ausführlichen Meldungen aus, falls use diagnostics eingeschaltet ist.

Zur Nachricht print on closed filehandle, die perl meldet, falls print Daten auf ein bereits geschlossenes Filehandle ausgibt, steht in perldoc diagnostics beispielsweise:

         print() on closed filehandle %s
         (W closed) The filehandle you're printing on got itself
         closed sometime before now.  Check your logic flow.

Hier gibt (W closed) an, dass der Text zu einer Warnung der Kategorie closed gehört. Die in perldoc perllexwarn dargestellte Hierarchie weist closed der Kategorie io zu, die wiederum Teil der Gesamtheit aller Warnungen, der Kategorie all, ist.

Ein anderes Beispiel: Wer sein perl vor dem Übersetzen mit

    ./Configure -Accflags=-DPERL_Y2KWARN

konfiguriert hat, bekommt seit neuestem Warnungen ins Haus, falls sich im Code potentielle Jahr-2000-Fehler eingeschlichen haben. Steht irgendwo

    $string = "19" . $wert;

schreit perl bei aktiviertem use warnings entsetzt auf:

    Possible Y2K bug: about to append an integer to '19' at ./t.pl line 6.

Handelt es sich oben aber nicht um eine Kalenderrechnungsfehler, sondern um beabsichtigten Code, unterdrückt ein no warnings die unerwünschte Y2K-Warnung im aktuellen Block:

    use warnings;
    {   no warnings 'y2k';
        $string = "19" . $wert;
    }

Die Anweisung lässt aber alle übrigen bisher definierten Warnungskategorien aktiv. Existierte beispielsweise $wert bislang uninitialisiert, käme perl sogleich mit

    Use of uninitialized value in print at t.pl line 6.

daher. Mehrere Kategorien gleichzeitig nimmt das use warnings-Pragma in Form einer Liste entgegen. Die Anweisung

    use warnings qw(once uninitialized);

aktiviert die Warnungen für uninitialisierte (uninitialized) und nur einmal verwendete Variablen (once). Von der Kommandozeile aus (perl -w) oder im Shebang (#!/usr/bin/perl -w) schaltet -w aus Kompatibilitätsgründen weiterhin alle Warnungen ein, langfristig sollten Skripts aber use warnings statt dem Schalter verwenden. Dann springen die Warnungen auch an, falls jemand das Skript mit perl scriptname startet, obwohl der Warnungsschalter im Shebang gesetzt war. Einmal mit -w gesetzte Warnungen kann man übrigens wiederum mit no warnings und use warnings im aktuellen Block aus- bzw. wieder einschalten. Der Schalter -W hingegen lässt perl die Meldungen auf jeden Fall ausgeben -- sozusagen der lint der Perl-Welt. Umgekehrt bringt -X alle Warnungen zum Schweigen, auch die, die explizit angefordert wurden.

In die vorher erwähnten once-Kategorie fallen Meldungen der Art

    Name "main::opt_v" used only once: possible typo at ./once.pl line 12.

Folgendes Beispiel nutzt die getopts-Funktion des Moduls Getopt::Std, das zu Kommandooptionen wie -x die zugehörige Variable $opt_x setzt. $opt_x kommt dabei nur einmal zum Einsatz -- aber das geht in Ordnung und deshalb hilft no warnings 'once' in handle_options (und nur dort und nicht im Hauptprogramm oder anderen Funktionen!) die Warnung zu unterdrücken:

    use warnings;
    use Getopt::Std;
    
    handle_options();
    print "Verbose is ", $VERBOSE ? "on" : "off", "\n";
    
    sub handle_options {
       no warnings 'once';
       getopts('v');
       $VERBOSE = $opt_v;
    }

Bei den once-Warnungen handelt es sich übrigens um Warnungen zur Compilezeit -- während die vorher erwähnten uninitialized-Warnungen zur Laufzeit erfolgen. Beide Varianten koexistieren friedlich nebeneinander.

Als besonderen Gimmick kann use warnings mit dem FATAL-Parameter bestimmte Warnungen auch so eskalieren, dass sie zum Programmabbruch führen:

    use warnings FATAL => 'substr';
    print substr("abc", 5, 1), "\n";
    print "Ende\n";

Hier bricht das Programm in substr mit einer Fehlermeldung ab, da substr mit Offset 5 weit über den drei Zeichen langen String hinausgreift. Die Ausgabe ``Ende'' wird gar nicht mehr erreicht, da perl das Programm in Zeile 2 bereits abbrach.

Die unter [4] aufgelisteten Manualseiten zeigen detailliert, was man mit den neuen Warnungen noch anstellen kann. Doch genug für heute! Nächstes Mal geht's in die vielsprachige Welt des Unicode, in der perl neuerdings auch kräftig mitmischt. Viel Erfolg beim Perl-Upgrade -- es lohnt sich!

Referenzen

[1]
Christian Kirsch, ``Uniperl'', iX 05/00, Seite 178, http://www.ix.heise.de/ix/artikel/2000/05/178/

[2]
Liste der Neuerungen auf der Manualseite perldoc perldelta

[3]
MikeGTN, ``Perl is finished'', http://www.segfault.org/story.phtml?mode=2&id=3905b40e-05c0a760

[4]
Manualseiten zu den lokalisierbaren Warnungen: perldoc perllexwarn, perldoc warnings, perldoc perldiag

[5]
Jeffrey Friedl, Mastering Regular Expressions, O'Reilly, 1997

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.