Perl/Espressioni regolari (oltre)

Indice del libro

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

modifica

Quando 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

modifica

Oltre 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

modifica

Se 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

modifica

Per 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

modifica

Se 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

modifica

In 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

modifica

In 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

modifica

Si è 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

modifica

Quando 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)

modifica

Dato 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)

modifica

Spesso 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

modifica

Con 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)

modifica

Con 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