Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Espressioni regolari con Ruby e Oniguruma

Creare pattern di ricerca sfruttando la potenza della libreria Oniguruma
Creare pattern di ricerca sfruttando la potenza della libreria Oniguruma
Link copiato negli appunti

Il potere intrinseco delle espressioni regolari come strumento per aumentare la produttività e facilitare la stesura del codice è stato spesso sottovalutato. Le espressioni regolari vengono teorizzate nel 1940 e dal 1960 fanno il loro ingresso nel mondo dei computer. La popolarità del Perl (1990) trasforma questo tool da dominio esoterico di pochi guru a strumento essenziale per ogni sviluppatore.

In questo articolo ripercorreremo le caratteristiche e la sintassi delle espressioni regolari in chiave Ruby.

Oniguruma

Oniguruma è il nome del nuovo engine per le espressioni regolari di Ruby (standard dalla versione 1.9 ma già presente in alcune release successive alla 1.8.1). Questo engine offre alcuni miglioramenti rispetto al precedente come un miglior supporto per la gestione dei charset ed alcune funzionalità avanzate. Per verificare la presenza di Oniguruma è sufficiente eseguire il seguente frammento di codice:

def oniguruma?
  eval("/(?

Nel caso la vostra installazione corrente non supporti Oniguruma non preoccupatevi, in questo articolo specificherò espressamente quali esempi funzionano solamente con il nuovo engine.

Sintassi

In Ruby un espressione regolare viene delimitata dal carattere slash '/', ma può essere utilizzato anche il marcatore %r, ecco alcuni esempi:

Esempio Risultato
/Ruby/ Ricerca la parola 'Ruby'
/[R|r]uby/ Ricerca le parole 'Ruby' o 'ruby'
/^abc/ Ricerca una stringa 'abc' ad inizio linea
%r(xyz$) Ricerca una stringa 'xyz' a fine linea
%r|[0-9]*| Ricerca una sequanza di zero o più cifre

È inoltre possibile specificare un modificatore (che consiste in una singola lettera) immediatamente dopo un espressione regolare, ecco i modificatori più famosi:

Modificatore Descrizione
i Ignora la differenza fra maiuscole e minuscole
o Effettua eventuali sostituzioni una sola volta
m Modalita multilinea (il . significa 'a capo')
x Modalità estesa

Ad esempio la seguente espressione regolare ricerca per le parole 'Ruby', 'ruby', 'rUby', 'RUBY', etc.

/ruby/i

La classe RegExp

Infine Ruby mette a disposizione una classe, Regexp, per salvare le proprie espressioni regolari, invocando il metodo compile (che è un alias di new) possiamo creare una variabile contenente:

  • l'espressione regolare (primo parametro)
  • il modificatore (secondo parametro)
  • e anche il charset che intendiamo usare (terzo parametro)

Ecco alcuni esempi:

pat = Regexp.compile(/Ruby/)                    # Salva /Ruby/
pat = Regexp.compile(/Ruby/,Regexp::IGNORECASE) # Salva /Ruby/i

# La prossima istruzione salva /Ruby/m con supporto UTF-8 
pat = Regexp.compile(/Ruby/,Regexp::MULTILINE,"U")

La classe Regexp ci dà anche la possibilità, tramite il metodo escape, di includere nelle nostre espressioni regolari caratteri che normalmente verrebbero interpretati (come ad esempio l'asterisco, il punto di domanda, etc.).

Le ancore

Un'ancora è un'istruzione all'interno di un'espressione regolare che serve per identifare una posizione invece che una particolare sequenza di caratteri, le ancore più famose sono il carattere cappelletto '^' e il carattere dollaro '$' che non rappresentano, come erroneamente a volte si è portati a credere, l'inzio e la fine della stringa, bensì l'inizio e la fine di una singola linea all'interno della stringa.

Per dimostrarlo possiamo mandare in esecuzione i seguenti frammenti di codice; ricordiamo che l'operatore =~ esegue l'espressione regolare sulla stringa data e ritorna la posizione del primo elemento che corrisponde alla ricerca effettuata:

stringa = "abcXdefXghi"
/def/  =~ stringa # 4
/^def/ =~ stringa # nil - la stringa non inizia con 'def'
/ghi$/ =~ stringa # 8
/^abc/ =~ stringa # 0

stringa = "abcndefnghi"
/def/  =~ stringa # 4
/^def/ =~ stringa # 4 <- '^' corrisponde ad ogni nuova riga
/def$/ =~ stringa # 4 <- '$' corrisponde ad ogni fine riga

Esistono anche altre ancore importanti:

Ancora Descrizione
A Corrisponde sempre all'inizio della stringa
Z Corrisponde sempre alla fine della stringa
b Corrisponde al confine di ogni parola
B Corrisponde ad ogni posizione che non sia il confine della parola

Cerchiamo di esemplificare le ultime due ancore con un esempio:

str = "questo è un esempio"
str.gsub(/b/,"|") # "|questo| |è| |un| |esempio|"
str.gsub(/B/,"-") # "q-u-e-s-t-o è u-n e-s-e-m-p-i-o"

Quantificatori

I quantificatori servono per identificare pattern ed elementi opzionali all'interno della stringa in esame; con un quantificatore è possibile specifiare le occorrenze che ci si aspetta utilizzando il formato esteso {x,y}, dove con x è indicato il numero minimo di occorrenze e con y il massimo.

# Ricerca per una stringa composta da: 
# da 1 a 3 '0', un '-', 3 '0', un '-', da 0 a 2 '0'
pattern = Regexp.compile(/^0{1,3}-0{3}-0{0,2}$/)
pattern =~ "0-000-"      # 0
pattern =~ "0000-000-0"  # nil 

Esistono 3 caratteri speciali che vengono usati come scorciatoie, li elenco di seguito:

Scorciatoia Significato
* sta per {0,} cioè 'zero o più' occorrenze
? sta per {0,1} cioè 'zero o una' occorrenza
+ sta per {1,} cioè 'una o più' occorrenze

Classi

Le classi, che sono specificate all'interno di parentesi quadre, contengono, come il nome stesso suggerisce, una classe di corripondenze tutte ugualmente valide ai fini dell'espressione, ad esempio:

/[aeiou]/  # Ogni singola vocale è una corrispondenza per questa espressione

Il trattino '-' viene usato all'interno di una classe per esprimere il concetto di range di valori mentre il segno cappelletto '^', usato all'interno della classe serve a negare la corrispondeza;

/[a-z]/  # Corrispondo a questa classe tutte le lettere 
         # dalla 'a' alla 'z'

/[^aeiou]/  # Corrispondono a questa classe tutti gli elementi 
            # eccetto le vocali

Esistono alcune classi predefinite che ci consentono di accorciare il tempo di stesura delle espressioni regolari, ad esempio [[:digit:]] (equivalente di [0-9]), o [[:alpha:]] (equivalente di [a-Z]).

Le parentesi tonde

Le parentesi tonde nelle espressioni regolari servono da sempre per raggruppare parte della corrispondenza in modo da potervi accedere in seguito. Ruby introduce un nuovo modo per accedere a tali frammenti utilizzando il metodo match che riassume tutte le corrispondenze in un istanza dell'oggetto MatchData.

Vediamo prima alcuni esempi di utilizzo della modalità "classica"; in questi esempi utilizzeremo i metodi sub e gsub, entrambi utilizzano le espressioni regolari per trovare frammenti di testo e sostituirli con quanto passato come secondo parametro, sub in particolare opera solamente sulla prima occorrenza trovata mentre gsub su tutte le occorrenze nel testo.

str = "a123b456c678"
str.sub(/(a[0-9]+)(b[0-9]+)(c[0-9]+)/, 'n1=1, n2=2, n3=3')
# "n1=a123, n2=b456, n3=c678"

In questo primo esempio l'espressione regolare raggruppa la stringa in tre blocchi contenenti ognuno una lettera (a,b,c) e una o piu cifre (quantificatore '+'). Nel secondo parametro del metodo sub possiamo quindi accedere ai gruppi utilizzando la sintassi k dove con k indichiamo il numero del gruppo che vogliamo richiamare.

Nota: nell'esempio abbiamo usato gli apici singoli per delimitare il testo del secondo parametro; questo perché se avessimo usato i doppi apici avremmo dovuto anteporre un'ulteriore '' al marcatore 'k' per impedire che Ruby interpretasse lo '' del marcatore vanificandone la funzionalità.

I metodi sub e gsub accettano anche un blocco come secondo parametro; in questo caso dovremo utilizzare '$k' come marcatore, ecco un esempio:

str = "mi chiamo Sandro"
str.gsub(/([[:alpha:]]+)/) { "parola:#{$1}" } 
# "parola:mi  parola:chiamo  parola:Sandro"

Come anticipato precedentemente Ruby mette a disposizione nuove tecniche per compiere queste operazioni; utilizzando il metodo match è possibile manipolare un istanza dell'oggetto MatchData ottenendo da questo i riferimenti a tutti i gruppi specificati (più altre interessanti informazioni).

str = "alpha beta gamma delta epsilon"
pat = /(b[^ ]+) (g[^ ]+) (d[^ ]+)/ 
refs = pat.match(str)
# refs.to_a contiene: ["beta gamma delta","beta","gamma","delta"]

Utilizzando la variabile refs possiamo accedere alle corrispondenze utilizzando i metodi tipici di accesso ad un array.

puts refs[1]      # beta
puts refs[2...3]  # gamma delta

Con begin, end e offset possiamo scoprire la posizione di inizio, fine (o l'intervallo) delle corrispondenze:

p1 = refs.begin(1)   # 6
p2 = refs.end(1)     # 11
p3 = refs.offset(1)  # [6,11]

Con pre_match e post_match possiamo infine scoprire i frammenti di stringa che precedono e seguono l'intera corrispondenza:

before = refs.pre_match   # "alpha "
after  = refs.post_match  # "epsilon"

Oniguruma e i Named Matches

Se siamo tra i fortunati utilizzatori di una versione di Ruby con Oniguruma, possiamo benificiare di un'ulteriore funzionalità per accedere ai gruppi che abbiamo specificato nelle nostre espressioni regolari: i named matches; eccone un esempio:

str = "Io sogno quando dormo"
pat = /Io (?<verbo1>[a-z]+) quando (?<verbo2>[a-z]+)/

Nell'espressione precedente abbiamo associato (col costrutto ?<val>) un nome ai due gruppi definiti in 'pat'. Nel prossimo esempio vediamo come recuperare tali corrispondenze utilizzando il nome associato:

refs = pat.match(str)
p1 = refs[:verbo1]    # sogno
p2 = refs[:verbo2]    # dormo
p3 = refs[1]          # sogno (gli indici sono ancora validi)

Conclusioni

In queste poche pagine, abbiamo saggiato la potenza della combinazione tra espressioni regolari e Ruby. Quanto spiegato fino a questo punto è solo una piccola parte dell'universo di possibilità offerto da questo potentissimo tool; basti pensare che è possibile creare espressioni regolari ricorsive, o con sub-espressioni al loro interno!


Ti consigliamo anche