Perl deel III
ArticleCategory: [Es gibt verschiedene Artikel Kategorien]
Software Development
AuthorImage:[Ein Bild von Dir]
![[Guido Socher]](../../common/images/Guido-S.gif) 
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]](../../common/images/illustration114.gif) 
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:
.... 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.