Perl/Espressioni regolari (oltre)
Le espressioni regolari hanno un livello di astrazione in più consentendo di concentrarsi su contenuti "generici" e non solo su quelli noti a priori.
Prima , Match , Dopo
modificaQuando chiediamo di fare un'operazione di match, il Perl mette a disposizione tre variabili che consentono di capire meglio "cosa" si è catturato: $` contiene quello che c'è prima $& contiene il match $' contiene quello che c'è dopo
riprendendo l'esempio relativo alla parola "vita" vediamo cosa succede in questo caso:
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "ma ti sembra vita??" , "non so se la mia vita mi piace molto" ) { if ( $stringa =~ m/\s+vita[\s.?!]+/ ) { print "$stringa fa match!\n"; print "prima :$`\n"; print "match :$&\n"; print "dopo :$'\n"; print "---\n"; } }
che produce:
che vita... stare in pace con il mondo fa match! prima :che: match : vita... : dopo :stare in pace con il mondo: --- sono contento della mia vita! fa match! prima :sono contento della mia : match : vita!: dopo :: --- ma ti sembra vita?? fa match! prima :ma ti sembra : match : vita??: dopo :: --- non so se la mia vita mi piace molto fa match! prima :non so se la mia: match : vita : dopo :mi piace molto: ---
Raggruppare
modificaOltre alle variabili precedenti le espressioni regolari consentono di fare dei raggruppamenti: considerare un certo "gruppo di sintassi" come un unico elemento. La sintassi per specificare questa possibilità è mettere la sintassi fra '(' e ')'. Vediamo allora come migliorare l'esempio precedente aggiungendo il caso di estrarre anche " vitavita " , " vitavita!! " e " vitavita? ". L'espressione regolare in questo caso diventa : "\s+(vita)+[\s.!?]+" ciò che si trova fra parentesi viene trattato dal moltiplicatore come se fosse un'unica entità.
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "ma ti sembra vita??" , "non so se la mia vita mi piace molto" , "non so se la mia vitavita mi piace molto" , "non so se vitavitavita sia corretta" ) { if ( $stringa =~ m/\s+(vita)+[\s.!?]+/ ) { print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce:
che vita... stare in pace con il mondo fa match! prima :che: match : vita... : dopo :stare in pace con il mondo: sono contento della mia vita! fa match! prima :sono contento della mia: match : vita!: dopo :: ma ti sembra vita?? fa match! prima :ma ti sembra: match : vita??: dopo :: non so se la mia vita mi piace molto fa match! prima :non so se la mia: match : vita : dopo :mi piace molto: non so se la mia vitavita mi piace molto fa match! prima :non so se la mia: match : vitavita : dopo :mi piace molto: non so se vitavitavita sia corretta fa match! prima :non so se: match : vitavitavita : dopo :sia corretta:
Raggruppare ed usare ciò che si è raggruppato
modificaSe non indicato esplicitamente il Perl mette da qualche parte in memoria i vari raggruppamenti. Questi possono essere usati nella stessa espressione regolare oppure "subito" dopo l'azione di match. Ogni elemento raggruppato viene numerato a partire da 1. Se si usano nell'espressione regolare occorre indicarli come \X dove X è una cifra. Se si usano fuori dall'espressione regolare allora vengono memorizzati in $X dove X è una cifra. Vediamo un esempio: voglio sapere se la parola "stringa" è presente due volte all'interno della frase. L'espressione regolare potrebbe essere la seguente: "(stringa).*\1"
$stringa = "questa è la stringa di test e rimane una stringa !";
if ( $stringa =~ /(stringa).*\1/ ) { print " mi interessa $1 \n"; }
che produce:
mi interessa stringa
Vediamo ora un caso più complesso : voglio sapere se una parola di almeno 5 caratteri è ripetuta in una frase e quale è (per ora ci si ferma alla prima che si incontra). L'espressione regolare potrebbe essere la seguente:"(\w{5,}).*\1"
# commentate una stringa e provate ! $stringa = "questa è la stringa di test e rimane una stringa !"; $stringa = "questa \350 la stringa di test e rimane una stringa questa !"; if ( $stringa =~ /(\w{5,}).*\1/ ) { print "ho trovato:$1: \n"; }
che produce:
ho trovato:questa:
Raggruppare ma NON usare ciò che si è raggruppato
modificaPer non usare il raggruppamento (libera memoria e semplifica la ricerca) è sufficiente usare la sintassi : "(?:" e ")". Cioè basta aggiungere subito dopo la parentesi "(" un "?:".
$stringa = "io contengo una parola di soli quattro caratteri";
if ( $stringa =~ m/\s(?:\w{4,4})\s/ ) { print "la stringa -$stringa- contiene una parola di 4 caratteri!\n"; print "parola è $&\n"; }
L'uso del raggruppamento senza memorizzare non modifica le altre caratteristiche del raggruppamento. Per esempio voglio parole di 4 o 8 o 12 ... etc
$stringa = "io contengo una parola di soli quattro caratteri";
if ( $stringa =~ m/\s(?:\w{4,4})+\s/ ) { print "la stringa -$stringa- contiene una parola multipla di 4 caratteri!\n"; print "parola è $&\n"; }
che produce:
la stringa -io contengo una parola di soli quattro caratteri- contiene una parola multipla di 4 caratteri! parola è contengo
Posizionamento della ricerca
modificaSe si vuole cercare qualcosa che si trova all'inizio della stringa occorre mettere come primo carattere il carattere ^. Se si vuole cercare qualcosa che si trova alla fine della stringa occorre mettere come ultimo carattere il carattere $. Se volessi cercare la parola vita all'inizio della stringa (ma non in mezzo!) l'espressione regolare potrebbe essere: "^vita" Vediamo un esempio:
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "vita : ma ti sembra vita??" , "non so se la mia vita mi piace molto" , "non so se la mia vitavita mi piace molto" , "non so se vitavitavita sia corretta" ) { if ( $stringa =~ m/^vita/ ) { print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce:
vita : ma ti sembra vita?? fa match! prima :: match :vita: dopo : : ma ti sembra vita??:
Se volessi cercare la parola vita alla fine della stringa (ma non in mezzo!) l'espressione regolare potrebbe essere : "vita[?!.]*$" NOTA:si è messa la punteggiatura finale che può non esserci, esserci , ed essere ripetuta !
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "vita : ma ti sembra vita??" , "non so se la mia vita mi piace molto" , "non so se la mia vitavita mi piace molto" , "non so se vitavitavita sia corretta" ) { if ( $stringa =~ m/vita[?!.]*$/ ) { print "---\n"; print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce:
--- sono contento della mia vita! fa match! prima :sono contento della mia : match :vita!: dopo :: --- vita : ma ti sembra vita?? fa match! prima :vita : ma ti sembra : match :vita??: dopo ::
Separatore di parola
modificaIn questa sezione si parla del separatore di parola. Con questo termine si indica il punto di inizio/fine di una parola. Negli esempi precedenti si è utilizzato lo spazio come elemento che separa una parola dalla successiva. Esiste un marcatore "più" preciso : \b indica la sequenza \W\w o \w\W e si posiziona "nel mezzo". \B è tutto quello che non è il \b
Occorre però capirne bene la sua utilità. Con il separatore la parola viene già considerata e separata ( caso di /b ) o considerata e non separata ( caso /B ) dal resto delle parole. Infatti prendendo il punto in cui si verifica \w\W o \W\w la si è già considerata come estranea ed appartenente ad una certa sequenza. Vediamo se con un esempio si chiarisce questo: applichiamolo alla ricerca della parola "vita" nell'esempio precedente:
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "ma ti sembra vita??" , "non so se la mia vita mi piace molto" , "non so se la mia vitavita mi piace molto" , "non so se vitavitavita sia corretta" ) { if ( $stringa =~ m/\b(vita)+\b/ ) { print "---\n"; print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce:
--- che vita... stare in pace con il mondo fa match! prima :che : match :vita: dopo :... stare in pace con il mondo: --- sono contento della mia vita! fa match! prima :sono contento della mia : match :vita: dopo :!: --- ma ti sembra vita?? fa match! prima :ma ti sembra : match :vita: dopo :??: --- non so se la mia vita mi piace molto fa match! prima :non so se la mia : match :vita: dopo : mi piace molto: --- non so se la mia vitavita mi piace molto fa match! prima :non so se la mia : match :vitavita: dopo : mi piace molto: --- non so se vitavitavita sia corretta fa match! prima :non so se : match :vitavitavita: dopo : sia corretta:
NOTA: Avete notato che ora "vita" viene identificata senza spazi? (confrontate l'inizio e la fine del carattere ":" in prima , match , dopo )
Guardare avanti
modificaIn inglese "lookahead" significa che durante la verifica dell'espressione regolare ne viene verificata la contemporaneità di un'altra (positivo) oppure la non contemporaneità di un'altra (negativo). La sintassi è la seguente : (?= ) per la positiva; (?! ) per la negativa. Come per (?: ) non viene memorizzata in uno spazio in memoria, ma fa parte del match. Vediamo questo esempio :
foreach $stringa ( "che vita... stare in pace con il mondo" , "mi sembra che sia vitale respirare" , "sono contento della mia vita!" , "ma ti sembra vita??" , "non so se la mia vita mi piace molto" , "non so se la mia vitavita mi piace molto" , "non so se vitavitavita sia corretta" ) { if ( $stringa =~ m/(vita)+(?=[!?.])/ ) { print "---\n"; print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce :
--- che vita... stare in pace con il mondo fa match! prima :che : match :vita: dopo :... stare in pace con il mondo: --- sono contento della mia vita! fa match! prima :sono contento della mia : match :vita: dopo :!: --- ma ti sembra vita?? fa match! prima :ma ti sembra : match :vita: dopo :??:
Cioè solo tutte quelle righe che che hanno un carattere .,!,? dopo la sequenza v-i-t-a.
Alternativa
modificaSi è visto nel caso dei caratteri che era possibile specificare dei caratteri diversi mettendoli fra []. Nel caso di espressioni complesse invece viene incontro il simbolo |. Attenzione che questo NON si riferisce al singolo carattere ma al raggruppamento nel quale è usato. Se per esempio voglio tutte le frasi con la parola vita o morte posso usare la seguente espressione regolare: "vita|morte"
foreach $stringa ( "che vita... stare in pace con il mondo" , "purché non sia un portatore di morte" , "mi sembra che sia vitale respirare" ) { if ( $stringa =~ m/\bvita\b|\bmorte\b/ ) { print "---\n"; print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce :
--- che vita... stare in pace con il mondo fa match! prima :che : match :vita: dopo :... stare in pace con il mondo: --- purché non sia un portatore di morte fa match! prima :purché non sia un portatore di : match :morte: dopo ::
vediamo un altro esempio : occorre che la parola vita sia all'inizio e la parola morte sia in fondo e viceversa
foreach $stringa ( "vita precede la morte" , "morte segue la vita" , "morte non segue ne precede morte" , "vita genera vita" ) { if ( $stringa =~ m/^vita.*morte$|^morte.*vita$/ ) { print "---\n"; print "$stringa fa match!\n"; print "prima :$`:\n"; print "match :$&:\n"; print "dopo :$':\n"; } }
che produce :
--- vita precede la morte fa match! prima :: match :vita precede la morte: dopo :: --- morte segue la vita fa match! prima :: match :morte segue la vita: dopo ::
Sii minimale
modificaQuando si usano i moltiplicatori di caratteri, il Perl cerca la stringa che maggiormente di adatta alla sequenza cercata. A volte questo comportamento non è esattamente quello desiderato. Per dare istruzioni di come cercare si usa la sintassi "?" posta dopo il ripetitore. Vediamone la sintassi: {min,max}? inizia dal numero minimo e poi incrementa fino al massimo {min,}? inizia dal numero minimo e poi incrementa {max}? prova max (non cambia!)
- ? inizia da 0 e poi incrementa
+? inizia da 1 e poi incrementa ?? inizia da 0 e poi incrementa
Vediamo un esempio : data una stringa contenente 'elemento="xxxx"', ripetuto più volte , estrarre i valori fra " e ". Di primo impulso verrebbe l'espressione regolare : "elemento=["].*["]" tuttavia dato che .* inizia dalla maggior numero e poi decrementa , mangia anche i caratteri che seguono, che diventano inutili. Allora si utilizza il modo minimale che consente di dare visibilità anche ai caratteri che seguono. Quindi si potrebbe utilizzare l'espressione regolare : "elemento=["].*?["]"
$stringa = 'elemento="questo è il primo elemento",elemento="questo è il secondo elemento"'; if ( $stringa =~ m/elemento=["].*?["]/ ) { print "---\n"; print "fa match!\n"; print ":$&:\n"; }
che produce :
--- fa match!
- elemento="questo è il primo elemento":
Al lettore che non gli è chiaro : provi a togliere il ? e a capire cosa accade !
gli scalari come espressioni regolari
modificaÈ possibile scrivere una espressione regolare in una variabile e poi sostituirla nel match oppure mettere una variabile nell'espressione regolare. Vediamo come modificare l'esempio precedente che cerca la stessa parola nella stringa:
$stringa = "questa è la stringa di test e rimane una stringa questa !"; foreach $prova ( '\w{5,}' , 'stringa' , 'questa' ) { if ( $stringa =~ /($prova).*\1/ ) { print "parola doppia: $1 \n"; } else { print "non trovata $prova ! \n"; } }
che produce :
parola doppia :questa parola doppia :stringa parola doppia :questa
se si ha l'intenzione di sostituire per intero l'espressione regolare dato che in assegnamento di variabile i caratteri speciali vengono interpretati occorre scriverli con l'apice singolo o con il "marcatore" qr che è quello dedicato.
Vediamo l'esempio precedente:
$stringa = "questa \350 la stringa di test e rimane una stringa questa !"; $p1 = "stringa"; foreach $prova ( qr(\w{5,}) , qr($p1) , qr(questa) ) { if ( $stringa =~ /($prova).*\1/ ) { print "parola doppia: $1 \n"; } else { print "non trovata $prova ! \n"; } }
commenti (in linea)
modificaDato che le espressioni regolari possono essere molto complicate, segnarsi degli appunti, per quando eventualmente dovranno essere modificate, potrebbe tornare utile. Per poter far questo basta usare la sintassi (?# ... ). Per esempio : (?# ATTENZIONE: questo è un commento per i posteri !)
Scorciatoie (lettera in fondo)
modificaSpesso complicati concetti possono esprimersi con una lettera in una posizione particolare. Vediamo aggiungendo una (o più) lettera in fondo alla sintassi /<espressione regolare>/ che possibilità offrono in più le espressioni regolari.
Sia minuscolo , sia maiuscolo
modificaCon la lettera "i" si intendono per tutta l'espressione regolare sia lettere maiuscole che minuscole. Per esempio : voglio sapere se hai digitato "si" maiuscolo o minuscolo. L'espressione regolare potrebbe essere : /si/i
foreach $s ( "Si" , "SI" , "sI" , "si" , "No" ) { if ( $s =~ m/si/i ) { print "$s fa match con /si/i\n"; } else { print "$s NON fa match con /si/i\n"; } }
che produce:
Si fa match con /si/i SI fa match con /si/i sI fa match con /si/i si fa match con /si/i No NON fa match con /si/i
Commenti (su più linee)
modificaCon la lettera "x" si possono mettere le espressioni regolari su più linee mettendo il commento "perl" in modo tradizionale con il simbolo # . Vediamo questo esempio:
foreach $s ( "Si" , "SI" , "sI" , "si" , "No" , "ni" ) { if ( $s =~ m/ si |#accetta Si ni #accetta Ni /ix ) { print "$s fa match \n"; } else { print "$s NON fa match \n"; } }
NOTA: Gli spazi sono IGNORATI pertanto occorrerà renderli esplici usando \s o [ ].
che produce :
Si fa match SI fa match sI fa match si fa match No NON fa match ni fa match