Weblabor - A kiindulopont webmestereknek
Leírások+Referenciák / Perl röviden / Reguláris kifejezések

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";
  }

az eredménye

Ami van: <Van 1 számom: >, a szám pedig <53147>.