original in en Guido Socher
en to nl Floris Lambrechts
Guido is al jaren een Linux fan en Perl hacker. Tegenwoordig houdt hij zich bezig met de renovatie van zijn huis en met tuinieren.
Perl is op zijn best als het gebruikt wordt voor het schrijven van kleine, gespecialiseerde programma's. Om de ontwikkeling van nieuwe programma's te versnellen is het handig om al een basis bij de hand te hebben met een minimum aan functionaliteit. Deze code biedt simpele interpretatie van opdrachtregel opties en heeft al een subroutine om een help-bericht weer te geven.
!/usr/bin/perl -w
|
We bekijken deze code. &getopts() is een subroutine uit de bibliotheek
Getopt::Std om de opdrachtregel opties in te lezen. Het zet de variabelen met
de naam $opt_<optie> in de toestand die overeenkomt met de opties
die via de command line worden doorgegeven. Alle opties in de command line
beginnen met een "-" (minteken) en komen ná de programmanaam
en vóór de andere argumenten (dit is een algemene Unix regel).
Het argument van &getopts (de "h" in dit geval) definieert de
opties die op de opdrachtregel mogelijk zijn. Als die optie zelf ook
nog een argument heeft, moet er een dubbelpunt staan na de letter van
de optie. Bv &getopts("d:x:h") zegt dat dit programma drie opties
-d, -x en -h heeft, waarvan -d en -x gegeven worden met een extra argument.
Dus "-d iets" is een geldige optie, maar "-d -x iets" is
fout omdat -d niet gevolgd wordt door een argument.
Als op de opdrachtregel de optie -h gegeven wordt, wordt de variabele $opt_h
gevuld en &help if ($opt_h);
roept dan de subroutine help aan.
De uitdrukking sub help{
declareert de subroutine.
Het is op dit moment niet echt noodzakelijk dat je elk detail van de code
begrijpt. Beschouw dit gewoon als een basis waar je de eigenlijke
functionaliteit aan kan toevoegen.
We beginnen met een klein omzettingsprogramma, een uitbreiding van ons
basisprogramma. numconv, zoals we het zullen noemen, converteert hexadecimale
getallen naar gewone decimale en omgekeerd.
numconv -x 30
geeft het hex equivalent van 30.
numconv -d 1A
geeft de decimale versie van hex 1A.
numconv -h
geeft een help tekst.
Er is in Perl een functie hex()
die hexadecimale getallen converteert naar
decimale. We gebruiken de functie printf()
om decimale getallen naar hex om
te zetten. Wanneer de juiste code is ingevoegd in ons basisprogramma,
krijgen we dit:
|
Klik
hier om de
(Engelstalige) broncode van numconv te downloaden.
In de volgende hoofdstukken bekijken we dit programma stap voor stap en
proberen we de werking te begrijpen.
In perl zijn er twee vormen van het if-statement:
uitdrukking if (voorwaarde);
of
if (voorwaarde) BLOK [[elsif (voorwaarde) BLOK ...] else BLOK]
BLOK staat voor een aantal opdrachten die tussen {accolades} staan. Je kan dus
bijvoorbeeld schrijven:
printf("hallo\n") if ($i);
|
Net als in C is het mogelijk om de verkorte notatie && en || te gebruiken.
printf("hallo\n") if ($i);
is dus hetzelfde als
($i) && printf("hallo\n");
Vooral de || zoals gebruikt in ons basisprogramma vertaalt makkelijk
naar mensentaal.
&getopts("d:x:h")||die "ERROR\n";
"Lees de opties of stop". De functie die()printf gevolgd door exit
. Het geeft een boodschap en
beëindigt het programma.
&getopts("d:x:h")||die "ERROR\n";
kan ook geschreven worden als
die "ERROR\n"; if (! &getopts("d:x:h"));
waar de ! staat voor een logische NOT voorwaarde. Dit is ook weer
hetzelfde als
die "ERROR\n"; unless (&getopts("d:x:h"));
unless
is hetzelfde als 'if-not' en is beter leesbaar dan if(!..)
Zoals je ziet zijn er in Perl meerdere manieren om een if-statement te schrijven. Natuurlijk heb je ze niet allemaal nodig; gebruik gewoon degene die je zelf het handigst vindt.
In het eerste perl artikel zagen we dat scalaire variabelen (de $-variabelen) gebruikt konden worden zonder ze te declareren. Ze worden gemaakt op het moment dat ze voor het eerst gebruikt worden. Dit is zeer handig bij kleine programma's, maar bij grotere projecten kan het tot moeilijk te ontdekken fouten leiden. Variabelen declareren geeft de compiler de mogelijkheid om extra controles te doen op typfouten.
|
#!/usr/bin/perl
|
Dit programma is correct en geeft "i is 1". Als we echter per ongeluk j in plaats van i typen:
#!/usr/bin/perl
|
Deze code geeft geen fouten in perl en produceert "i is ". De perl module "use strict;" dwingt de compiler om te klagen over een dergelijk programma. Als "strict" gebruikt wordt, moet alles gedeclareerd worden, anders krijg je een foutmelding.
#!/usr/bin/perl
|
Dit leidt tot het volgende bericht en maakt het makkelijker om de typfout te vinden:
Global symbol "$j" requires explicit package name at ./vardec line 4. Execution of ./vardec aborted due to compilation errors. Exit 255
Variabelen kunnen we declareren met my of, zoals we eerder
zagen, met use vars qw():
use vars qw($opt_h);
Globale variabelen worden gemaakt met use vars. Deze
variabelen zijn globaal, zelfs in alle ingevoegde bibliotheken.
Variabelen die enkel gelden in het huidige programma bestand (dus
globaal voor alle subroutines in dat bestand) worden gedeclareerd met
my in het begin van het programma (buiten een subroutine).
Variabelen locaal in de huidige subroutine worden in de
subroutine gedeclareerd met my.
Mensen met ervaring in shell programmeren zullen het $-teken waarschijnlijk liever weglaten. Dit is niet mogelijk in perl; niet bij het declareren en niet bij het gebruiken. Voor een scalaire variabele moet gewoon áltijd een $-teken staan.
Bij het declareren van een variabele kan je er ook meteen een waarde aan toewijzen. my $myvar=10; declareert de variabele $myvar en zet zijn beginwaarde op 10.
De "help" subroutine hebben we hierboven al gebruikt in het numconv
programma. Subroutines worden gebruikt om eigen functies te maken. Ze zijn
dus zeer nuttig bij het structureren van een programma.
Een subroutine kan overal in het programma geschreven worden: of het nu voor
of na de aanroep ervan geschreven staat, maakt niet uit. Een subroutine
beging met sub naam(){... en wordt aangeroepen (i.e. uitgevoerd)
met $retwaarde=&naam(...argumenten...). De $retwaarde is de waarde
die de functie als 'return' geeft, met andere woorden de waarde van de
laatst uitgevoerde bewerking in die subroutine. De argumenten die je de
subroutine geeft worden doorgegeven in de speciale array @_. In het derde
deel zullen we hier dieper op ingaan. Nu volstaan we met de opmerking dat
in de subroutine de waarde van scalaire variabelen in de array @_ gelezen
kunnen worden met shift.
Hier is een voorbeeld:
#!/usr/bin/perl
|
Nu we al heel wat perl syntax en taalelementen gezien hebben, is het tijd om
een echt programma te schrijven.
Perl was ontworpen om tekstbestanden te kunnen manipuleren met zeer weinig
programmeerwerk. Ons eerste Perl programma gaat een lijst met afkortingen
bekijken en alle dubbelen in de lijst opsporen. Dubbel zijn alle afkortingen
die meer dan één keer voorkomen. De lijst heeft de volgende
vorm:
|
AC Access Class AC Air Conditioning AFC Automatic Frequency Control AFS Andrew File System ... <
Je kan de lijst hier
downloaden. De syntax van dit bestand is:
Hoe lezen we nu zo'n tekstbestand? Hier is de Perl code om een tekst regel voor regel te lezen:
|
De 'open' functie neemt een file descriptor als eerste argument en de naam
van het bestand als tweede. File descriptors zijn een speciaal soort
variabelen. Je zet ze gewoon in de 'open' functie, je gebruikt ze in de
functie die de data uit het bestand inleest en tenslotte geef je ze door aan
'close', waardoor het bestand gesloten wordt. Het bestand lezen kan met
<FD>. De <FD> kan als argument aan een while lus gegeven worden,
wat resulteert in het regel voor regel lezen van het bestand.
In Perl worden file descriptors volgens traditie volledig in hoofdletters
geschreven.
Waar gaan de gegevens naartoe? Perl heeft een aantal impliciete variabelen. Dit zijn variabelen die je niet hoeft te declareren, ze zijn er altijd. Een voorbeeld van zo'n variabele is $_. Hierin staat het nummer van de regel die op dat moment gelezen wordt in de while lus. Even proberen (download de code):
#!/usr/bin/perl
|
|
Zoals je ziet hebben we dit niet geschreven als print "regel $i is $_\n". De $_ variabele bevat namelijk niet allen de huidige regel van het tekstbestand, maar daarna ook nog een newline karakter (enter-teken).
Nu kunnen we het bestand lezen. Om het volledige programma te schrijven moeten we nog twee dingen leren:
Reguliere expressies zijn zeer handig bij het zoeken naar een bepaald patroon in een tekst. We zijn op zoek naar alle letters vanaf het begin van de regel tot aan de eerste spatie. Met andere woorden, ons zoekpatroon is "begin van de regel-->een aantal karakters, geen spatie-->een spatie". Geschreven met de reguliere expressies in perl is dit ^\S+\s. Als we die uitdrukking nu in een m//; plaatsen, zal perl deze bewerking uitvoeren op de $_ variabele (die de huidige regel bevat). De \S+ in de reguliere expressie staat voor een aantal tekens behalve spatie. Als we haakjes zouden plaatsen rond de \S+, dan zou de tekst "not space characters" in de variabele $1 geplaatst worden. We passen nu ons programma aan:
#!/usr/bin/perl -w
|
De match operator (m/ /) retourneert 1 als de reguliere expressie met succes is toegepast op de huidige regel. Daardoor kunnen we hem gebruiken binnen een if-statement. Zet voor dat je $1 gebruikt een if-statement rond de match operator om te controleren of de gegevens wel correct zijn.
Nu we het bestand en de afkortingen kunnen inlezen missen we nog een manier om te controleren of we de afkorting al eerder zijn tegengekomen. We gebruiken een nieuw data type: Hash Tables. Hash Tables zijn arrays die kunnen geïndexeerd worden met een string. Als je de volledige Hash Table nodig hebt schrijf je gewoon een % voor zijn naam. Om een individuele waarde te lezen gebruik je $naam{"index_string"}. Dit is dezelfde $ als bij andere scalaire variabelen omdat een veld in een Hash Table eigenlijk een gewone scalaire variabele bevat. Een voorbeeld:
#!/usr/bin/perl -w
|
De uitvoer van dit programma is:
%htab op index "iets" is waarde_van_iets %htab op index "ietsanders" is 42
Nu kunnen we ons programma afwerken:
1 #!/usr/bin/perl -w
|
Hoe werkt het? We lezen het bestand regel voor regel en slaan de regels op in de Hash %htab (zie regel 33). De index van de Hash is de afkorting. Bij elk nieuw element in de Hash wordt gecontroleerd of de index nog niet bezet is. (regel 18). Als er al iets staat, zijn er twee mogelijkheden:
Het kan handig zijn om de code te downloaden en er wat mee te spelen.