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.
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.
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 Funktionenexists
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.
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.
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!
perldoc perldelta
MikeGTN
, ``Perl is finished'',
http://www.segfault.org/story.phtml?mode=2&id=3905b40e-05c0a760
perldoc perllexwarn
,
perldoc warnings
,
perldoc perldiag
Michael Schilliarbeitet 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. |