Perl Teil III
ArticleCategory: [Es gibt verschiedene Artikel Kategorien]
Software Development
AuthorImage:[Ein Bild von Dir]
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]
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:
.... 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.
- push fügt ein Element an das Ende des Feldes hinzu
- pop liest ein Element vom Ende des Feldes aus
- shift liest ein Element vom Anfang des Feldes aus
- unshift fügt ein Element an den Anfang des Feldes hinzu
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.