
Flavio Poletti
Consulente in telecomunicazioni per una piccola società a Roma, usa Perl
per lavoro e per diletto, con particolare attenzione all'automazione di
processi ripetitivi e noiosi. www.polettix.it
|
Se avete sentito parlare delle espressioni regolari, ma avete sempre
- pensato che fossero solo concorrenti delle fave di fuca...
- dichiarato che voi tutto, ma la matematica no...
- sostenuto che un'accozzaglia di caratteri esprime poco, e non è sicuramente regolare...
... allora forse riusciamo a farvi cambiare idea.
Cominciamo a renderci conto che probabilmente sappiamo già usare dei lontani cugini delle espressioni
regolari: le cosiddette wildcard. Vi è mai capitato di usare l'asterisco cercando un file? Tipo: per
avere tutti i file PDF, digitare qualcosa come dir *.pdf? Beh, che lo vogliate o no, vi state già piegando al
concetto che le espressioni regolari vogliono esprimere.
Analizziamo la wildcard *.pdf. Cosa significa? Semplice: "prendi tutti quei file che
hanno una sequenza iniziale qualunque, ma che finiscono con i caratteri .pdf", nell'ordine. E se
vogliamo cercare tutti i file PDF di ricette? Beh, probabilmente qualcosa tipo ricette*.pdf
potrebbe fare al caso nostro, perché sta chiedendo: "tutti i file che cominciano con ricette, poi
hanno una sequenza qualsiasi di caratteri, ma finiscono con .pdf". Nessuno credo abbia da
obiettare sulla semplicità di questo strumento.
Il problema è che semplice spesso litiga con flessibile e versatile. Se volessimo trovare tutti i file che
"iniziano con ricette, poi hanno una qualunque serie di cifre numeriche, e finiscono con
.pdf" saremmo già nei guai. Qualcuno non ce l'ha fatta a star dentro a questa gabbia: voleva
tutto. Voleva un sistema per poter descrivere in dettaglio che tipo di struttura deve avere un testo perché
possa essere considerato "valido". E sono nate le espressioni regolari.
Visto che l'idea di stare in gabbia piace a pochi, si trovano molte varianti delle espressioni regolari, ed ogni linguaggio o strumento (come il
mitico grep) presenta le sue peculiarità. Perl però si distingue in modo particolare: le espressioni regolari (le cosiddette
regex) fanno parte del linguaggio, non di una qualche libreria esterna, il che rende semplice inserirle nel proprio codice.
Negli esempi che faremo, assumeremo sempre di dover verificare se un dato testo è compatibile con una regex
oppure no. Se il nostro testo è contenuto in $testo, questa verifica viene fatta come segue:
if ($testo =~ /QUI IL CONTENUTO DELLA REGEX/) {
print("ok, compatibile\n");
}
else {
print("NO, incompatibile\n");
}
L'operatore =~ è detto operatore di binding, per il
fatto che effettua una connessione (il binding) fra la variabile
$testo e l'espressione regolare. In sostanza, può essere letto
come "applica l'espressione regolare a destra sul testo della variabile
a sinistra". Attenzione, dunque: non si tratta né di un'assegnazione né di un confronto.
Inoltre, seguiremo la tradizione dicendo che c'è un match se il testo è compatibile con la regex.
A questo punto, allacciamo le cinture e partiamo!
I concetti base per iniziare a giocare con le regex sono fondamentalmente due: gli elementi ed i
quantificatori. Gli elementi rappresentano grossolanamente una parte di testo. I quantificatori esprimono
invece quante volte ci si aspetta che il dato elemento possa essere ripetuto.
Gli elementi più semplici sono i caratteri alfanumerici stessi. Essi semplicemente rappresentano sé stessi;
l'espressione regolare
/a/
indica che $testo deve contenere almeno una a. Alcuni caratteri, però, non rappresentano sé
stessi, come il punto:
/./
Questo significa che $testo deve contenere almeno un carattere. Qualunque carattere. Va bene, quasi qualunque carattere, ma
per ora va bene così.
A questo punto siamo già in grado di scrivere alcune espressioni regolari:
/ricette.pdf/ ==matcha==> 'ricette-pdf', 'ricettexpdf', ...
Ebbene sì, non si parla sempre di file! Se vogliamo un punto vero e proprio dobbiamo segnalarlo con un
backslash:
/ricette\.pdf/ ==matcha==> 'ricette.pdf' ma NON 'ricette-pdf'
Come controlliamo che ci siano almeno tre caratteri fra ricette e .pdf? Un sistema è
ripetere:
/ricette...\.pdf/ ==matcha==> 'ricetteABC.pdf' e 'ricette....pdf'
A parte che per 50 caratteri invece di 3 diventa difficile, come facciamo, ad esempio, a dire "una qualunque
sequenza (anche vuota) di qualunque carattere"? A questo ci pensano i quantificatori:
* ripete l'elemento 0 o più volte
+ ripete l'elemento 1 o più volte
? l'elemento 0 o 1 volta
{n} l'elemento esattamente n volte
{n,} l'elemento almeno n volte
{n,m} l'elemento un numero minimo di n volte e massimo di m
Dunque, la nostra wildcard "tutti i file PDF che hanno ricette e .pdf" si scrive
così (per il momento!)
/ricette.*\.pdf/
C'e ancora qualcosa che non va, però. Le regex sono flessibili, abbiamo detto; questo si traduce nel fatto che
assumono il meno possibile su quello che volete realmente fare. La regex sopra dice: "$testo deve
contenere la sequenza ricette, seguita da una sequenza qualsiasi di caratteri, seguita da
.pdf". Né più, né meno. Che ne dite allora di questo testo?
pipporicette1234abcdef.pdf-old
^^^^^^^^^^^^^^^^^^^^^
Hey! Verifica! Ma noi vogliamo i nomi che cominciano con ricette, e finiscono con
.pdf. Beh, va detto utilizzando dei marcatori speciali:
^ marca l'inizio della riga
$ marca la fine della riga
Questi, in realtà, sono degli elementi a tutti gli effetti, solo che la loro lunghezza è nulla (in termini di
numero di caratteri su cui è verificato il match) e la loro posizione è fissata. La regex giusta è allora:
/^ricette.*\.pdf$/
Già sento il brusio. Qualcuno sta borbottando: "non mi sembra che siamo andati molto oltre le wildcard!". Hey,
ci stiamo solo scaldando, quindi calma, eh? Avevamo detto che volevamo qualcosa tipo "inizia con
ricette, prosegue con caratteri numerici, finisce con .pdf". Mi sembra che siamo a
buon punto, manca solo la roba dei numeri. Qui il problema è che "una sequenza di caratteri numerici" vuol dire
"una sequenza di caratteri 1, oppure 2, oppure 3, ecc.". Come si dice "oppure"? Semplice, con la barretta
verticale:
/0|1|2|3|4|5|6|7|8|9/
Qui si pone un problema, però. Se scriviamo:
/^ricette0|1|2|3|4|5|6|7|8|9*\.pdf$/
otteniamo:
- inizia con
ricette0
- oppure contiene
1
- oppure contiene
2
- ...
- oppure finisce con una sequenza qualsiasi di
9, seguita da .pdf
Oooops. Stiamo facendo l'OR sugli elementi sbagliati! Che faremmo di solito? Useremmo le parentesi. E qui?
Pure:
/^ricette(0|1|2|3|4|5|6|7|8|9)*\.pdf$/
Funziona? Sì, ma è brutto. È brutto per due motivi: perché è brutto (ok, sono ricorsivo), e perché si rischia
di sbagliare. Perl di solito mette a disposizione più frecce per colpire la stessa mela; nel nostro caso, la
prima è \d, che vuol dire semplicemente "qualunque carattere numerico":
/^ricette\d*\.pdf$/
Chiaro, conciso, e comincia a sembrare un'accozzaglia. Ma che potenza! Riecco il brusio. È chiaro che il tizio in seconda fila (che esiste davvero!) vuole fare polemica: "e se volessi solo cifre numeriche da 6 a 9?". Beh, a questo ci penso
con la seconda freccia: le classi di caratteri. Un esempio prima di tutto:
/^ricette[6789]*\.pdf$/
Quello che racchiudo fra parentesi quadre è un insieme di caratteri, ognuno dei quali può andare bene. In
pratica, [6789] vuol dire "un qualsiasi carattere fra 6, 7, 8 e 9". Visto che sono pigro, e che
soprattutto potrei dimenticare qualcosa, è meglio usare un intervallo:
/^ricette[6-9]*\.pdf$/
Nelle classi posso mettere anche gli altri caratteri, ovviamente; quindi:
/^ricette[6-9a-fp-z]*\.pdf$/
vuol dire quello che immaginate: "inizia con ricette, seguito da una qualsiasi sequenza di cifre
fra 6 e 9, o di lettere fra a e f, o di lettere fra p e z, seguita dalla stringa di chiusura
.pdf". Spero che il tizio della seconda sia contento ora.
Con tutta questa tiritera non abbiamo visto nemmeno una riga di codice utile. Senza contare che abbiamo solo
scalfito la superficie! Però io sono pigro, voi pure, per cui vi do una bella canna da pesca, così da mangiare
ve lo trovate da soli.
Quell'impareggiabile archivio che è CPAN contiene un modulo preziosissimo per chi si affaccia nel mondo delle
regex: YAPE::Regex::Explain (YAPE sta per Yet Another Parser/Extractor, ossia Ancora un
altro parser/estrattore di testo). Voi gli date una regex, lui vi dice cosa fa. L'uso è piuttosto
semplice:
#!/usr/bin/perl
use strict; # Sempre!
use warnings; # Sempre in debug!
use YAPE::Regex::Explain;
$|++; # Disabilita buffering
print("Regex: ");
while (<>) {
chomp; # Elimina il carattere a-capo
last if /quit/; # Esci se richiesto
print(YAPE::Regex::Explain->new($_)->explain());
print("Regex: ");
}
Il programma effettua un ciclo sull'input, plausibilmente da tastiera. Voi inserite una riga: se contiene la
parola quit esce dal ciclo, altrimenti considera quello che avete immesso come una regex, e vi
spiega cosa vuol dire. Ad esempio, immettendo una nostra vecchia conoscenza:
^ricette[6-9a-fp-z]*\.pdf$
otteniamo:
NODE EXPLANATION
----------------------------------------------------------------------
(?-imsx: group, but do not capture (case-sensitive)
(with ^ and $ matching normally) (with . not
matching \n) (matching whitespace and #
normally):
----------------------------------------------------------------------
^ the beginning of the string
----------------------------------------------------------------------
ricette 'ricette'
----------------------------------------------------------------------
[6-9a-fp-z]* any character of: '6' to '9', 'a' to 'f',
'p' to 'z' (0 or more times (matching the
most amount possible))
----------------------------------------------------------------------
\. '.'
----------------------------------------------------------------------
pdf 'pdf'
----------------------------------------------------------------------
$ before an optional \n, and the end of the
string
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
Il raggruppamento più esterno può essere ignorato, viene sempre aggiunto da Y::R::E per motivi suoi. Per il
tale in seconda fila che si agita: sì, sì, sono spiegati nel manuale. Tutto il resto della stampata corrisponde a quanto abbiamo già
spiegato sopra.
Il tizio in seconda fila oggi non dà tregua (sai che novità...): non gli piacciono gli strumenti a riga di comando! Attenzione, qui chiamiamo
la cavalleria:
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Tk;
5 use YAPE::Regex::Explain;
6 my $mw = MainWindow->new();
7 my $top_frame = $mw->Frame();
8 $top_frame->pack(-side => 'top');
9 my $text = $mw->Scrolled(qw/Text -scrollbars e/);
10 $text->pack(-side => 'bottom', -expand => 1, -fill => 'both');
11 $top_frame->Label(-text => 'Espressione regolare:')->pack(-side => 'left');
12 my $entry = $top_frame->Entry();
13 $entry->pack(-side => 'left');
14 $top_frame->Button(
15 -text => 'Spiega',
16 -command => sub {
17 my $yape = YAPE::Regex::Explain->new($entry->get());
18 $text->selectAll();
19 $text->Insert($yape->explain());
20 $text->unselectAll();
21 }
22 )->pack(-side => 'left');
23 MainLoop();
Anche se non è proprio in tema, diciamo cosa fa! Viene creato l'oggetto che rappresenta la finestra principale
(riga 6); tale finestra è divisa in due parti, una superiore ($top_frame) ed una inferiore
($text). La prima contiene un'etichetta di testo ("Espressione regolare"), un campo per
l'immissione ed il bottone per chiedere la spiegazione (riga 14). La seconda è dove verrà stampata la
spiegazione della Regex. Alla pressione del tasto "Spiega", viene chiamata la sub associata al parametro
-command, che cancella il contenuto di $text e vi immette la spiegazione data da
Y::R::E.
L'eliminazione dei numeri di riga è lasciata come esercizio :)
... è consigliabile cominciare con una punta piccola e fare un invito. Questo è esattamente lo scopo di
questo articoletto: invitarvi ad entrare nel mondo delle regex, dal quale non uscirete più dopo averne appreso
i concetti basilari e le potenzialità.
Il materiale di riferimento è piuttosto copioso. Nel manuale di Perl troviamo il riferimento principe - perlre, in inglese - accanto a
dissertazioni introduttive più complete di questa, come ad esempio perlrequick, in italiano,
e perlretut, in inglese. Volontari per
le traduzioni?
Ti è piaciuto questo articolo? Iscriviti al feed!
|