Mi è capitato di osservare che alcune chiavi di ricerca che hanno portato al mio sito sono tipicamente italiane e alcune di queste riguardano le espressioni regolari o regex. Sul mio sito ne parlo brevemente perché possono essere usate nella sezione di ricerca degli articoli. In particolare affronto le espressioni regolari POSIX.
Conviene ricordare che, in linea di principio, non esistono Le Espressioni Regolari, cioè non c'è un solo modo per dire a un computer, attraverso un linguaggio di programmazione, come cercare pattern in stringhe (sequenze di caratteri), o fare compiti simili.
Esistono degli standard diffusi, o delle convenzioni diffuse, e la loro diffusione è più o meno legata alla diffusione di un linguaggio o di una libreria che fornisce le funzioni necessarie per poter implementare le regex in proprie applicazioni. In inglese si trova ogni sorta di informazione voluta a riguardo.
Tanto per fare un esempio di differenze: il *.txt potrebbe
essere inteso come una espressione che cattura tutte le parole terminanti
in punto ti-ics-ti
, anche se più comunemente in tal caso
si parla di globbing; la stessa cosa nelle regex POSIX ma anche
in quelle fornite dal Perl (che sono una estensione di quelle POSIX),
e dunque in tutti i casi in cui ci si attiene a tali standard,
si direbbe .*\.txt; usando il pattern matching stile
Amiga, si direbbe #?.txt.
Possiamo dire comunque che la base più usata è quella POSIX, a cui poi possiamo trovare estensioni varie e piuttosto uniformi, come le regex usante in Perl, di stile (e sostanza) del tutto equivalente a quelle che possiamo per esempio usare in JavaScript, a partire dalla version 1.2 (e dunque diciamo dall'ECMA-262 prima edizione; ma le regex che vedremo qua sono tutte disponibili solo dall'ECMA-262 terza edizione, cioè dallo JavaScript version 1.5).
Nell'MSDN 2005 è detto che gli oggetti RegEx in JavaScript sono una estensione della Microsoft. Questo è falso inquanto il documento in mio possesso Core JavaScript Guide 1.5, conforme allo standard ECMA-262 terza edizione, risale al 2000. Dunque se avete a che fare con le regex nel mondo Windows prendete cum grano salis le affermazioni dell'MSDN e tenete presente che potete scrivere codice usabile anche da altri browser che implementano JavaScript 1.5, senza dover differenziare il codice.
In Microsoft Windows (almeno prima di Vista, che non credo abbia cambiato le regex, però forse ha eletto una nuova tecnologia di scripting) è dunque possibile usare le espressioni regolari che vedremo qua per esempio in JScript (così lo chiamano) o VBScript, linguaggi di scripting presenti in Windows.
Le espressioni regolari sono utilissime per identificare dei pattern (figurazioni?) in stringhe, dove una parte è variabili secondo uno schema noto; per validare indirizzi e-mail o URI in generale; per estrarre informazioni da stringhe.
Vediamo degli esempi in cui l'uso delle regex è fondamentale:
estensionenota, come it, oppure org e così via (sono molte).
Tanto per riscaldarci, ecco delle possibili soluzioni:
This is Windows NT, o
This Windows XP sucks, oppure
Questo programma gira su Windows 3.1, oppure
Non è Windows 98, ma Windows 2000(in questo caso, attenzione: solo uno dei due match è considerato); inoltre avremo a disposizione il testo catturato dalle parentesi, ovvero negli esempio: NT, XP, 3.1, 98.
Ora che forse abbiamo un'idea, vediamole più sistematicamente.
Se vogliamo sapere semplicemente se una stringa contiene una nostra sottostringa, l'espressione regolare sarà la sottostringa stessa, dove avremo avuto però cura di anteporre ai caratteri speciali che vedremo a breve il carattere di escape \.
Per esempio se vogliamo controllare che la stringa Non sono parolacce
non contenga la sottostringa on s
, useremo l'espressione
on s, che nel caso specifico darà vero.
Consideriamo la semplice espressione regolare abc, che dà
vero se la stringa in esame contiene la sottostringa abc
. L'espressione
regolare abc|def ritorna vero se esiste la sottostringa
abc
o (inclusivo) la sottostringa def
.
In generale | separa diverse alternative. E dunque
A|B
darà una corrispondenza se l'espressione regolare (comunque complessa) A dà una corrispondena oppure B dà una corrispondenza. Dunque, sottolineamo che A e B qui sono due espressioni regolari qualunque.
Essendo | un carattere speciale, per trovare la stringa
0x0f|val
in un sorgente C (o altro) dovremo scrivere \|,
e dunque l'espressione regolare generica sarà:
0x[A-Fa-f\d]+\s*\|\s*val.
Per indicare un carattere qualunque si usa un punto, .. Per matchare abaco, obici, abici etc. possiamo usare .b.c., questo beccherà qualunque sequenza di caratteri del tipo XbXcX, dove X è appunto una qualunque cosa, anche uno spazio, un numero, un tab etc.
Per cercare un punto dovremo usare \..
Attraverso i caratteri speciali, introdotti da \ seguito da una lettera o altro (vedremo), possiamo considerare non solo i caratteri raggiungibili attraverso la tastiera, ma anche sequenze di controllo e quanto altro.
Ecco i caratteri speciali
Le classi di caratteri permettono di specificare in una volta sola un insieme di caratteri, una sorta di famiglia.
bianco: un formfeed, uno spazio, un LF, i tab, il CR e i codici Unicode \u00A0, \u2028 e \u2029 sono considerati caratteri di spaziatura.
Esistono di alcuni le negazioni, cioè le famiglie complementari:
La sintassi più generale per specificare una famiglia o insieme di caratteri è specificarli tra parentesi quadre. Per esempio [abc] corrisponde a un carattere qualunque tra quelli nell'insieme specificato. Possiamo usare le famiglie precedenti: [\d\s] indica un carattere qualunque tra cifre e caratteri di spaziatura.
Per costruire l'insieme complementare, basta che il primo carattere dopo la parentesi quadra sia ^: [^\d] è equivalente a \D, e [^abc] è un qualunque carattere che non sia a o b o c.
Possiamo specificare anche degli intervalli, qualora abbia un senso l'ordinamento (in generale vale la pena solo per alfabetici e numerici): [A-Z] corrisponde a una lettera maiuscola qualunque; [A-Za-z0-9_] è equivalente a \w e così via.
Se vogliamo mettere nell'insieme il meno, ovviamente lo dovremo mettere in coda per evitare ambiguità di interpretazione; p. es. per prendere un carattere che sia o A, o Z o - dovremo scrivere [AZ-], o anche [-AZ].
Similmente se vogliamo nell'elenco ^, non lo metteremo in prima posizione dove il suo significato è di negazione. Possiamo dunque dire che la negazione dell'insieme è [^ e non il solo ^ che se non preceduto dalle quadre o se non è il primo carattere dell'espressione regolare (cfr. dopo), viene considerato come carattere normale.
Le classi brevi (\+carattere) su viste sono dunque delle abbreviazioni; per esempio \s è [\f\n\r\t\v\u00A0\u2028\u2029].
Lo standard POSIX (esteso?) contempla anche la sintassi, usabile nelle quadre, [:nome_classe:], tipo [:digit:]. Sono:
Al solito per non far interpretare come inizio o fine di insieme le
quadre, le possiamo far precedere da \. Se non c'è una
aperta, possiamo anche scrivere la chiusa senza il backslash (non so
se alcune implementanzioni possono lamentarsi): abaco]
cattura la sottostringa abaco
seguita da una quadra chiusa.
Attraverso i quantificatori possiamo dire quanti oggetti del tipo precedente ci aspettiamo. Per esempio per indicare che una sequenza di zero o più caratteri di qualunque tipo usiamo .*: il punto indica un carattere qualunque, mentre l'asterisco indica che ce ne possono essere 0 o più; dunque prendiamo sia la stringa vuota, sia una qualunque seguenza di caratteri. Ora dovrebbe essere chiaro che .*\.txt corrisponde a un nome di file qualunque terminante con l'estensione txt.
I quantificatori si pongono dopo il carattere o il gruppo (che vedremo) che vogliamo quantificare.
Come si comportano i quantificatori quando c'è una corrispondenza corta
e una lunga? Per esempio a.*ba in pensa alla base in batteria
c'è a alla ba
ma anche a alla base in ba
(considerando
l'ancora sempre sulla prima a). Normalmente viene presa la
corrispondenza più lunga. Per cambiare questo comportamento
aggiungiamo un ? dopo il quantificatore:
a.*?ba prende la corrispondenza più corta.
Similmente vale per gli altri quantificatori.
Normalmente la corrispondenza viene cercata nella stringa in una posizione qualunque. Ma se vogliamo ancorare la ricerca all'inizio o alla fine, o a qualche altra condizione possiamo usare altri caratteri speciali opportuni.
Questo abacoperché abaco non compare all'inizio; mentre dà una corrispondenza in
abaco sconosciuto. Se vogliamo trovare un ^ all'inizio, dovremo usare il backslask: \^2 trova qualcosa in
x^2per esempio (notazione TeX per scrivere x2)
ciaocompare alla fine della stringa.
parola; per esempio i trova le i in
Questi istrioni sono buoni(il primo match), mentre i\b trova solo le i a fine parola, oppure \bi a inizio parola.
Le parentesi tonde raggruppano espressioni regolari e nello stesso tempo memorizzano la corrispondenza. Come questa poi sia accessibile dipenderà dal linguaggio di programmazione in uso.
A parte nel linguaggio però possiamo usare quanto catturato
anche nell'espressione regolare stessa.
Per esempio Win(\d{1,2}) cattura il numero che segue
Win
, per esempio Win8, Win16, Win32, Win64... ma anche,
perché sia chiaro, cattura 12 in Win128
!
Quando usiamo le parentesi tonde (raggruppamenti con cattura),
catturiamo
la corrispondenza e possiamo successivamente usarla.
La sintassi è \N dove N è un numero maggiore di 0.
Per esempio [a-z]+([aeio])\s*?bell\1 trova corrispondenze
in sostantivi (parole) seguiti dall'aggettivo bello
che
concorda con l'ultima lettera, trova dunque corrispondenze
in casa bella
,
ramo bello
e così via, ma non in eco bella
oppure
cane bello
... né tantomeno se la prima parola non termina
in una delle vocali dell'insieme dato.
Talvolta è necessario raggruppare ma è inutile forzare il sistema a memorizzare il match del gruppo. In tal caso si usa la sintassi (?:X) dove X è l'espressione regolare in questione.
Per esempio Win\(?:\d+|XP) trova WinXP
oppure
Win
seguito da un numero di una o più cifre, senza possibilità
di usare quanto catturato, né nel programma né nell'espressione
stessa tramite i riferimenti all'indietro.
Alcune espressioni ci consentono di guardare in avanti (look ahead) e qui io le ho definite, arbitrariamente, match condizionati.
WinXPma non in WinPOP oppure in Zak32
Le espressioni regolari fin qui viste sono disponibili per esempio in JavaScript (tranne quando ho parlato di classi di caratteri con la sintassi [:NOME:], che è POSIX). Il Perl accetta lo standard POSIX. Inoltre accetta altre sintassi sue, di cui qui metto qualcosa:
standardNOME. Per esempio \N{Sigma} corrisponde a una sigma greca maiuscola.
Anche il PHP (tramite apposita libreria) permette di usare le espressioni regolari con la sintassi Perl (tra l'altro si dice che siano più efficienti)
Come vi dovrebbe essere chiaro ormai le regex sono
una sintassi
per specificare pattern anche piuttosto complessi. A livello
dell'implementazione non sono altro che un pezzo di software e dunque
basta che qualcuno fornisca le opportune funzioni, e che qualcuno le usi,
ed ecco che abbiamo su questo o quel sistema operativo, su questo o quel
linguaggio le espressioni regolari tal dei tali. La libreria PCRE
fornisce per esempio tutto quanto serve per usare le regex Perl.
Ciò detto... l'Amiga ha il supporto per le sue regex nel sistema operativo (usate specialmente per il pattern matching di nomi di file o cartelle, come filtro nei requester etc.), cioè ci sono le apposite funzioni facenti parte del sistema per usare le espressioni regolari.
Non mi risulta ci siano cose come look ahead o look behind, abbreviazioni per classi di caratteri e altre cosette (almeno non fino ad AmigaOS 3.1). Tuttavia ci sarebbe stato sempre spazio per migliorarle ed espanderle...
Qui volevo dare qualche rudimento sparso, tanto per farsene un'idea.
Facciamo qualche esempio: