Mar, 22 Novembre 2005 - 07:58
Inviato da: Marco Coïsson
Iniziamo finalmente a parlare di uno degli argomenti più complessi, ma anche più potenti ed utili, di Perl; per la verità le espressioni regolari o regular expression (regexp per gli amici) non sono una caratteristica esclusiva di questo linguaggio, e si trovano in molti altri contesti, ma senz'altro in Perl hanno un'importanza tale da giustificare, quasi da sole, l'utilizzo di questo linguaggio. Ma che cosa saranno, allora, di tanto importante queste regular expression? A definirle si potrebbe restare un po' delusi: esse, anche dette pattern (schemi) in Perl, sono schemi che possono essere presenti oppure no all'interno di una stringa. Se la cosa non vi eccita più di tanto, è perché ancora non avete presente le meraviglie che con le regular expression potete fare!
Aprite allora il vostro editor di testo preferito e digitate il seguente codice:
#!/usr/bin/perl
$lettera="a";
while(<>)
{
if(m/$lettera/)
{
chomp;
print "All'interno di $_ ho trovato $&;\n";
print "prima c'era $` e dopo $'\n";
}
else
{
chomp;
print "Non ho trovato $lettera all'interno di $_\n";
}
}
Salvatelo come volete (ad esempio come trovalettera.pl) ed assegnategli i permessi di esecuzione. Eseguitelo e iniziate a digitare del testo. Ogni volta che premete invio o a capo il programma vi segnalerà se nel testo che avete digitato sia o meno presente la lettera "a". Naturalmente con questo programma non ci fate molto, e quando vi stufate potete uscire, al solito, con control-D per interrompere il flusso di dati verso l'operatore diamante. Ma anche se il programma non è niente di particolarmente utile, possiamo imparare molto da esso.
Se l'inizio non è una novità (il solito ciclo while sul testo inserito dallo standard input o da file se avete passato argomenti di invocazione), molto più interessante è l'istruzione if che c'è subito dopo. Qui infatti, nel suo test logico, troviamo un costrutto che non avevamo mai incontrato prima: m//. Al posto delle due slash, così come per il comando qw// di cui abbiamo parlato in passato, potete usare qualunque simbolo vi piaccia: parentesi tonde, quadre, graffe, punti esclamativi, cancelletti, barre verticali, accenti circonflessi, simboli di percento ecc. Ma quello che conta è ciò che c'è al loro interno. Lì infatti troviamo, manco a dirlo, una regular expression.
Non è vero, direte voi, lì dentro c'è una variabile ($lettera), che di fatto è la stringa "a", non c'è nessuna regular expression; ma in effetti c'è; si tratta solo della forma più semplice di pattern, quello individuato da una semplice lettera. Il costrutto m//, infatti, esegue un match (ricerca) di quanto specificato dal pattern compreso tra le due slash. Sì, ma dove lo cerca? In mancanza di altre specificazioni, usa il solito Perl favorite default$_, che contiene la riga di testo digitata dall'utente (o letta da file) dal momento che pure l'operatore diamante ha usato il Perl favorite default per la sua assegnazione. Orbene, l'operatore di ricercam// verifica se all'interno di quanto contenuto in $_ ci sia o meno quanto specificato dal pattern riportato tra le due slash (o tra i simboli che avete scelto). Nel caso specifico, la regular expression che cerchiamo è estremamente semplice, una sola lettera (potete cambiarla modificando l'assegnazione della variabile $lettera all'inizio del programma, naturalmente). Nel caso in cui la lettera in questione venga trovata nella riga di input, il programma effettua le seguenti operazioni:
toglie il carattere di new line dal fondo di $_ (siccome chomp viene scritto senza argomenti, indovinate un po', agisce sul Perl favorite default);
mediante print scrive:
il testo originale, contenuto in $_;
il testo che è stato trovato dall'operatore m//, contenuto nella variabile speciale $&;
il testo contenuto in $_ che si trova prima di quello che è stato trovato; tale testo è contenuto nella variabile speciale $`;
il testo contenuto in $_ che si trova dopo quello che è stato trovato; tale testo è contenuto nella variabile speciale $'.
Se invece la lettera in questione non viene troata nella riga di input, il programma ci informa di questo spiacevole episodio.
Le variabili speciali $`, $& e $', in quest'ordine, riproducono esattamente il testo di partenza; infatti, se nel nostro programma aggiungessimo l'istruzione:
print $` . $& . $';
riscriveremmo esattamente in testo di input contenuto in $_.
Pur avendo un certo fascino, questo programmino è però scarsamente utile. Possiamo incominciare a trasformarlo in qualche cosa di più interessante, senza tuttavia complicare la regular expression utilizzata, se cerchiamo di ottenere un programma che sia in grado di contare quante volte la lettera scelta compaia nel testo inserito dall'utente (o letto da file). Aprite pertanto il vostro editor di testo preferito e digitate il seguente codice:
Salvatelo col nome che volete (ad esempio contalettera.pl), assegnategli i permessi di esecuzione, ed eseguitelo, esattamente come avete fatto col programma precedente; notate che questa volta vi viene detto, riga per riga, quante volte avete digitato la lettera prescelta (sempre quella contenuta nella variabile $lettera all'inizio del programma). Questa volta, però, volendo fare qualche cosa di più sofisticato, non ci possiamo più affidare al comodo ma poco versatile Perl favorite default; così facciamo in modo che l'operatore diamante assegni la riga ad una variabile ($testo), alla quale togliamo il carattere di new line con chomp, poi iniziamo un ciclo while che contiene un costrutto nuovo, che fa uso dell'operatore di legame (ma possiamo chiamare anche lui operatore di ricerca) =~ (il simbolo ~ si ottiene con la combinazione di tasti opzione-5). Esso è perfettamente identico a m//, con la differenza che il pattern compreso tra le due slash non viene cercato all'interno di $_ ma all'interno della stringa specificata a sinistra di =~ (quindi in $testo nel nostro caso). Niente di nuovo, quindi: stiamo facendo esattamente quello che facevamo prima. Solo che prima avevamo un'istruzione if che discriminava tra la presenza o l'assenza del pattern specificato in $_, ora invece abbiamo un ciclo while. Perché? Perché così facendo, quando la condizione di test è vera (l'operatore =~ ha trovato il pattern specificato in $testo), il contenuto del ciclo while viene eseguito, ovvero:
la variabile $contatore viene incrementata di una unità;
la variabile $testo viene nuovamente assegnata con $', che, ricordiamolo, è la variabile che contiene ciò che è presente nel testo originale dopo il punto in cui è stato trovato il pattern scelto. Così facendo, se la variabile $testo contiene più occorrenze di quanto contenuto in $lettera, il ciclo while proseguirà trovandole tutte; quando non ne troverà più (l'operatore =~ restituisce un valore logico falso), il ciclo while termina.
Alla fine, un'istruzione print mostra quante volte, nella riga di testo che avete digitato o che avete letto da file, compare la lettera contenuta nella variabile $lettera.
Prima di congedarci da questo primo, sufficientemente morbido approccio con le regular expression vorrei aggiungere un piccolo commento: la sintassi che abbiamo usato qui per il pattern è lecita, ma non è la più usata. Tipicamente, sempre negli esempi specifici che abbiamo qui usato, avremmo scritto i due operatori di ricerca in questo modo: m/a/ e =~ /a/, ovvero scrivendo la lettera da cercare direttamente (e senza virgolette) all'interno delle due slash (o simboli equivalenti) che delimitano il pattern stesso. Se la cosa vi ricorda vagamente il modo con cui abbiamo sempre scritto, fino ad ora, il primo argomento di split è perché anche esso, come abbiamo già accennato in passato, è in effetti una regular expression. Su cui avremo ancora molto, molto da dire!.