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