sintassi delle espressioni regolari in UNIX

Sapienza Università di Roma
Corso di Laurea in Ingegneria Informatica
Esercitazione di Fondamenti di Informatica II
A.A. 2008-2009
Espressioni regolari nella pratica
Luigi Laura
8 ottobre 2008
Fino a questo punto del corso si sono viste le espressioni regolari come formalismo per descrivere
Linguaggi di Tipo 3. In questa esercitazione vedremo:
• Cenni preliminari sulla sintassi delle espressioni regolari in UNIX.
• Come usare le espressioni regolari all’interno di editor di testo e IDE di programmazione, per
compiti di find and replace.
• Come usare le espressioni regolari all’interno di programmi Java.
1
Preliminari: sintassi delle espressioni regolari in UNIX
La sintassi delle espressioni regolari che vedremo è quella tradizionale di UNIX, che è definita obsoleta
dallo standard POSIX ma è tuttora molto usata ed è largamente diffusa. Nella Tabella 1 vediamo
uno schema riassuntivo della sintassi1 . E’ possibile usare il comando grep2 con la seguente sintassi:
grep PATTERN FILE
visualizza tutte le linee del file FILE che contengono una stringa che corrisponde al PATTERN,
specificato mediante una espressione regolare3 . Ad esempio,
grep [Jj]ava *
stampa tutte le linee dei file nella cartella corrente che contengono la parola java o la parola Java.
Alcuni esempi:
• matica trova tutte le parole che contengono la stringa matica, come matematica, informatica,
automatica e automaticamente.
• [aeiou]matica trova tutte le parole che contengono una vocale seguita dalla stringa matica,
come matematica, automatica e automaticamente (ma non informatica).
• ^I trova tutte le linee che iniziano per I (maiuscola).
1
Sono numerosissime le varianti; si consiglia, prima di utilizzare un qualsiasi strumento software che supporti le
espressioni regolari di controllarne l’effettiva sintassi nella documentazione.
2
Esiste anche il comando egrep, con una sintassi estesa di espressioni regolari.
3
A seconda dell’espressione regolare fornita come parametro a grep può essere o meno necessario circondarla con
una coppia di apici. Negli esempi successivi ci concentriamo sulle espressioni regolari e quindi non faremo uso di apici
1
.
[ ]
[^ ]
^
$
\<
\>
*
+
?
{x,y}
( )
\n
|
Per trovare singoli caratteri
Trova ogni singolo carattere.
Trova un singolo carattere contenuto nelle parentesi.
Ad esempio, [abc] trova a, b, o c. [a-z] è un intervallo e trova ogni lettera minuscola
dell’alfabeto. Possono esserci casi misti: [abcq-z] trova a, b, c e i caratteri da q a z,
esattamente come [a-cq-z]. Il carattere - è letterale solo se è primo o ultimo carattere
nelle parentesi: [abc-] o [-abc]. Per trovare un carattere [ o ], il modo più semplice
è metterli primi all’interno delle parentesi: [][ab] trova ], [, a o b.
Trova ogni singolo carattere non incluso nelle parentesi. Ad esempio, [^abc]
trova ogni carattere diverso da a, b, o c. [^a-z] trova ogni singolo carattere che
non sia una lettera minuscola.
Marcatori di posizione
Corrisponde all’inizio della linea.
Corrisponde alla fine della linea o alla posizione immediatamente
precedente un carattere di nuova linea.
Corrisponde all’inizio della parola1 .
Corrisponde alla fine della parola1 .
Marcatori di conteggio
Un’espressione seguita da * trova zero o più copie di tale espressione
Ad esempio, [xyz]* trova "", x, y, zx, zyx, e cosı̀ via.
Un’espressione seguita da + trova una o più copie di tale espressione.
Ad esempio, [xyz]+ trova x, y, zx, zyx, e cosı̀ via.
Un’espressione seguita da ? trova zero o una copia di tale espressione.
Ad esempio, [xyz]? trova "",x, y e z.
Trova l’ultimo blocco almeno x volte e non più di y volte.
Ad esempio, a{3,5} trova aaa, aaaa o aaaaa1 .
Altro
Definisce una sottoespressione marcata. Ciò che è incluso nella sottoespressione,
può essere richiamato in seguito. Vedi sotto, \n.
Dove n è una cifra da 1 a 9; trova ciò che la n-esima sottoespressione ha trovato1 .
Rappresenta l’OR: con (infor|mate)matica trovo tutte le parole
informatica e matematica.
1 non supportato da tutte le versioni di egrep
Tabella 1: Sintassi base delle espressioni regolari in UNIX
• ^[Ii] trova tutte le linee che iniziano per i, maiuscola o minuscola.
• \<[A-Ca-c] trova tutte le parole che iniziano per a,b o c, maiuscole o minuscole.
Particolarmente interessante è l’uso delle parentesi e delle backreference \n: le parentesi consentono di raggruppare un’espressione, e il backreference si riferisce a espressioni (all’interno di parentesi)
precedentemente individuate (\1 si riferisce alla prima espressione, \2 alla seconda e cosı̀ via). Ad
esempio,
([a-z]+)te\1
identifica tutte le parole in cui compare una qualche sottostringa, seguita da te, seguita dalla
sottostringa originale: ad esempio matematica (ma compare prima e dopo di te).
Esercizio 1. Scaricare il file baregrep.exe e il file divinacommedia.txt dalla cartella dell’e2
sercitazione sul sito del corso. Baregrep è un programma per Windows che offre (alcune del)le
funzionalità di grep attraverso una interfaccia grafica. Fate partire baregrep e selezionate il file
divinacommedia.txt: rispondete alle seguenti domande mediante la scrittura di opportune espressioni regolari.
• Quali versi della Divina Commedia iniziano con una lettera T e finiscono con una lettera e?
• Sono più numerosi i versi che iniziano o quelli che finiscono con la parola Amor? Attenzione: la
prima linea di ogni terzina inizia con due spazi; vi state perdendo qualche verso nel conteggio?
Esercizio 2. Provare con baregrep tutti i comandi mostrati in Tabella 1. Quali funzionano?
2
Uso all’interno di editor di testo o IDE
Ci sono diversi editor di testo che supportano funzionalità di find and replace mediante espressioni regolari. In particolare, segnaliamo (per Windows) Notepad++4 (editor di testo generico) e JCreator5
come ambiente di programmazione Java (vedi Figura 1).
Oltre a essere utilizzate per compiti di ricerca all’interno di file, come nel caso di grep, è possibile
specificare un pattern di sostituzione in maniera simile a quanto visto per le backreference: in questo
caso la sintassi è $n dove n è un intero: $1 si riferisce alla prima sottoespressione identificata, $2
alla seconda e cosı̀ via. Ad esempio, supponiamo che gli indirizzi delle home page dei docenti
del dipartimento cambino secondo la seguente regola: da www.dis.uniroma1.it/~NOMEDOCENTE a
NOMEDOCENTE.dis.uniroma1.it/. Se abbiamo un elenco di file di testo contenente gli indirizzi nel
vecchio formato da sostituire con il nuovo, è sufficiente aprire i file con JCreator e inserire le due
espressioni regolari:
(TROVA) www.dis.uniroma1.it/~([a-z]+)
(SOSTITUISCI) $1.dis.uniroma1.it/
In questo caso in $1 viene riportato quanto trovato nella parentesi ([a-z]+), ovvero il cognome del
docente. Da notare che le singole occorrenze del sito del dipartimento non vengono modificate.
Esercizio 3. Far partire JCreator. Inserire un nuovo programma Java contenente una serie di
coordinate del tipo (A,B) dove A e B sono numeri interi di diverse cifre: (12,45), (41,34), (6,789).
Scrivere opportunamente, mediante il comando find and replace, due espressioni regolari per fare in
modo che ogni occorrenza della coppia (A,B) sia sostituita da una occorrenza di (B,A).
Nota bene. Quando è necessario identificare dei caratteri che sono parte della sintessi delle espressioni regolari, come ad esempio *.()^$ (e quindi le parentesi dell’esercizio precedente) è necessario
farli precedere dal simbolo \: per esempio le parentesi si denotano con \( e \).
Esercizio 4. Supponiamo di aver scritto un metodo statico Somma(intero a,intero b), definito
nella classe Java riportata qui sotto Decidiamo di cambiare il metodo e di trasformarlo in un metodo
non statico somma(intero y) che viene invocato su un oggetto di tipo intero. Dobbiamo quindi
sostituire tutte le occorrenze del metodo Somma(x,y) in x.somma(y): scrivere le espressioni regolari
necessarie (sempre all’interno di JCreator).
public c l a s s i n t e r o {
public int v a l o r e ;
4
5
http://sourceforge.net/projects/notepad-plus
www.jcreator.com
3
Figura 1: Schermata dell’applicazione JCreator in cui è possibile vedere il pannello di find and
replace
.
public i n t e r o ( int i ) {
v a l o r e=i ;
}
public s t a t i c i n t e r o Somma( i n t e r o a , i n t e r o b ) {
return new i n t e r o ( a . v a l o r e+b . v a l o r e ) ;
}
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
i n t e r o a=new i n t e r o ( 1 ) ;
i n t e r o b=new i n t e r o ( 1 ) ;
i n t e r o c=new i n t e r o ( 1 ) ;
i n t e r o d=new i n t e r o ( 1 ) ;
i n t e r o e=new i n t e r o ( 1 ) ;
intero
intero
intero
intero
intero
intero
intero
f=Somma( a , b ) ;
g=Somma( b , c ) ;
h=Somma( e , d ) ;
i=Somma( a , e ) ;
j=Somma( i , f ) ;
k=Somma( h , g ) ;
l=Somma( a , h ) ;
4
intero
intero
intero
intero
m=Somma( b , i ) ;
n=Somma( c , j ) ;
o=Somma( d , k ) ;
p=Somma( e , l ) ;
}
}
3
Uso all’interno di programmi Java: il package java.util.regex
Java gestisce le espressioni regolari attraverso il package java.util.regex, che consiste essenzialmente delle tre classi Pattern, Matcher e PatternSyntaxException:
• Un oggetto di tipo Pattern contiene la rappresentazione di una espressione regolare. La classe
Pattern non ha costruttori pubblici. Per creare un oggetto di tipo Pattern bisogna invocare
uno dei metodi statici compile della classe, che restituiranno un oggetto di tipo Pattern.
Questi metodi prendono come primo parametro una espressione regolare.
• Un oggetto di tipo Matcher è il motore che interpreta il pattern ricevuto e lo confronta con una
stringa di input passatagli come argomento. Come per la classe Pattern, la classe Matcher non
ha costruttori pubblici. Si ottiene un oggetto di tipo Matcher invocando il metodo matcher
su un oggetto della classe pattern.
• Un oggetto di tipo PatternSyntaxException è una eccezione (unchecked6 ) che indica un errore
di sintassi nel pattern dell’espressione regolare.
Per i dettagli sull’utilizzo del package Java si rimanda alla documentazione delle relative classi
e al tutorial Sun [2]. Nell’appendice riportiamo un programma di test che illustra il funzionamento
delle due classi Pattern e Matcher, oggetto dell’esercitazione.
Esercizio 5. Scrivere e compilare il programma Java riportato in appendice. Testare le capacità della
libreria di riconoscere sottostringhe denotate da espressioni regolari. Cosa succede se l’espressione
regolare è [ab]aba e la stringa è babababababa? Quante occorrenze segnala il programma? Quante
occorrenze ci sono?
Esercizio 6. Ispirandosi al programma Java riportato in appendice, scrivere un programma che:
1. chiede all’utente una estensione di file (ad esempio txt, java, doc) e
2. stampa su schermo l’elenco dei file di quella estensione contenuti nella directory corrente.
Suggerimento. Si ricorda che le seguenti linee di codice Java
F i l e c o r r e n t e= new F i l e ( ” . ” ) ;
S t r i n g [ ] n o m i f i l e= c o r r e n t e . l i s t ( ) ;
memorizzano nell’array di stringhe nomifile i nomi di tutti i file della cartella corrente.
NOTA BENE: ovviamente si devono usare le espressioni regolari per filtrare i file!
Esercizio 7. Modificare il programma scritto nell’esercizio precedente in modo da ottenere un
programma che:
6
Si ricorda che le eccezioni unchecked sono quelle eccezioni run-time per le quali non è necessario dichiarare la
clausola throws nei metodi che potrebbero generare le eccezioni stesse.
5
1. chiede all’utente una estensione di file (ad esempio txt, java, doc) e una espressione regolare
RE
2. stampa su schermo le occorrenze dell’espressione regolare nei file di quella estensione contenuti
nella directory corrente.
Riferimenti bibliografici
[1] Jeffrey E. F. Friedl, Mastering regular expressions, 2nd edition, O’Reilly 2002.
[2] Tutorial online sul package java.util.regex, disponibile all’indirizzo http://java.sun.com/
docs/books/tutorial/essential/regex/
[3] Espressione Regolare, voce di Wikipedia.it
http://it.wikipedia.org/wiki/Espressione_regolare.
6
Appendice
Qui di seguito riportiamo il testo del programma di test delle classi pattern e matcher del package
java.util.regex. Il programma è una versione modificata di quello preso dal tutorial della Sun:
è stato eliminato l’utilizzo della classe Console in quanto funzionante solo su Java versione 6 (e al
laboratorio è installata la versione 5) e i messaggi di interazione con l’utente sono stati tradotti in
italiano.
import j a v a . i o . ∗ ;
import j a v a . u t i l . r e g e x . P a t t e r n ;
import j a v a . u t i l . r e g e x . Matcher ;
public c l a s s RegexTestHarness {
public s t a t i c void main ( S t r i n g [ ] a r g s ) throws IOException {
B u f f e r e d R e a d e r i n=new B u f f e r e d R e a d e r (new InputStreamReader ( System . i n ) ) ;
while ( true ) {
System . out . p r i n t l n ( ” I n s e r i s c i l ’ e s p r e s s i o n e r e g o l a r e : ”
);
Pattern pattern =
Pattern . compile ( in . readLine ( ) ) ;
System . out . p r i n t l n ( ” I n s e r i s c i l a s t r i n g a n e l l a q u a l e
effettuare la ricerca : ”) ;
Matcher matcher =
p a t t e r n . matcher ( i n . r e a d L i n e ( ) ) ;
boolean found = f a l s e ;
while ( matcher . f i n d ( ) ) {
System . out . p r i n t l n ( ”Ho t r o v a t o i l t e s t o ”+
matcher . group ( )+” a p a r t i r e d a l l a p o s i z i o n e
” +matcher . s t a r t ( )+
” f i n o a l l a p o s i z i o n e ”+matcher . end ( ) ) ;
found = true ;
}
i f ( ! found ) {
System . out . p r i n t l n ( ” Nessuna c o r r i s p o n d e n z a t r o v a t a . ” ) ;
}
}
}
}
7