A Perl nyelv egyik, ha nem a legnagyobb erõssége az igen fejlett
mintaillesztési lehetõség, vagy másképp fogalmazva a
reguláris kifejezések kezelése. Persze a Perl nyelv elõtt jóval
voltak már UNIX operációs rendszer alatt olyan eszközök, amelyek
reguláris kifejezéseket használtak, de a Perl nyelvnben talán
mindegyiknél jobb és teljesebb lehetõségek vannak.
Ha még nem használtál soha reguláris kifejezéseket, akkor ennek a
fejezetnek az elolvasása elõtt ajánlott az
m operátorról illetve az s operátorról
szóló fejezetek elolvasása. Ugyanakkor azok teljes megértéséhez
szükséges ennek a fejezetnek az elolvasása. Így tehát nem tudom,
hogy melyiket érdemes elolvasni elõször. Feltehetõleg az egyiket,
aztán a másikat, és azután néhányszor újra.
Bevezetõ
Reguláris kifejezésre rendkívül egyszerû példát
mutatni. A következõ példa a @mail tömb elemeirõl
próbálja meg eldönteni, hogy jók lesznek-e e-mail címnek.
@mail = (
'verhas@mail.digital.hu',
'hab.akukk%mikkamakka@jeno',
);
for( @mail ){
if( /^.*@\w+\..+$/ ){
print "$_ jó email címnek látszik\n";
}else{
print "$_ Ez nem jó cím\n";
}
}
Az elsõ utasítás a @mail tömböt hozza létre. Ennek
elemein megy végig a for ciklus, és minden egyes elemet megvizsgál,
és megpróbálja eldönteni, hogy jó lesz-e elektronikus
levélcímnek vagy sem. Mivel a for ciklusnak nem adtunk meg
változót, ezért a Perl a $_ változót használja ciklus
változónak, és mivel az if utasításban sem adjuk meg,
hogy mely változót akarjuk a reguláris kifejezéshez hasonlítani,
ezért ez is ezt a változót használja. (Minõ véletlen
egybeesés.)
A példabeli reguláris kifejezés szavakkal elmondva a következõt
jelenti:
a füzér kezdõdjön ^
nulla vagy több bármilyen karakterrel .*
utána következzen egy @ karakter
majd legalább egy, vagy több betû \w+
majd egy pont \.
végül még legalább egy vagy több bármilyen karakter .+
és legyen vége a füzérnek $.
A kimenet:
verhas@mail.digital.hu jó email címnek látszik
hab.akukk%mikkamakka@jeno Ez nem jó cím
A reguláris kifejezéseket, mint a fenti példában is
látható használhatjuk arra, hogy bizonyos szintaktikus szabályoknak
való megfelelést vizsgáljunk füzéreknél. Ehhez az m
operátort használtuk a fenti példában. Magát az operátort
azért nem kellett kiírni, mert a / jelet használtuk
határolónak, a fûzért pedig azért nem, mert az a default
változó, a $_ volt. Az if utasításbeli kifejezést
írhattuk volna
$_ =~ m/^.*@\w+\..+$/
alakban is.
A másik lehetõség reguláris kifejezés
használatára, amikor bizonyos részeket ki akarunk cserélni
egy fûzérben. Nézzük példának a következõ kis
programot:
$text = 'Java szigetén nem használnak JavaScript programokat.';
$text =~ s/Java(?!Script)/Borneo/;
print $text;
aminek a kimenete
Borneo szigetén nem használnak JavaScript programokat.
Az s operátor egy reguláris kifejezést és egy
interpolált füzért használ.
Végignézi a változót, amely a példában $text
és az elsõ olyan részfüzért, amely megfelel a reguláris
kifejezésnek kicseréli a második paraméterként megadott
füzérre. Így lesz Java szigetébõl Borneó.
Érdemes megfigyelni, hogy ezeknél a mûveleteknél nem = jelet
használunk, hanem =~ operátort. Ezt pedig nem szabad
összetéveszteni a ~= operátorral! (A nyelv szépségei!) Ha
ugyanis valaki az elõbbi példában a
$text = s/Java(?!Script)/Borneó/;
írná, akkor a helyettesítési operátor (s///) nem a
$text változót, hanem a default $_ változót keresné,
amelyben nem talána egyetlen Java alfüzért sem, és
visszatérési értékként üres füzért ad vissza.
Más szavakkal: a program nem írna ki semmit, vagy precízebben semmit írna
ki.
Részletesen és precízen az m
operátorról, és a s operátorról.
Ezeket egy kicsit nehéz megérteni a reguláris kifejezések ismerete
nélkül, de vígasztaljon, hogy a reguláris kifejezéseket pedig
nehéz megérteni az m és az
s operátorok ismerete nélkül.
Amikor a Perl egy füzért megpróbál hozzáilleszteni egy
reguláris kifejezéshez mind a füzérben, mind pedig a reguláris
kifejezésben balról jobbra halad, egészen addig, amíg el nem ér
a reguláris kifejezés vagy a füzér végére. Ha ekkor az
egész reguláris kifejezést hozzá tudta illeszteni a
füzérhez, akkor a füzér megfelel a reguláris kifejezésnek,
ha nem, akkor pedig nem. (Például az m operátor
igaz, vagy hamis értéket ad vissza.)
A meta karakter fogalma
Az illesztés során a reguláris kifejezésben minden
közönséges karakter, amelyik nem metakarakter (definíció
mindjárt) önnmagának felel meg. Ha tehát a reguláris
kifejezés következõ karaktere egy 'a' betû, akkor a
füzér következõ illesztendõ karaktere is 'a' betû kell,
hogy legyen.
A metakarakterek speciális karakterek, amelyek a reguláris kifejezésben
speciális jelentõséggel bírnak. Ilyen például a
. pont, amely a füzérben bármilyen nem újsor karakternek megfelel.
Egy másik speciális karakter a $ jel, amelyik a füzér
végének, vagy a füzér végén lévõ újsor
karakternek felel meg. Hasonlóan a ^ karakter a füzér elejének
felel meg. Ezt a viselkedést mind az m, mind pedig az
s operátoroknál változtatni lehet az m
és s opciókkal. Az m opció esetén a füzért
többsorosnak tekinti a rendszer, ami praktikusan annyit jelent, hogy minden sor
végét meg lehet találni a $ jellel, ami a reguláris
kifejezésben a sor vagy a füzér végét jelenti, és
hasonlóan minden sor elejét a ^ jellel. Ennek az ellentéte a s
opció, amely esetben a füzért egysorosnak tekinti a Perl, és a $
csak a füzér végét fogja megtalálni, vagy a füzér
végén álló soremelést a ^ jel pedig csak a
füzér elejét úgy, mintha sem az s sem pedig a m
opciót nem adtuk volna meg. Az s opció igazi értelme, hogy
ennek megadásakor a reguláris kifejezésekben a . pont karakter megfelel
a soremelés karaktereknek is, míg enélkül az opció
nélkül ezeknek a karaktereknek nem felel meg ez a metakarakter.
Az egyik legfontosabb metakarakter a \ amelyet ha egy metakarakter elé írunk
a füzérben a karakter pontosan önnmagának fog megfelelni, így
\. egy pontot jelent a füzérben vagy \\ egy fordított törtvonal
karaktert. Ha nem metakarakter elé írjuk a fordított törtvonalat, akkor
elõfordulhat hogy más jelentést kap a karakter, például \t
a tabulátor, vagy \w a betû karaktereknek felel meg, de errõl
még késõbb.
Zárójelek
A zárójelek is speciális jelentéssel bírnak. Ezekkel a
reguláris kifejezés egyes részeit lehet bezárójelezni,
és ezekre a részekre lehet a késõbbiekben hivatkozni a $1,
$2, ... változókkal a reguláris kifejezésen kívül,
vagy a \1, \2, ... referenciákkal a reguláris kifejezésen
belül. Példaképpen
$text = 'Java szigetén nem használnak szigonyt.';
$text =~ /(Ja(va)).*(szi).*\3(g(e|o))/;
print "$1 $2 $3 $4 $5\n";
kimenete
Java va szi go o
Látható, hogy a számozási sorrend a nyitó
zárójelek szerint megy, és nem a záró zárójelek
szerint. Ennek csak abban az esetben van jelentõsége, ha, mint ebben a
példában, egymásba vannak ágyazva a zárójelek.
Lehetõség van arra is, hogy úgy zárójelezzünk be egy
részt a reguláris kifejezésbõl, hogy az ne hozzon
létre referenciát. Ehhez a (?: nyitó karakter sort és a
szokásos ) zárójelet kell használni, például:
$text = 'Java szigetén nem használnak szigonyt.';
$text =~ /(Ja(?:va|vi)).*(szi).*\2(g(e|o))/;
print "$1 $2 $3 $4\n";
kimenete
Java szi go o
Itt rögtön látunk egy új metakaraktert, a | jelet. Ez a jel
választást jelent a bal és a jobb oldalán álló
rész reguláris kifejezések közül bármelyik megfelelhet
a füzér éppen soron levõ darabjának.
Karakter osztály
Még egy érdekes zárójel van a reguláris kifejezésekben,
a [ és ]. Ezek kartakter osztályokat fognak közre. Ha egy
reguláris kifejezésben azt írjuk, hogy [acfer], akkor az az a,
c, f, e és r betûk bármelyikének megfelel.
Ugyanakkor rövidíthetünk is karakterosztályok
megadásánál. Például [a-f] az összes a
és f közötti karakternek felel meg, vagy [a-fA-F] ugyanez a karakter
osztály, de a kisbetûk és a nagybetûk is. Ha a legelsõ karakter a
karakterosztály megadásánál a [ karakter után egy ^
karakter, akkor a karakter osztály invertálódik, azaz [^a-f]
bármilyen karakternek megfelel, kivéve az a és f közötti
karaktereket. Karakterosztály megadásánál a | karakter is
közönséges karakternek számít.
Ismétlés
Egy reguláris kifejezés egy részére, vagy az egész
reguláris kifejezésre elõírhatjuk, hogy hányszor kell
egymás után alkalmazni. Ezt a reguláris kifejezések
ismétlést elõíró modosítóival tehetjük meg,
amelyeket mindig a reguláris (rész)kifejezés után kell írni. Ezek:
*
nulla vagy többszöri ismétlés
+
egy, vagy többszöri ismétlés
?
nulla, vagy egyszer
{n}
pontosan n-szer
{n,}
legalább n-szer
{n,m}
legalább n-szer, de legfeljebb m-szer
Ha a kapcsos zárójel bármilyen más környezetben fordul elõ,
akkor normál karakterként értelmezi a Perl. n és m nulla
és 65,535 közötti értékek lehetnek.
Alaphelyzetben ezek az ismétlési módosítók falánkak,
és annyira elõreszaladnak, amennyire csak lehet. Ezt lehet visszafogni azzal, ha az
ismétlési módosító mögé egy ? jelet
írunk. Ekkor csak annyit ismételnek ezek a módosítók, amennyit
feltétlenül kell. Példaképp egy reguláris kifejezés
illesztés * és *? módosítóval
'Java szigetén nem használnak szigonyt.' =~ /Ja(.*)g(?:e|o)/;
print "$1\n";
'Java szigetén nem használnak szigonyt.' =~ /Ja(.*?)g(?:e|o)/;
print "$1\n";
és a kimenete
va szigetén nem használnak szi
va szi
Speciális karakterek
Mivel a reguláris kifejezések mint interpolált füzérek
kerülnek kiértékelésre, ezért a következõk is
használhatók:
\t
tabulátor (HT, TAB)
\n
újsor, soremelés (LF, NL)
\r
kocsivissza karakter (CR)
\f
lapdobás (FF)
\a
csengõ (bell) (BEL)
\e
escape (ESC)
\033
oktális kód megadása (mint a PDP-11 nél)
\x1B
hexa karakter
\c[
kontrol karakter
\l
a következõ karakter kisbetûsre konvertálva
\u
a következõ karakter nagybetûsre konvertálva
\L
kisbetûsek a következõ \E-ig
\U
nagybetûsek a következõ \E-ig
\E
kisbetû, nagybetû módosítás vége
\Q
reguláris kifejezések meta karakterei elé fordított törtvonalat rak a következõ \E-ig
Ezeken kívül a Perl még a következõket is definiálja
\w
egy szóban használható karakter (alfanumerikus karakterek és aláhúzás)
\W
minden ami nem \w
\s
szóköz fajta karakter, szóköz, tabulátor, újsor stb.
\S
minden ami nem \s
\d
számjeg
\D
nem számjegy
Ezek a jelölések karakterosztályok megadásában is
használhatók, de nem intervallum egyik végén.
A következõk is használhatók még reguláris
kifejezésekben:
\b
szó határának felel meg
\B
nem szó határnak felel meg
\A
csak a füzér elején
\Z
csak a füzér végén
\G
ott folytatja ahol az elõzõ m//g abbahagyta
A \b karakterosztályban használva visszalépés karakternek
felel meg. Egyébként mint szóhatár olyan helyet jelöl, ahol az egyik
karakter \w és a mellette levõ \W. Ennek eldöntésére
a füzér elején az elsõ karakter elé és a füzér
végén az utolsó karakter utánra egy-egy \W karaktert
képzel a rendszer.
A \A és \Z ugyanaz, mint a ^ és a $ azzal a
különbséggel, hogy ezek még a m opció használata
esetén sem felelnek meg a füzér belsejében levõ soremelés
karakternek.
Egyéb kiterjesztések
A reguláris kifejezések a Perl nyelvben a szokásos egyéb UNIX
eszközökhöz képest további kiterjesztéseket is tartalmaznak.
Így reguláris kifejezésen belül lehet használni
(?#megjegyzés)
megjegyzéseket lehet a reguláris kifejezésekben elhelyezni.
Amennyiben az x opciót használjuk egy sima # is megteszi.
(?:regexp)
Zárójelezett reguláris kifejezés, amely azonban nem
generál referenciát, azaz nem lehet rá hivatkozni a $1, $2,
... változókkal, illetve a \1, \2 ... visszahivatkozásokkal.
Ugyanakkor nagyon hasznosak, akkor ha valamilyen alternatívát kell megadni a |
metakarakterrel.
(?=regexp)
Nulla hosszúságú elõrenézés. Ezzel a
konstrukcióval egy olyan rész reguláris kifejezést lehet megadni, amelyet
a Perl leellenõriz, de mégsem vesz bele az illesztett listába.
Például /\w+(?=\t)/ reguráris kifejezésnek egy olyan szó
felel meg, amelyet egy tabulátor követ, a tabulátor azonban nem veszi figyelembe
a Perl, csak megnézi, hogy ott van. Ha a helyettesítés operátort
használjuk, akkor
$t = 'jamaica rum rum kingston rum';
$t =~ s/([aeoui])(?=\w)/uc($1)/ge;
print $t;
kimenete
jAmAIca rUm rUm kIngstOn rUm
ami azt jelenti, hogy a mintaillesztés során a Perl megnézte, hogy betû
áll-e a magánhangzó után, de azt a betût már nem tekintette
az illesztett, és így helyettesítendõ füzérdarab
részének. Ennek megfelelõen ez a kis programdarab minden olyan
magánhangzót nagybetûre cserélt, amely nem szó
végén állt.
(?!regexp)
Negatív nullahosszúságú elõrenézés.
Hasonló a (?= ... )-höz, de akkor fogadja el a mintát, ha a
következõ füzérdarab nem felel meg a megadott reguláris
kifejezésnek. Az elõzõ példát használva és
persze módosítva egy kicsit
$t = 'jamaica rum rum kingston rum';
$t =~ s/([aeoui])(?!\w)/uc($1)/ge;
print $t;
kimenete
jamaicA rum rum kingston rum
ami minden olyan magánhangzót cserél nagyra, amelyet nem betû
követ. Nagyon fontos megjegyezni, hogy ezekkel a konstrukciókkal csak elõre
lehet nézni, visszafelé nem. Ha ugyanis azt írjuk, hogy (?!Borneo)Script,
akkor az nem azt jelenti, hogy minden olyan Script amely elõtt nem
Borneo áll. Ez a reguláris kifejezés minden Script-et meg fog
találni, a BorneoScript-eket is, illetve annak Script részét,
hiszen amikor a (?!Borneo) részt értékeli ki a Perl azt fogja
találni, hogy a vizsgált helyen nem Borneo áll, hanem Script. Ez
így neki rendben van, megy tovább és ismételten látni fogja, hogy
az adott helyen Script áll, ezt most már illeszti is a reguláris
kifejezés további részéhez, hiába állt a Script
elõtt Borneo.
(?imsx)
Reguláris kifejezés értelmezését
módosító opció megadása a reguláris kifejezésen
belül. Ezeket az opciókat általában az m vagy
s operátorok után szoktuk megadni, néha azonban
szükség lehet, hogy magába a reguláris kifejezésbe
kerüljön bele az opció. Ilyen lehet az, amikor a reguláris kifejezés
egy változóban van megadva. Az egyéb opciók, mint az e vagy g
nem adhatók meg a reguláris kifejezésen belül, hiszen ezek nem a
reguláris kifejezésre vonatkoznak, hanem magára a m
és s operátorok végrehajtására. Az
opciókat érdemes a reguláris kifejezés elején megadni, mert
különben igen érdekes, és meglehetõsen nehezen
értelmezhetõ hatásuk lesz, például a
$t = 'jAmAIca rUm rUm kIngstOn rUm';
$t =~ s/(?i)ica/juli/g;
$t =~ s/RuM(?i)/bor/;
print $t;
hatására miért csak az utolsó rUm-ból lesz bor:
jAmAjuli rUm rUm kIngstOn bor
Visszalépéses kiértékelés
Amikor a Perl egy reguláris kifejezést illeszt egy füzérhez az
illesztés csak akkor sikeres, ha az egész reguláris kifejezést
sikerült illesztenie. Amikor az illesztés során a mintaillesztõ algoritmus
elõre halad, és valahol elakad, akkor még nem adja fel, hanem
megpróbál visszalépni és újra próbálkozik.
Így, amikor a
$text = 'Java szigetén nem használnak JavaScript programokat.';
$text =~ s/Java(?=Script)/Borneo/;
print $text;
példában elindul a mintaillesztés a Perl eljut egészen az
elsõ Java végéig, és illeszti a reguláris
kifejezést. Itt azonban elakad, mert a (?=Script) megkövetelné, hogy a
Java szó után a Script szó következzen. Ezért
visszalép és próbálja a füzér további
részéhez illeszteni a reguláris kifejezést, amíg el nem jut a
JavaScript szóhoz, amelyhez sikerül az illesztés, és ennek
megfelelõen megtörténik a csere, így az eredmény
Java szigetén nem használnak BorneoScript programokat.
Egy másik példát megnézve
$t = 'Mi van a jing és a jang között? Talán a Jangce?';
$t =~ /jing(.*)jang/i;
print $1;
az eredmény
és a jang között? Talán a
Ennek az az oka, hogy amikor a jing szót megtalálja az illesztés a
.* részkifejezésseé addig rohan elõre, ameddig csak tud.
Elõször a füzér végéig. Itt azonban elakad, és elkezd
visszafelé lépkedni. Egészen a Jangce szóig, aholis sikeres a
reguláris kifejezés maradék részének az illesztése is.
Ismét egy példát nézve a
$_ = "Van 1 számom: 53147";
if ( /(.*)(\d*)/ ) {
print "Ami van: <$1>, a szám pedig <$2>.\n";
}else{
print "nincsen számom...\n";
}
az eredménye
Ami van: <Van 1 számom: 53147>, a szám pedig <>.
aminek az oka, hogy a \d* hozzáilleszthetõ a füzér
végén egy üres füzérhez. Helyesen:
$_ = "Van 1 számom: 53147";
if ( /(.*\D)(\d+)/ ) {
print "Ami van: <$1>, a szám pedig <$2>.\n";
}else{
print "nincsen számom...\n";
}