Lucrarea 5

Lucrarea 5

Regexp-uri în limbajul Perl

Operatori

=~ şi !~ - operatori pentru regexp-uri

RegExp-urile sunt un set de unelte care se utilizează în prelucrarea şirurilor de caractere. Cu ajutorul lor se execută foarte uşor căutări în şiruri, validări sau transformări de şiruri. Regexp-urile se găsesc în unelte unix ca: egrep, awk sau sed, în module sau fişiere header (regex.h) sau direct integrate în limbajul de programare (Perl).

Exemplu: $str =~ m/^abc[0-9]+def$/

La bază, regexp-urile sunt formate dintr-un set restrâns de caractere: . $ | ^ + * ? { } [ ]. Fiecare caracter poate avea diferite semnificaţii în funcţie de locul în care este plasat într-un regexp sau în funcţie de modul în care se grupează caracterele speciale.

În PERL regexp-urile se folosesc cu ajutorul operatorilor: =~ sau !~. La fel cum operatorul pentru adunare primeşte doi parametri şi retunrează o sumă, operatorul pentru regexp-uri primeşte un string şi un regexp şi returnează un rezultat: adevarăt sau fals. În cazul operatorului =~ se returnează adevarat daca regexp-ul găseste cel puţin o potrivire în şir, în caz contrar returnează fals (0).

Pentru a testa dacă un string conţine cuvântul "def" putem construi următorul regexp:

@lista = ("defilare", "definitie", "abecedar", "morcov",

"sidef", "nedeformabil");

foreach (@lista) {

print "$_: ";

if ($_ =~ m/def/) {

print "ADEVARAT\n";

}

else {

print "FALS\n";

}

}

Metacaractere de poziţionare

^ și $ - început și sfârșit de linie

În exemplul anterior expresia a returnat adevarăt indiferent de poziţia lui "def" în string-urile în care s-a căutat. Pentru a putea specifica poziţia exactă unde să se găsească şablonul (pattern) "def" într-un şir se pot folosi metacaracterele: ^ si $. Caracterul ^ marchează începutul unei linii, caracterul $ marchează sfârşitul unei linii. Astfel, ca să obţinem doar liniile care încep / se termină cu "def" modificăm expresia în felul urmator:

@lista = ("defilare", "definitie", "abecedar", "def", "morcov",

"sidef", "nedeformabil");

foreach (@lista) {

print "$_: ";

if ($_ =~ m/^def/) {

print "Incepe cu \"def\"\n";

}

elsif ($_ =~ m/def$/){

print "Se incheie cu \"def\"\n";

}

elsif ($_ =~ m/def/){

print "Contine \"def\"\n";

}

else {

print "Nu contine \"def\"\n";

}

}

Clase de caractere

[ ] şi [^ ] - potrivirea oricărui caracter dintr-un set de caractere şi potrivirea oricărui alt caracter în afara celor din clasă.

Clasele de caractere sunt construcţii care înlocuiesc un singur caracter. Într-o clasă de caractere se găsesc mai multe caractere, potrivirea făcându-se pentru oricare dintre ele o singură dată. De exemplu pentru găsirea cuvântului confort sau a cuvântului comfort se va utiliza o clasa de caractere: /co[nm]fort/.

Pentru a găsi toate cuvintele din trei caractere care încep cu a, se termină în c şi nu conţin în mijloc caracterele b sau d putem scrie: /^a[^bd]c$/. Clasa [^ ] este negarea clasei [ ] şi potriveşte orice caracter care nu este găsit în clasă.

Intervale de caractere pentru clase

Pentru a scrie mai uşor secvenţe de caractere consecutive se pot folosi intervale de caractere cum ar fi:

0-7 - oricare cifră de la 0 la 7.

A-Z - oricare literă de la A (mare) la Z (mare).

a-z - oricare literă de la a la z.

Într-o clasă poate fi nevoie de caractere ca [, ] sau ^. Acestea, după cum am văzut mai devreme fac parte din sintaxa unei clase şi nu pot fi scrise liber. În limbajul perl acestea pot fi scrise (escaped) cu ajutorul caracterului \.

De asemeanea se pot scrie câteva clase de caractere uzuale folosind prescurtări. Mai jos sunt redate câteva dintre acestea:

\d = [0-9] -- orice caracter cifră.

\w = [A-Za-z0-9_] -- orice caracter alfanumeric.

\s = orice caracter spaţiu ( spatiu, \t, \r sau \n).

Prescurtările \D, \W si \S sunt inversele (negările) prescurtarilor de mai sus.

Întrebare:

Ce caractere contine clasa: [\-\] ,] ?

Metacaracterele . şi |

Metacaracterul . poate ţine locul oricărui caracter în afară de caracterul \n (linie nouă), asemeni unui "jolly joker"

. Astfel construcţia /^From./ înseamnă "Froma", "Fromb", "FromX", "From:", etc. Acest metacaracter este o "prescurtare" a unei clase de caractere care conţine toate caracterele. De exemplu dacă se doreşte găsirea unei date scrisă sub orice formă de genul "27/10/2008", "27-10-2008" sau chiar "27:10:2008" se poate folosi construcţia /27.10.2008/. Atentie !, această construcţie este însă destul de permisivă, returnând adevarat chiar şi pentru "27010a2008".

Metacaracterul | se foloseşte în cazul în care avem două sau mai multe variante de construcţii posibile. Exemplul de mai sus: m/^co[mn]fort$/ se va putea rescrie astfel: m/^co(m|n)fort$/ . Dacă se doresc liniile care conţin "Subject:" sau "From:" se poate utiliza metacaracterul | astfel:

print $str if ($str =~ m/^([Ff]rom|[Ss]ubject):/);

Atentie !

Metacaracterul . aflat într-o clasă de caractere işi pierde semnificaţia de

"jolly joker" şi devine doar caracterul ".".

Metacaractere de multiplicare

?, +, *, {n}, {min,}, {min,max} - zero sau o apariţie, una sau mai multe, zero sau mai multe, n, minim n, minim n - maxim m apariţii

Aceste metacaractere se folosesc în combinaţii cu alte caractere, metacaractere sau clase de caractere. Ele se plasează imediat după caracterul sau clasa pe care îl/o multiplică. Vom arata comportamentul acestora prin exemple:

? - metacaracterul de opţiune "zero sau unu":

Pentru a găsi într-un text atât cuvântul color cât şi cuvântul colour se scrie: /colou?r/.

Pentru a găsi atât 1650 cât şi 1650ad sau 1650 ad se scrie: /1650 ?(ad)?/.

+ - metacaracterul "unu sau mai multe":

Pentru a găsi orice număr matricol care începe cu "ag" se foloseste: /ag\d+/

(ag7, ag342, ag4453, ag99845)

Pentru a găsi orice tag html de forma <font size=3>: /<font +size=\d+>/

Pentru a găsi şirul abc urmat de unul sau mai multe caractere x, y sau z: /abc[xyz]+/

* - metacaracterul "zero sau mai multe":

Pentru găsirea construcţiei <font size=3> sub orice formă sintactic corectă: /<\s*font +size *= *\d+\s*>/

Pentru validarea unui număr de la 00 la 29: /^[012]?\d$/

{n}, {min,}, {min,max} - metacaracterele de cuantificare:

Pentru verificarea simplă a unei date calendaristice: /\d{2}-\d{2}-\d{4}/ sau /\d{1,2}-\d{1,2}-\d{2}(\d{2})?/

Pentru verificarea simplă a unui nume de domeniu: /[a-z-]{2,63}\.[a-z]{2,4}/i

Memorarea secvenţelor găsite

( ) - memorează în variabile $1, $2,... $n construcţiile găsite.

De multe ori se doreşte memorarea construcţiilor găsite pentru utilizarea lor ulterioară. De exemplu la validarea unei date calendaristice se doreşte păstrarea zilei, a lunii şi a anului în variabile separate. Cu ajutorul regexp-urilor se poate face acest lucru direct în operaţia de validare. Astfel, folosirea funcţiei split nu mai e necesară.

$date = '10/05/1960';

if ($date =~ m/(\d{2})[-\/](\d{2})[-\/](\d{4})/) {

print "Zi: $1\nLuna: $2\nAn: $3\n\n";

}

Exercitiu:

Validaţi şi procesaţi comanda SQL:

DELETE FROM <nume_tabela> [WHERE <nume_camp=valoare>];

Substituții (search and replace) și transformări cu regexp-uri

$sir =~ s/old/new/gm; #schimba toate aparitiile "old" cu "new"

$sir =~ tr/a/z/; #transforma toate literele mici a in litere mici z

Limbajul perl pune la dispoziţia programatorului pe lângă analiza şirurilor cu regexp-uri şi funcţii de înlocuiri sau "search and replace" şi funcții de transformări. Sintaxa este deosebit de simplă şi intuitivă, foarte asemănătoare cu sintaxa pentru "match-uri". De exemplu pentru a înlocui toate apariţiile cuvântului "stop" cu "start" se va folosi codul (1) sau (2) de mai jos. În cazul (3) se observă că substituirea se execută chiar şi dacă "stop" apare în componenţa unui alt cuvânt. În exemplul (4) se utilizează delimitatorii de cuvânt, ei specifică faptul că înlocuirea trebuie să se facă doar pentru cuvintele "stop".

#1.)

$str = 'Pentru decolare apasati butonul stop.';

$str =~ s/stop/start/;

print "1. $str\n";

#2.)

$str = 'Pentru decolare apasati butonul sToP.';

$str =~ s/sTOp/start/i; # case insensitive.

print "2. $str\n";

#3.)

$str = 'Pentru decolare apasati butonul nonstop';

$str =~ s/stop/start/;

print "3. $str\n";

#4.)

$str = 'Pentru decolare apasati butonul stop nonstop';

# doar cuvintele stop, nu constructiile care contin stop.

$str =~ s/\bstop\b/start/;

print "4. $str\n";

#5.)

$str = 'automobil';

# transforma toate literele mici in litere mari.

$str =~ tr/[a-z]/[A-Z]/;

print "5. $str\n";

#6.)

$str = 'automobil';

# transforma toate vocalele in litere mari.

$str =~ tr/[a,e,i,o,u]/[A,E,I,O,U]/;

print "6. $str\n";

Spre deosebire de substituţii, transformările se execută la nivel de caracter. În exemplele (5) şi (6) se poate vedea cum lucrează această transformare.

! IMPORTANT !

Secvențele memorate se pot utiliza și în interiorul regexp-ului. Secvența următoare înlocuiește cuvintele duplicate dintr-un text utilizând delimitatorul de "cuvânt" \b și secvențe memorate:

$line = "and the the quick brown brown fox was jumping"; $line =~ s/\b(.+)\b\s\1/\1/g; print $line;

Se poate observa că \1 este prima secvență memorată iar aceasta e folosită atât în partea de "match" cât și în partea de înlocuire.

Comportamentul GREEDY în regexp-uri

Regexp-urile în limbajul perl încearcă să "match-uie" cât mai mult dintr-un şir. Acest comportament este numit comportament GREEDY. Uneori e nevoie să "prindem" doar partea de început dintr-o construcţie, de exemplu:

$text = 'mississippi';

$text =~ m/(i.*s)/;

print $1 . "\n"; # => mississippi

În acest caz, regexp-ul stochează în $1 de la primul "i" până la ultimul "s" (mississippi). Dacă se doreşte doar partea de la primul "i" până la primul "s" se va folosi caracterul ? pentru a marca multiplicatorul ca "non greedy". Iată şi exemplul:

$text = 'mississippi';

$text =~ m/(i.*?s)/;

print $1 . "\n"; # => mississippi

Exerciţii:

1.) Transformaţi /x/y/../orice în /x/orice.

2.) Folosind fişierul persoane.txt (din ataşamente) ca bază de date şi fişierul template.html ca şablon, creaţi pentru fiecare persoană din baza de date un fişier nume-prenume.html în care să fie salvată o scrisoare adresată lui "nume prenume" folosind template-ul dat.

Note finale

Aţi observat deja că toate exemplele de până acum au fost date pentru şiruri de caractere formate dintr-o singură linie. Există însă unele cazuri în care dorim să căutăm în şiruri formate din mai multe linii sau există situaţii când nu ne interesează dacă şirul e cu litere mari sau mici. Limbajul perl pune la dispoziţia programatorului o serie de modificatori (sub forma unor litere i, m, g, etc) care se plasează la sfârşitul regexp-ului astfel:

dacă se doreşte o căutare "case insensitive" se va folosi modificatorul "i" (insensitive).

Ex: /^from:/i

dacă se doreşte căutarea pe mai multe linii se va folosi modificatorul "m" (multiline).

Ex: /^from:/m

dacă se doreşte înlocuirea tuturor apariţiilor unui sablon într-un text se va folosi modificatorul "g" (global).

Ex: s/John/Ion/gm

Bibliografie

Jeffrey E. F. Friedl - Mastering Regular Expressions (Ed. O'Reilly)

The Tao of RegExp - tutorial regexp-uri

regular-expressions.info - colecţie de programe, documentatii, exemple legate de regexp-uri