Programmeren met Perl
- Introductie
- Reguliere expressies (Regular expressions)
- Gebruik maken van pattern matching
- Speciale variabelen
- Werken met bestanden en mappen (files en folders)
- Arrays
- Hashes
- Modules (en gebruik van andermans code)
- Fouten om van te leren
- Perl functies
- Links
Introductie
Reguliere expressies (Regular expressions)
Introductie
De kracht van Perl zit voor een deel in dat je in de taal gebruik kan maken van reguliere expressies. Het nadeel van die regex's (om het zo maar even af te korten) is dat het bijna onleesbare code oplevert.
Regex's zijn vooral handig voor tekstbewerking.
Patroonvergelijking (Pattern matching)
Patronen staan vaak tussen forward slashes, dus bv. /te?t$/.
De tekens in onderstaande tabel, zoals '^', '$', '/', '.' hebben speciale betekenissen en kunnen dus niet zonder meer in een patroon gebruikt worden. Om ze wel te gebruiken (dus als je een punt wilt matchen i.p.v. hetgeen waar de punt normaal gesproken voor staat) moet je er een '\' voor zetten. Dit soort tekens wordt metacharacters genoemd.
Laten we een voorbeeldzin nemen om in de onderstaande voorbeelden te gebruiken:
Alle bestanden die ouder zijn dan 5 november 2003
Symbool | Betekenis | Voorbeeld |
---|---|---|
^ | Begin van de string | /^Alle best/ levert een match, omdat de string er mee begint. |
$ | Eind van de string | /2003$/ levert een match, omdat de string er mee eindigt. |
\d | Een cijfer van 0..9 | /\d\d\d\d\d\d$/ levert geen match, omdat er geen 5 cijfers aan het eind van de string staan (/\d\d\d\d$/ had wel gewerkt). |
\ | Escape character. Hiermee geef je aan dat het teken dat erna komt een andere betekenis heeft dan normaal. | Als je in het voorgaande voorbeeld geen escape character had gebruikt voor de 'd', had Perl gewoon gezocht naar 4 of 5 'd'-s op een rijtje. |
\D | Elk teken dat geen cijfer van 0..9 is. | /\D2/ matcht, omdat er voor de '2' een niet-cijfer staat. /\D3/ matcht niet, omdat je geen '3' kan vinden met een niet-cijfer ervoor. |
\w | Letters (hoofdletters en gewone), cijfers van 0..9 en de underscore (_). | /r\w2/ match niet, omdat het teken tussen november en 2003 een spatie is. |
\W | Elk teken dat niet aan \w voldoet. | |
\s | Spaties, tabs, einde van de regel, etc. Dus eigenlijk alles waar geen zichtbare tekens staan. | /r\s2/ matcht wel, omdat er precies 1 spatie tussen november en 2003 staat. /r\s\s2/ zou niet hebben gematched. |
\S | Alles wat niet aan \s voldoet. | |
. | De punt matcht elk teken. | /^.$/ matcht elke string die uit precies 1 teken bestaat. |
? | Matcht 0 of 1 keer het teken dat er voor staat. | /sta?nd/ levert een match, omdat 'stand' in de zin zit. Maar /staa?nd/ levert ook een match. In dit geval wordt de 'a' voor de vraagteken vergeten. |
* | Match 0 of meer voorkomens van het teken voor de asterisk. | /sx*tx*and/ matcht, omdat de 'x*' in dit geval 2 keer gewoon niet wordt meegeteld (0 voorkomens van 'x'). /Al*e/ matcht ook, omdat de 'l*' gelijk is aan 'll'. |
+ | Is bijna hetzelfde als *, maar nu moet het teken minimaal 1 keer voorkomen. | /sx+tx+and/ matcht dus niet, maar /Al+e/ wel. |
\b | Match een woordgrens, de grens tussen een letterteken, cijfer of underscore ('_') en een ander teken, bv. een spatie. | /Alle\b/ matcht, omdat er na 'Alle' een spatie komt. Maar /All\b/ match niet, omdat 'e' een word character is (overeenkomend met \w). |
\B | Het omgekeerde van \b. Match dus juist niet met een woordgrens. | /oud\B/ matcht, omdat er nog 'er' na 'oud' komt. |
| | Matcht alternatieven. | /ouder|oudst/ matcht, omdat 1 van de 2 in de zin voorkomt (ouder). /^Alle|2003$/ matcht ook, omdat ze allebei correct zijn. Speciale tekens worden alleen toegepast op het alternatief waar ze in staan. Dus /^ouder/Alle$/ match niet, omdat ze allebei incorrect zijn. |
Groepen van tekens
Alternatieven kan je scheiden met een '|', maar als het om tekens gaat kan je ze ook tussen vierkante haken zetten.
Als je wilt testen op een oneven cijfer kan je dus de notatie
[13579]
gebruiken.
Testen op een klinker doe je met [aeiou]
.
Binnen vierkante haken heeft '^' een andere betekenis dan erbuiten. Je kan er mee aangeven dat je de groep tekens die er op volgt juist niet wilt.
Dus als je wilt testen op een medeklinker kan je
[^aeiou]
gebruiken.
Als je wilt testen op een opeenvolgende reeks van tekens, bv. de cijfers van
'2' t/m '7', dan kan je dat als volgt aangeven:
[2-7]
.
Herhaling van een patroon
Soms wil je dat een bepaald patroon meerdere keren voorkomt, en vooral als dat meer dan 2 keer is kan het nogal onnodig ingewikkeld worden.
Stel dat je 3 klinkers achter elkaar wilt, dan krijg je
[aeiou][aeiou][aeiou]
.
Gelukkig kan je tussen accolades het aantal repetities opgeven.
Het voorgaande patroon wordt dan [aeiou]{3}
.
Als het patroon een variabel aantal keren voorkomt, bv. tussen 3 en 5 keer,
dan wordt het [aeiou]{3,5}
.
Als het patroon minimaal een aantal keren moet voorkomen, laat je gewoon
het 2e getal weg.
Minimaal 5 keer een klinker wordt dus [aeiou]{5,}
.
Refereren aan gevonden stukken
Ik begin weer met de voorbeeldzin:
Alle bestanden die ouder zijn dan 5 november 2003
Stel je wilt een patroon gebruiken waarmee je het stuk kan vinden na 'ouder zijn dan', en met het resultaat een nieuwe zin wilt maken.
Naar elk stuk binnen een reguliere expressie dat tussen haakjes staat kan je later terugverwijzen met bv. de variabelen $1, $2, $3, etc.
Het volgende programmaatje:
$line1 = "Alle bestanden die ouder zijn dan 5 november 2003";
if ($line1 =~ /ouder zijn dan (.*)$/) {
print "Datum: $1\n"
}
produceert als uitvoer:
Datum: 5 november 2003
De reguliere expressie eindigt met (.*)$
wat betekent: een willekeurig teken (.), willekeurig vaak herhaald (*) tot
je het einde van de regel ($) tegenkomt.
Het stuk van de regel dat voldoet aan het gedeelte tussen haakjes krijgt de naam '$1', en daar kan in het programma verder mee werken tot je door een nieuwe pattern matching een nieuwe '$1' krijgt, of er zelf een waarde aan geeft.
Speciale variabelen
Een variabele, en dus ook een speciale variabele, kan een waarde hebben of ongedefinieerd zijn.
Soms wil je dat een speciale variabele niet zijn standaardwaarde heeft, en
in dat geval kan je er de undef
-functie op loslaten.
Variabele | Betekenis | Alternatieve namen | Default waarde |
Voorbeeld |
---|---|---|---|---|
$_ | Als je geen variabele opgeeft op een plek waar er 1 nodig is wordt de waarde van $_ gebruikt. | $ARG | ||
$" | Als je een array afdrukt wordt deze string na elk item afgedrukt (default spatie). | $LIST_SEPARATOR | ||
@_ | De lijst met parameters die aan een subroutine worden doorgegeven. | |||
$\ | String die afgedrukt moet worden nadat een list is afgedrukt. | $OUTPUT_RECORD_SEPARATOR $ORS |
||
$, | String die afgedrukt moet worden tussen elementen uit een list. | $OUTPUT_FIELD_SEPARATOR $OFS |
Leeg | |
$" | Zelfde als $OUTPUT_FIELD_SEPARATOR, maar dan alleen voor list variabelen in strings. | Spatie | ||
$! | De meest recente operating system error. | $OS_ERROR | ||
$= | Aantal regels dat je op elke pagina wilt hebben. | $FORMAT_LINES_PER_PAGE | ||
$& | In het 2e deel van een replacement operatie, hetgene wat gematched is in het 1e deel. | $MATCH |
Gebruik maken van pattern matching
Testen of een string een bepaald patroon bevat
Dat doe je als volgt:
if ($string =~ m/aap/) {
statements...
}
De 'm' staat voor match, en '=~' is de pattern-matching operator.
Er staat dus: als de variabele $string
de
string aap
bevat, voer dan de commando's tussen
de {} uit.
De 'm' is in dit geval optioneel.
Vaak wordt de default variabele $_ (i.p.v. zoals hier $string) gebruikt, en
dan wordt de 1e regel dus:
if (/aap/) {
Uppercase en lowercase
Als je wilt dat case geen rol speelt bij de pattern-matching (dus case-insensitive), dan moet je de regulier expressie laten volgen door een 'i'.
/google/i
matcht dus zowel 'Google' als 'GOOGLE' als
'GoOgLe'.
Variabele | Betekenis | Voorbeeld |
---|---|---|
i | Er wordt niet gekeken naar hoofdletters of kleine letters | |
g | Global. Wordt gebruikt bij replacement (substitutie) om meerdere voorkomens op 1 regel mee te nemen. | |
c | In een tr(anslate)-operatie, het omdraaien (complement) van de operatie. | |
s | In een tr(anslate)-operatie, het vervangen van een serie dezelfde tekens door 1 ervan (de 's' van squash). | |
d | In een tr(anslate)-operatie, het niet verlengen van te korte replacement strings, maar het verwijderen (delete) van de overtollige tekens. | |
Werken met bestanden en mappen (files en folders)
De volgende onderwerpen:
Openen van een file
De volgende subsecties:
- File handles
- Voorbeelden
- Haakjes of niet?
- Enkele of dubbele quotes?
- Informatie aan het einde van een file plakken
- Wat is het resultaat van open, en wat als een bestand nog niet bestaat?
- Standaard gedefiniëerde files
File handles
Om een file te kunnen lezen of er naar toe te kunnen schrijven heb je een 'handle' nodig naar die file. Om dezelfde file te kunnen lezen en beschrijven heb je 2 handles nodig.
De naam van een file handle is uppercase, moet beginnen met een letter, en kan verder ook cijfers en underscores bevatten.
Voorbeelden
Voorbeelden van het openen van een bestand:
open WP, 'computer.htm';
open HFO, '>d:\test\op.txt';
In het 1e geval geef je geen padnaam op, en open je het bestand
computer.htm
in de huidige directory (de map
van waaruit je script draait, of een andere als je hem gewijzigd hebt).
De naam van de file handle is WP
, en je gebruikt
die naam om het bestand te benaderen.
In het 2e geval creëer je een file handle HFO
naar een bestand met een volledige padnaam.
Vanwege het '>'-teken betekent het dat je naar het bestand kan schrijven.
Let op: als het bestand al bestaat wordt de inhoud overschreven!!
Haakjes of niet?
Je kan bv. het 1e commando ook schrijven als
open (WP, 'computer.htm');
(dus met haakjes).
Ik weet niet precies wanneer je wel of geen haakjes moet schrijven.
Wel lijkt me veiliger.
Enkele of dubbele quotes?
In het 2e voorbeeld zijn de enkele quotes cruciaal. Als je nl. dubbele quotes had gebruikt, was het bestand als een string opgevat, en had '\t' gestaan voor een TAB-character. Als je toch perse dubbele quotes wilt gebruiken moet je elke '\' in een padnaam vervangen door '\\'.
Informatie aan het einde van een file plakken
Er is nog een 3e manier (naast lezen en schrijven) om een file te openen, en
dat is voor het toevoegen van informatie aan het bestand:
open (WP, '>>computer.htm');
Wat is het resultaat van open, en wat als een bestand nog niet bestaat?
Het openen (voor lezen) van een bestand dat nog niet bestaat levert een fout op. En vanzelfsprekend kunnen er nog allerlei andere dingen mis gaan.
Het open
-commando levert 1 van 2 resultaten op:
undef
als het niet goed is gegaan, en 1 als het wel
goed is gegaan.
Wat wil je graag doen als je een bestand opent en het lukt niet? Een foutmelding afdrukken en het script stoppen (in de meeste gevallen).
Gelukkig heeft Perl daar een mooie constructie voor:
open (WP, 'computer.htm') or die "Kan computer.htm niet
openen: $!\n";
Als het fout gaat wordt de melding afgedrukt.
'\n' staat voor een newline (nieuwe regel) aan het eind, en '$!' staat
voor een eventuele foutmelding van het operating system, bv.
Permission denied
Voorgedefinieerde files
Er zijn 3 file handles die altijd bestaan:
STDIN
,
STDOUT
en
STDERR
Afdrukken naar en lezen van bestanden
Afdrukken naar het scherm
Het commando print "Hello world!"
drukt de tekst tussen dubbele quotes op het scherm af.
Als je dus geen file handle opgeeft (bij het afdrukken) wordt automatisch
STDOUT
gebruikt.
Arrays (en lists)
Arrays zijn geïndexeerde verzamelingen van 'scalar things'. De index van het eerste element is 0.
I.t.t. in veel andere talen hoeven de elementen niet allemaal hetzelfde type te hebben. Je kan bv. strings en getallen door elkaar gebruiken in 1 array.
De naam van een array begint met '@'.
Je kan zonder problemen zowel een scalar met de naam $x
als een array met de naam @x
gebruiken.
Een element (bv. het 3e) in een array wordt weergeven als
$x[2]
(0 is het 1e element!).
Als je een element benadert dat niet bestaat krijg je een foutmelding (als je die aan hebt staan) of de waarde 0 of "".
Arrays creëren
Je kan een array bv. als volgt aanmaken:
@mix = (3, 'Europa', 4.21, 'soep', 'aap');
Er is een speciaal geval voor arrays die alleen strings van 1 woord bevatten:
@kleuren = qw(zwart, groen, rood, lila);
(waarbij 'qw' staat voor quoted words).
Een derde mogelijkheid is een array die een aaneengesloten serie (strings
of getallen) moet bevatten:
@letters = ('d' .. 'p');
Aantal elementen in een array
Perl heeft een speciale manier om de index van het laatste element in
een array weer te geven.
Voor de array @letters
zou dat bv.
@#letters
zijn.
Bovenstaande waarde geeft het aantal elementen - 1, maar je kan ook
rechtstreeks het aantal elementen opvragen:
$numletters = @letters;
Arrays wijzigen of uitbreiden
Arrays sorteren
Sorteren is simpel:
@sortedx = sort @x;
Deze sortering is in ASCII volgorde, dus 392 komt voor 5.
Numeriek sorteren is iets ingewikkelder:
@sortedx = sort {$a <==> $b}
Iets doen met elk element van een array
Vanzelfsprekend kan dit door een standaard loop te gebruiken, zoals met
while
,
maar je kan ook gebruik maken van foreach
.
foreach $elt (@x) {
statements;
}
Hashes
Hashes zijn verzamelingen gegevens waarbij steeds een sleutel (key) gekoppeld is aan een waarde (value).
Hashes vullen met informatie
Je kan een hash bv. als volgt aanmaken:
%ages = ('Henk', 60, 'Bert', 59, 'Martin', 53);
Als er een element teveel in de lijst zit wordt er niets mee gedaan.
Voor de overzichtelijkheid zou je het ook zo kunnen doen:
%ages = (
'Henk', 60,
'Bert', 59,
'Martin', 53
);
En je kan ook gebruik maken van een speciale operator, '=>', die zich net zo gedraagt als een komma:
%ages = (
'Henk' => 60,
'Bert' => 59,
'Martin' => 53
);
Bewerken van hashes
Je kan (in bovenstaand voorbeeld) de leeftijd van Bert afdrukken met
print $ages{'Bert'};
En je kan hem een nieuwe leeftijd geven met
$ages{'Bert'}=60;
Met de functie delete kan je elementen uit een hash
verwijderen.
Met
delete $ages{'Bert'};
gooi je zowel 'Bert'
als 60
weg, en het resultaat is de waarde, in dit geval 60
.
Modules (en gebruik van andermans code)
Vanuit het oogpunt van hergebruik van (je eigen of andermans) code en het overzichtelijk houden van programma's, kan je gebruik maken van modules.
Er zijn 2 Perl-commando's om van modules gebruik te maken:
use
en require
.
De module English.pm
In het begin van je programma's zou je bv. het statement
use English
kunnen zetten.
English.pm
('pm' staat voor Perl module) is een
stuk code waarin leesbare namen worden gedefiniëerd voor de vaak
cryptische namen van ingebouwde Perl-variabelen.
In plaats van de $/
kan
je dan bv. ook $RS
of
$INPUT_RECORD_SEPARATOR
gebruiken.
Het zoeken van modules
Zie hier.
Het installeren van modules
Zie hier.
Fouten om van te leren
In deze sectie een serie fouten die mij de nodige hoofdbrekens hebben bezorgd. Probeer als puzzel evt. eerst zelf te kijken wat er mis is in het codefragment.
De volgende onderwerpen:
- Regels zoeken in een invoerbestand
- Pattern matching vindt niets
- File kan niet geopend worden
- Can't find string terminator
- Waarom is $argv[0] niet goed?
- Mismatch van haakjes
Regels zoeken in een invoerbestand
Ik wilde in een bestand regels zoeken die aan een bepaald patroon voldeden.
Het bestand specificeerde ik in het programma (dus niet via een parameter achter het perl-commando), als volgt:
use English;
open ITC, 'C:\Projects\Output.txt'
or die "Bestand bestaat niet\n";
while (<>) {
print "$1$2$3\n" if (/PRO\s\w{2}\s(\w)\w{2}-(\w{2})-(\w{2})/);
}
close ITC;
Om 1 of andere duistere reden kwam er geen output op het scherm, hoewel ik vrijwel 100% zeker was dat er een aantal matchende regels waren.
Zelfs toen ik in de while-lus gewoon een keiharde
print "Test"
zette kwam er geen uitvoer.
Toen ging me eindelijk een lampje branden.
Het is leuk dat ik een bestand open en het de naam ITC
geef, maar ik zeg in de while-lus niet dat ik daaruit wil lezen.
Het moet dus zijn: while (<ITC>)
Pattern matching vindt niets
Ik wilde in een bestand regels zoeken die aan een bepaald patroon voldeden.
Het bestand specificeerde ik in het programma (dus niet via een parameter achter het perl-commando), als volgt:
use English;
open ITC, 'C:\Projects\Output.txt'
or die "Bestand bestaat niet\n";
while (<ITC>) {
print "$1$2$3\n" if (/pro\s\w{2}\s(\w)\w{2}-(\w{2})-(\w{2})/);
}
close ITC;
Om 1 of andere duistere reden kwam er geen output op het scherm, hoewel ik vrijwel 100% zeker was dat er een aantal matchende regels waren.
Oplossing: ik had me niet gerealiseerd dat Perl case-sensitive is. In het bestand stond meerdere malen PRO, in hoofdletters dus.
File kan niet geopend worden
In het volgende stukje code kreeg ik steeds een foutmelding omdat de file niet geopend kon worden:
use English;
$dir = "C:\Problems";
opendir COP, $dir or die "Can't open folder\n";
@files = readdir COP;
foreach $f (@files) {
if (-d $f) {
next;
}
open OPL, "<", $f or die "Can't open file $f\n";
...
Het if-statement in de regels na foreach
dient
om te testen of ik geen map (directory) te pakken heb.
Dan ga ik verder met de volgende iteratie van de loop.
Ik kreeg de volgende foutmelding:
Use of uninitialized value in open at ...
Het probleem zit hem erin, dat de open
een
*volledige* filenaam nodig heeft, dus inclusief de map(pen) waarin het
bestand zit.
Je kan daartoe bv. de laatste regel van het codefragment vervangen door:
$filepath = $dir . "\\" . $f;
open OPL, "<", $filepath or die "Can't open file $filepath\n";
Can't find string terminator
Mijn programma begint met de volgende code:
use English;
$website = 's:\website mas\stick\computer\';
Dat levert de volgende melding op:
Can't find string terminator "'" anywhere before EOF at Print text between title
tags of random web page from folder to screen.pl line 2.
Oplossing: het probleem zit hem in de backslash voor de quote aan het eind. Als ik daar een extra backslash voor zet gaat het wel goed.
Waarom is $argv[0] niet goed?
Ik wil het Perl programma 1 argument meegeven, en zoals ik me
(min of meer) correct herinnerde, doe je dat met
$ARGV[0]
.
Maar waarom geeft onderstaande code dan de daaropvolgende foutmeldingen?
use English;
$website = "s:\\website mas\\stick\\computer\\";
print "$website\n";
$page = $argv[0];
print "$page\n";
$fn = $website.$page;
Name "main::argv" used only once: possible typo at Print text between title tags
of random web page from folder to screen.pl line 4.
s:\website mas\stick\computer\
Use of uninitialized value $page in concatenation (.) or string at Print text be
tween title tags of random web page from folder to screen.pl line 5.
Oplossing: opnieuw vergat ik even dat Perl case-sensitive is.
$ARGV
is een voorgedefinieerde array, met naam in
hoofdletters.
Beide foutmeldingen zijn daarmee te verklaren.
Mismatch van haakjes
Ik kreeg de foutmelding
Unmatched ) in regex; marked by <-- HERE in m/\s{3}(\w{6})\s{22}ABSOLUTE\s{5}\(w
{2}\sDEC\s14) <-- HERE / at itccompc.pl line 11.
in de volgende code:
while (<OPL>) {
if (/\s{3}(\w{6})\s{22}ABSOLUTE\s{5}\(w{2}\sDEC\s14)/) {
$comp{"$1"} = "$2";
}
}
Het is niet zo dat er een haakje te veel of te weinig staat. Het probleem is dat er per ongeluk een backslash op de verkeerde plaats is beland.
In plaats van \(w{2}
moet er natuurlijk staan
(\w{2}
Het haakje voor de 'w' werd vanwege de backslash niet gezien.
Perl functies
De volgende functies worden besproken (in alfabetische volgorde):
chomp
Argument: string of list.
Newlines aan het eind van de string, of aan het eind van elk element in de list, worden verwijderd.
Resultaat: het aantal verwijderde tekens.
defined
Argument: variabele of array element.
Resultaat: true als de waarde ongelijk is aan
undefined
Voorbeeld:
delete
Argument: referentie naar een hash element, bv.
$hashname($key)
Actie: verwijdert zowel de key als de bijbehorende waarde.
Resultaat: de verwijderde waarde.
exists
Argument: referentie naar een hash element, bv.
$hashname($key)
Actie: kijkt of er een waarde gekoppeld is aan $key.
Resultaat: de waarde.
keys
Argument: een hash.
qw
qw
heeft als argument een lijst van strings (zonder
quotes), gescheiden door spaties.
Het resultaat is een list van individuele strings.
Je bespaart jezelf hiermee het intikken van een hoop quotes en komma's.
scalar
Argument: (bv.?) een array of list.
Resultaat: het aantal elementen in die list.
De functie behandelt het argument in een scalar context.
sort
Argument: een array of list.
Resultaat: een gesorteerde array of list.
undef
Voordat een variabele gebruikt wordt heeft hij een ongedefiniëerde waarde.
Je kan daar op testen met de de functie
defined
, en je kan een
gedefiniëerde variabele weer ongedefinieerd maken met de
functie undef
.
Voorbeeld: undef $/;
Daarmee maak je de waarde van de "output record separator" undefined.
Links
Reageer via E-mail (dalmolen@xs4all.nl)
Deze pagina is voor het laatst gewijzigd op: 04-02-23 14:43:47