Perl deel III

ArticleCategory: [Es gibt verschiedene Artikel Kategorien]

Software Development

AuthorImage:[Ein Bild von Dir]

[Guido Socher]

TranslationInfo:[Author and translation history]

original in en Guido Socher

en to nl Floris Lambrechts

AboutTheAuthor:[Eine kleine Biographie über den Autor]

Guido is al lang een Linux fan en Perl hacker. Zijn Linux home page staat op www.oche.de/~bearix/g/.

Abstract:[Here you write a little summary]

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.

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

[Illustratie]

ArticleBody:[The article body]

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:
> add 42 2
42 + 2 is:44
.... 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. 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.