door Guido Socher <guido.socher(at)linuxfocus.org>
Over de auteur:
Guido is al lang een Linux fan en Perl hacker.
Zijn Linux home page staat op www.oche.de/~bearix/g/.
Vertaald naar het Nederlands door:
Floris Lambrechts <floris.lambrechts(at)linuxfocus.org>
Inhoud:
|
Perl deel III
Kort:
Perl deel I was een algemene inleiding tot Perl.
In het tweede deel schreven we het eerste
bruikbare programma. In dit derde deel behandelen we nu de arrays.
Arrays
Een array bestaat uit een lijst variabelen die met indices kunnen benaderd worden.
We zagen reeds dat "normale variabelen", scalars, altijd beginnen met een dollarteken
($). Arrays beginnen daarentegen met een @-teken alhoewel de data erin uit verschillende
scalaire variabelen bestaat. Je moet dus een dollarteken gebruiken om de individuele
velden in de arrays te benaderen. Een voorbeeld:
!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
# declareer een nieuwe array variabele:
my @myarray;
# zet er iets in:
@myarray=("data1","data2","data3");
# lees het eerste element (met index 0):
print "het eerste element van myarray is: $myarray[0]\n";
|
Met @myarray bedoelen we dus de array in zijn geheel, terwijl $myarray[0] verwijst
naar een individueel element ervan. Perl arrays beginnen met index 0. Nieuwe indices worden
automatisch bijgemaakt wanneer je er data instopt. Je moet dus op voorhand niet weten hoe
groot de array zal worden. Zoals hierboven te zien is, vul je arrays door de data tussen ronde
haakjes te plaatsen, met komma's ertussen.
("data1","data2","data3")
is eigenlijk een anonieme array. Je kan ook schrijven
("data1","data2","data3")[1]
om het tweede element van deze anonieme array te krijgen:
!/usr/bin/perl -w
print "Het tweede element is:"
print ("data1","data2","data3")[1];
print "\n"
|
Lussen over arrays
In Perl geeft de foreach lus de mogelijkheid om alle elementen in een array aan te spreken.
Het werkt als volgt:
#!/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 nummer $i is $lvar\n";
$i++;
}
|
Dit programma geeft:
element nummer 0 is data1
element nummer 1 is data2
element nummer 2 is data3
|
Het foreach commando neemt elk element uit de array en zet het in een lus variabele
($lvar in dit geval). Merk op dat de waardes niet van de array naar de lus variabele gekopieerd
worden, maar dat de lus variabele eigenlijk een pointer is. De lus variabele veranderen,
verandert dus ook de waarde in de array. Het volgende programma maakt van alle elementen in de
array hoofdletters.
De perl opdracht tr/a-z/A-Z/ is hetzelfde als het unix commando "tr".
Het zet in dit geval alle letters om naar hoofdletters.
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
my @myarray =("data1","data2","data3");
my $lvar;
print "Voor:\n";
foreach $lvar (@myarray){
print "$lvar\n";
$lvar=~tr/a-z/A-Z/;
}
print "\nNa:\n";
foreach $lvar (@myarray){
print "$lvar\n";
}
|
Na het uitvoeren zie je dat @myarray in de tweede lus enkel nog hoofdletters bevat:
Voor:
data1
data2
data3
Na:
DATA1
DATA2
DATA3
|
De opdrachtregel
In Perl II zagen we dat de functie &getopt de opties op de opdrachtregel leest. &getopt lijkt
op zijn C broeder; het is een functie uit de bibliotheek. De waardes van de opdrachtregel
worden in Perl toegewezen aan een array genaamd @ARGV. &getopt leest enkel deze @ARGV array
en bekijkt de elementen.
Anders dan in C is het eerste element in de array niet de programmanaam maar
wel het eerste argument dat op de command line werd gegeven. Als je de naam van het perl
programma wilt kennen moet je de $0 variabele lezen. Hier is een voorbeeldprogramma genaamd
add. Het leest twee nummers van de
opdrachtregel en telt ze op:
.... en hier is het programma:
#!/usr/bin/perl -w
# controleer of we twee argumenten hebben:
die "USAGE: gebruik twee cijfers\n" unless ($ARGV[1]);
print "$ARGV[0] + $ARGV[1] is:", $ARGV[0] + $ARGV[1] ,"\n";
|
Een stack
Perl heeft een aantal ingebouwde functies die een array als stack gebruiken.
- push voegt een element toe aan het eind van een array
- pop leest het laatste element van de array
- shift leest het eerste element van de array
- unshift voegt een element toe in het begin van de array
Het volgende programma voegt twee elementen
toe aan een bestaande array:
#!/usr/bin/perl -w
my @myarray =("data1","data2","data3");
my $lvar;
print "de array:\n";
foreach $lvar (@myarray){
print "$lvar\n";
}
push(@myarray,"a");
push(@myarray,"b");
print "\nna toevoeging van \"a\" en \"b\":\n";
while (@myarray){
print pop(@myarray),"\n";
}
|
Pop verwijdert hier op het einde alle laatste elementen van de array totdat deze leeg is.
Directories lezen
Perl kent de functies opendir, readdir en closedir om de inhoud van directories te lezen.
Readdir levert een array met alle bestandsnamen.
Met een foreach loop kan je alle bestandsnamen afgaan en er een bepaalde uitzoeken.
Hier is een simpel programma
dat een gegeven bestand zoekt in de huidige directory:
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
die "Gebruik: zoek_in_huidige_directory bestandsnaam\n" unless($ARGV[0]);
opendir(DIRHANDLE,".")||die "FOUT: kan de huidige directory niet lezen\n";
foreach (readdir(DIRHANDLE)){
print"\n";
print "gevonden: $_\n" if (/$ARGV[0]/io);
}
closedir DIRHANDLE;
|
We bekijken dit programma. Eerst controleren we of de gebruiker wel een argument heeft gegeven
op de opdrachtregel. Zoniet printen we gebruiksinformatie en stoppen we het programma.
Hierna openen we de huidige directory ("."). opendir is gelijk aan
de open functies voor bestanden. Het eerste argument is een file descriptor die je
aan de readdir en closedir functies moet doorgeven. Het tweede argument is het pad naar de
directory.
Dan komt de foreach lus. We merken meteen op dat de lus variabele er niet is.
In dat geval snelt Perl ter hulp en maakt een variabele genaamd $_ die je kan gebruiken als
lus variabele. readdir(DIRHANDLE) geeft een array en we gebruiken foreach om naar elk element
te kijken. /$ARGV[0]/io vergelijkt de reguliere
expressies in $ARGV[0] met de variable $_.
De "io" betekent zoek ongevoelig voor hoofdletters compileer de reguliere expressies
slechts één keer. Dit laatste is een optimalisatie om het programma sneller te
laten lopen.
Je kan het gebruiken voor een variabele in een reguliere expressie als je er zeker van
bent dat deze variabele niet verandert tijdens de duur van het programma.
Even proberen. Stel dat de huidige directory artikel.html, array1.txt en array2.txt bevat.
Zoek naar "HTML" geeft:
>zoek_in_huidige_directory HTML
.
..
artikel.html
Gevonden: artikel.html
array1.txt
array2.txt
Zoals je ziet heeft de readdir functie nog twee andere bestanden gevonden, namelijk "."
en "..". Dit zijn de namen van de huidige en de hogere directory.
Een bestandszoeker
Ik zou dit artikel willen afsluiten met een complexer, bruikbaar programma.
Het is een bestandszoeker. We noemen het pff (perl file finder).
Het werkt in principe net zoals het programma hierboven, maar het zoekt ook in subdirectories.
Hoe kunnen we zoiets ontwerpen? De code om de huidige directory te lezen en te doorzoeken hebben
we al. We moeten dit doen voor de huidige directory, maar als één van de bestanden
(behalve . en ..) opnieuw een directory is moeten we ook daar beginnen te zoeken. Dit is een
typisch recursief algoritme:
sub zoek_bestand_in_dir(){
my $dir=shift;
...lees de directory $dir ....
...als een bestand een directory is
doe dan &zoek_bestand_in_dir(that file)....
}
In perl kan je testen of een bestand een directory is en niet een symbolische link met
if (-d "$bestand" && ! -l "$dir/$_"){....}.
Nu hebben we alle
nodige functies en kunnen we de eigenlijke code (pff.gz) schrijven.
#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
# geschreven door: guido socher, copyright: GPL
#
&help unless($ARGV[0]);
&help if ($ARGV[0] eq "-h");
# start in de huidige directory:
zoek_bestand_in_dir(".");
#-----------------------
sub help{
print "pff -- perl regexp bestandszoeker
GEBRUIK: pff [-h] regexp
pff speurt de huidige directory en alle subdirectories af
op zoek naar bestanden die voldoen aan een gegeven reguliere
expressie. Het zoeken is altijd hoofdlettergevoelig.
VOORBEELD:
zoek een bestand dat begint met de regel foo:
pff foo
zoek een bestand dat eindigt op .html:
pff \'\\.html\'
zoek een bestand dat begint met de letter \"a\":
pff \'^a\'
zoek een bestand met de naam artikel<iets>html:
pff \'artikel.*html\'
bemerk de .* in plaats van gewoon *
\n";
exit(0);
}
#-----------------------
sub zoek_bestand_in_dir(){
my $dir=shift;
my @blijst;
if (opendir(DIRH,"$dir")){
@blijst=readdir(DIRH);
closedir DIRH;
foreach (@blijst){
# negeer . en .. :
next if ($_ eq "." || $_ eq "..");
if (/$ARGV[0]/io){
print "$dir/$_\n";
}
zoek_bestand_in_dir("$dir/$_") if (-d "$dir/$_" && ! -l "$dir/$_");
}
}else{
print "FOUT: kan directory $dir niet lezen\n";
}
}
#-----------------------
|
We bekijken dit programma. Eerst testen we of de gebruiker een argument heeft doorgegeven.
Als dit niet zo is printen we een kleine helptekst. Dat doen we ook als de optie -h gebruikt
is. Dan beginnen we te zoeken in de huidige directory. We gebruiken het hierboven beschreven
recursief algoritme. Lees de directory, zoek de bestanden, test of het een directory is, zoja
begin dan opnieuw te zoeken.
Daar waar we kijken of het om een directory gaat, controleren we ook meteen of het geen link is
naar een directory. Dit moeten we doen omdat iemand een link gemaakt kan hebben naar de
directory "..". Dan zou het programma nooit stoppen (een oneindige lus.)
"next if ($_ eq "." || $_ eq "..");" is een uitdrukking
die we nog niet besproken hebben. De "eq" operator is de string vergelijker van perl.
Hier testen we of de inhoud van de variabele $_ gelijk is aan ".." of ".".
Als het inderdaad hieraan gelijk is dan wordt het "next-e" of volgende commando
uitgevoerd. "next" in een lus betekent dat de lus terug moet gestart worden bij het
begin, met het volgende element uit de array. Het is gelijk aan het C commando "continue".
Referenties
Hier is een lijst van andere interessante Perl cursussen.
Talkback voor dit artikel
Elk artikel heeft zijn eigen talkback pagina. Daar kan je commentaar geven of commentaar van anderen lezen:
2002-06-08, generated by lfparser version 2.28