Perl Teil III

ArticleCategory: [Es gibt verschiedene Artikel Kategorien]

Software Development

AuthorImage:[Ein Bild von Dir]

[Photo of the Author]

TranslationInfo:[Author and translation history]

original in en Guido Socher

en to de Katja Socher

AboutTheAuthor:[Eine kleine Biographie über den Autor]

Guido ist ein langjähriger Linuxfan und Perlhacker. Seine Linux Homepage findet man unter www.oche.de/~bearix/g/.

Abstract:[Here you write a little summary]

Perl Teil I gab einen generellen Überblick über Perl. In Perl Teil II wurde das erste nützliche Programm geschrieben. In Teil III werden wir uns jetzt Felder (arrays) genauer anschauen.

ArticleIllustration:[This is the title picture for your article]

[Illustration]

ArticleBody:[The article body]

Felder (Arrays)

Ein Feld (array) besteht aus einer Liste von Variablen, auf die über einen Index zugegriffen werden kann. Wir haben gesehen, daß der Name von "normalen Variablen", die auch skalare Variablen genannt werden, mit einem Dollarzeichen ($) anfängt. Felder beginnen mit einem @-Zeichen, obwohl die Daten innerhalb eines Feldes aus mehreren skalaren Variablen bestehen. Man muß deshalb wieder ein Dollarzeichen schreiben, wenn man auf ein individuelles Feld in einem Feld verweist. Laßt uns ein Beispiel betrachten:

!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
# declare a new array variable:
my @myarray;
# initialize it with some data:
@myarray=("data1","data2","data3");
# access the first element (at index 0):
print "the first element of myarray is: $myarray[0]\n";
Wie du sehen kannst, schreiben wir @myarray, wenn wir auf das Gesamte und $myarray[0], wenn wir auf ein individuelles Element verweisen. Felder in Perl starten mit dem Index 0. Neue Indizes werden automatisch erzeugt, sobald Daten hinzugefügt werden. Du mußt zu der Zeit der Felddeklaration nicht wissen, wie groß dein Feld werden wird. Wie du oben sehen kannst, kann man Felder mit einem ganzen Bündel von Daten initialisieren, indem man die Daten durch Komma voneinander getrennt innerhalb von runden Klammern auflistet.
("data1","data2","data3")
ist wirklich ein anonymes Feld. Du kannst deswegen ("data1","data2","data3")[1]
schreiben, um das zweite Element dieses anonymen Feldes zu bekommen:
!/usr/bin/perl -w
print "The second element is:"
print ("data1","data2","data3")[1];
print "\n"

Schleifen über Feldern

Die foreach Schleife in Perl erlaubt eine Ausführung eines Befehls über alle Elemente. Sie funktioniert wie folgt:
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
my @myarray =("data1","data2","data3");
my $lvar;
my $i=0;
foreach $lvar (@myarray){
   print "element number $i is $lvar\n";
   $i++;
}
Laufenlassen des Programms ergibt:
element number 0 is data1
element number 1 is data2
element number 2 is data3
Der foreach Befehl nimmt jedes Element aus dem Feld heraus und steckt es in eine Schleifenvariable ($lvar in dem obigen Beispiel). Es ist wichtig zu beachten, daß die Werte nicht aus dem Feld in die Schleifenvariable kopiert werden. Stattdessen ist die Schleifenvariable eine Art Pointer und Verändern der Schleifenvariable verändert die Elemente im Feld. Das folgende Programm schreibt alle Elemente im Feld als Großbuchstaben. Der Perlausdruck tr/a-z/A-Z/ ist dem Unixbefehl "tr" ähnlich. Es übersetzt in diesem Fall alle Buchstaben in Großbuchstaben.
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
my @myarray =("data1","data2","data3");
my $lvar;
print "before:\n";
foreach $lvar (@myarray){
    print "$lvar\n";
    $lvar=~tr/a-z/A-Z/;
}
print "\nafter:\n";
foreach $lvar (@myarray){
    print "$lvar\n";
}
Wenn du das Programm laufen läßt, dann kannst du sehen, daß @myarray in der zweiten Schleife nur Großbuchstabenwerte enthält:
vorher:
data1
data2
data3

nachher:
DATA1
DATA2
DATA3

Die Kommandozeile

Wir haben in Perl II gesehen, daß eine Funktion &getopt dazu benutzt werden kann, die Kommandozeile zu lesen sowie irgendwelche Optionen, die auf der Kommandozeile eingegeben wurden. &getopt ist wie das Äquivalent in C. Es ist eine Bibliotheksfunktion. Die Werte der Kommandozeile werden in Perl an ein Feld namens @ARGV angehängt. &getopt nimmt nur dieses @ARGV und bewertet die Elemente.
Anders als in C ist der Inhalt des ersten Elements in dem Feld nicht der Programmname, sondern das erste Argument der Kommandozeile. Wenn du den Namen des Perlprogramms wissen willst, dann mußt du $0 lesen, aber das ist nicht das Thema dieses Artikels. Hier ist ein Beispielprogramm namens add. Es nimmt zwei Zahlen von der Kommandozeile und addiert sie:
> add 42 2
42 + 2 is:44
.... und hier ist das Programm:
#!/usr/bin/perl -w
# check if we have 2 arguments:
die "USAGE: add number1 number2\n" unless ($ARGV[1]);
print "$ARGV[0] + $ARGV[1] is:", $ARGV[0] + $ARGV[1] ,"\n";

Ein Stapel (stack)

Perl hat eine ganze Reihe von eingebauten Funktionen, die ein Feld als Stapel benutzen. Das folgende Programm fügt zwei Elemente zu einem bereits existierenden Feld hinzu:
#!/usr/bin/perl -w
my @myarray =("data1","data2","data3");
my $lvar;
print "the array:\n";
foreach $lvar (@myarray){
    print "$lvar\n";
}
push(@myarray,"a");
push(@myarray,"b");
print "\nafter adding \"a\" and \"b\":\n";
while (@myarray){
    print pop(@myarray),"\n";
}
Pop entfernt die Elemente vom Ende des Feldes und die while-Schleife läuft solange, bis das Feld leer ist.

Lesen von Verzeichnissen

Perl bietet die Funktionen opendir, readdir und closedir zum Auslesen des Inhalts eines Verzeichnisses. readdir gibt ein Feld mit allen Dateinamen zurück. Durch Benutzen einer foreach Schleife kannst du den Befehl für alle Dateinamen ausführen und nach einem bestimmten Namen suchen. Hier ist ein einfaches Programm, das nach einem bestimmten Dateinamen in dem gerade aktuellen Verzeichnis sucht:
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
die "Usage: search_in_curr_dir filename\n" unless($ARGV[0]);
opendir(DIRHANDLE,".")||die "ERROR: can not read current directory\n";
foreach (readdir(DIRHANDLE)){
    print"\n";
    print "found $_\n" if (/$ARGV[0]/io);
}
closedir DIRHANDLE;
Laß uns das Programm anschauen. Zuerst überprüfen wir, ob der Benutzer ein Argument in der Kommandozeile eingegeben hat. Wenn nicht, geben wir Benutzerinformationen aus und beenden das Programm. Als nächstes öffnen wir das gerade aktuelle Verzeichnis ("."). opendir ist ähnlich zu den offenen Funktionen für Dateien. Das erste Argument ist ein file descriptor, den du an die readdir und closedir Funktionen weitergeben mußt. Das zweite Argument ist der Pfad zu dem Verzeichnis.
Als nächstes kommt die foreach Schleife. Das erste Interessante ist, daß die Schleifenvariable fehlt. Perl tut in diesem Fall etwas Magisches für dich und erzeugt eine Variable namens $_ , die dann als Schleifenvariable benutzt wird. readdir(DIRHANDLE) gibt ein Feld zurück und wir benutzen foreach, um jedes Element zu betrachten. /$ARGV[0]/io vergleicht die regulären Ausdrücke, die in $ARGV[0] vorhanden sind, mit der Variablen $_. Das io bedeutet, daß die Suche zwischen Groß- und Kleinbuchstaben unterscheidet und die regulären Ausdrücke nur einmal kompiliert werden. Das letztere ist eine Optimierung, die das Programm schneller macht. Du kannst sie benutzen, wenn du eine Variable innerhalb des regulären Ausdrucks hast und du sicher sein kannst, daß sich diese Variable zur Laufzeit nicht verändert.
Laß es uns ausprobieren. Nehmen wir an, wir haben die Dateien article.html, array1.txt und array2.txt in dem aktuellen Verzeichnis, dann gibt die Schleife für "HTML" folgendes aus:
>search_in_curr_dir HTML
.
..
article.html
found article.html
array1.txt
array2.txt
Wie du sehen kannst, hat die readdir Funktion zwei weitere Dateien gefunden. "." und "..". Dies sind die Namen des gerade aktuellen und des vorherigen Verzeichnisses.

Ein Dateifinder

Ich würde diesen Artikel gerne mit einem etwas komplexeren und nützlicheren Programm abschließen. Es soll ein Dateifinderprogramm sein. Wir nennen es pff (perl file finder). Es soll grundsätzlich wie das Programm oben arbeiten, aber auch in Unterverzeichnissen suchen. Wie können wir ein solches Programm entwerfen? Oben haben wir einigen Code, der das aktuelle Verzeichnis einliest und darin nach Dateien sucht. Wir müssen mit dem gerade aktuellen Verzeichnis beginnen, aber wenn eine der Dateien (außer . und ..) wieder ein Verzeichnis ist, dann müssen wir darin suchen. Dies ist ein typischer rekursiver Algorithmus:
sub search_file_in_dir(){
  my $dir=shift;
  ...read the directory $dir ....
  ...if a file is again a directory 
    then call &search_file_in_dir(that file)....
}
Man kann in Perl testen, ob eine Datei ein Verzeichnis und nicht ein symlink zu einem Verzeichnis ist durch Benutzen von if (-d "$file" && ! -l "$dir/$_"){....}.
Jetzt haben wir die gesamte Funktionalität, die wir brauchen und können den tatsächlichen Code (pff.gz) schreiben.
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
# written by: guido socher, copyright: GPL
#
&help unless($ARGV[0]);
&help if ($ARGV[0] eq "-h");

# start in current directory:
search_file_in_dir(".");
#-----------------------
sub help{
    print "pff -- perl regexp file finder
USAGE: pff [-h] regexp

pff sucht im aktuellen Verzeichnis und allen Unterverzeichnissen nach Dateien, die mit einem bestimmten regulären Ausdruck übereinstimmen.

Die Suche unterscheidet immer zwischen Groß- und Kleinbuchstaben.

BEISPIEL:
suche eine Datei, die die Zeichenkette foo enthält:
    pff foo
suche eine Datei, die mit .html endet:
    pff \'\\.html\'
suche eine Datei, die mit dem Buchstaben \"a\" anfängt:
    pff \'^a\'
suche eine Datei mit dem Namen article<irgendwas>html:
    pff \'article.*html\'
    beachte .* statt nur *
\n";
    exit(0);
}
#-----------------------
sub search_file_in_dir(){
    my $dir=shift;
    my @flist;
    if (opendir(DIRH,"$dir")){
        @flist=readdir(DIRH);
        closedir DIRH;
        foreach (@flist){
            # ignore . and .. :
            next if ($_ eq "." || $_ eq "..");
            if (/$ARGV[0]/io){
                print "$dir/$_\n";
            }
            search_file_in_dir("$dir/$_") if (-d "$dir/$_" && ! -l "$dir/$_");
        }
    }else{
        print "ERROR: can not read directory $dir\n";
    }
}
#-----------------------
Laßt uns das Programm ein bißchen anschauen. Zuerst testen wir, ob der Benutzer ein Argument in der Kommandozeile eingegeben hat. Wenn nicht, dann ist dies ein Fehler und wir drucken einen kleinen Hilfetext aus. Wir geben auch einen Hilfetext aus, wenn die Option -h eingegeben worden ist. Andernfalls starten wir die Suche im aktuellen Verzeichnis. Wir benutzen den rekursiven Algorithmus wie oben beschrieben. Lies das Verzeichnis, suche die Dateien, teste, ob eine Datei ein Verzeichnis ist, wenn ja, rufe wieder search_file_in_dir() auf.

In der Anweisung, in der wir nach Verzeichnissen überprüfen, prüfen wir auch, daß es kein Verweis auf ein Verzeichnis ist. Wir müssen dies tun, da vielleicht jemand einen sym-link zu ".." erzeugt hat. Ein solcher Verweis würde das Programm dazu veranlassen, für immer weiterzulaufen, wenn wir die Überprüfung nicht hätten.

Das next if ($_ eq "." || $_ eq ".."); ist eine Anweisung, die wir bisher noch nicht diskutiert haben. Der "eq" Operator ist der Vergleichsoperator von Zeichenketten in Perl. Hier testen wir, ob der Inhalt von Variable $_ gleich ist mit ".." oder ".". Wenn er gleich ist, dann wird die "next" Anweisung ausgeführt. "next"innerhalb einer foreach Schleife bedeutet, daß noch einmal am Anfang der Schleife mit dem nächsten Element im Feld gestartet wird. Es ist der C-Anweisung "continue" ähnlich.

Referenzen

Hier ist eine Liste von anderen interessanten Perltutorien.