COMANDI LINUX 5° PARTE

A che cosa servono le espressioni regolari?
Le espressioni regolari sono molto utili nell’ambito della ricerca e della sostituzione. Immaginiamo di avere un testo storico enorme e di dover convertire tutte le date in formato americano (yyyy/mm/dd) in quello classico italiano…

Sarebbe un lavoraccio dover fare tutto il lavoro a mano! E probabilmente ci sfuggirebbero degli errori!

Ecco, con le espressioni regolari è possibile farlo in pochi caratteri! Con un risparmio di tempo, di errori e di fegato ??

La sintassi: Uguale per tutti i programmi / linguaggi?
Solitamente si, in javascript e in perl la sintassi è simile, preg_replace di php utilizza la sintassi di perl, mentre ereg… beh, ereg ormai è deprecato.
Anche Ide e programmi di testo come vim, notepad++, Komodo edit, Dreamweaver, etc. supportano il search&replace con le espressioni regolari.
Diciamo che la sintassi potrebbe cambiare, ma non di molto. E comunque se imparate quella di perl potrete comunque cavarvela con qualsiasi altra variante ??

Ma passiamo alla descrizione dettagliata di ogni singolo elemento:

i quantificatori;
i quantificatori “non golosi”;
le classi di caratteri e i POSIX;
i modificatori;
le ancore;
i caratteri speciali;
gli intervalli;
i gruppi;
i metacaratteri;
le asserzioni;

I metacaratteri
Nelle espressioni regolari esistono diversi “caratteri speciali” dalle diverse funzioni

Meta Descrizione
. significa qualsiasi carattere ad eccezione di quelli che identificano una riga nuova (\n e \r per intenderci)
Vedi esempio »
1
$testo = “espressioni regolari!”;
2
preg_match_all(‘/./’, $testo, $ris);
3
// Troverà tutti i caratteri
^ identifica l’inizio di una riga; inoltre all’inizio di un gruppo nega il gruppo stesso
Vedi esempio »
1
$testo = “espressioni regolari!”;
2
preg_match_all(‘/^./’, $testo, $ris);
3
// Troverà solamente la lettera “E”
$ identifica la fine di una riga
Vedi esempio »
| è una condizione OR
Vedi esempio »
() le parentesi tonde identificano dei gruppi di caratteri
[] le parentesi quadre identificano intervalli e classi di caratteri
\ questo carattere annulla gli effetti del metacarattere successivo
Vedi esempio »

I quantificatori
I quantificatori, come dice il termine stesso, indicano quante volte ricercare una data sequenza di caratteri.

Classi Descrizione
* indica 0 o più occorrenze
Vedi esempio »
+ indica 1 o più occorrenze
Vedi esempio »
? indica 1 o 0 occorrenze
Vedi esempio »
{n} ricerca esattamente n occorrenze; da ricordare che le parentesi grafe vengono considerate caratteri normali in tutti gli altri contesti
Vedi esempio »
{n,} ricerca minimo n occorrenze; vedi sopra
Vedi esempio »
{n,m} ricerca minimo n occorrenze ma non superiori alle m; vedi sopra
Vedi esempio »

I quantificatori “non golosi” (ungreedy)

Quasi tutti inciampano prima o poi in questo problema: se utilizzo una espressione del tipo /”.*”/ troverò tutte le parole racchiuse tra doppi apici? Purtroppo no!
Questo perché i quantificatori normali sono “golosi” (in inglese greedy), cioè cercano l’occorrenza il più grande possibile.
Vediamo con un esempio:

1
$testo = ‘class=”pluto” id=”pippo”‘;
2
preg_match_all(‘/”.*”/’, $testo, $ris);
3
// Troverà un’unica occorrenza:
4
// “pluto” id=”pippo”
Come vedete non è il risultato sperato! Come fare quindi?
Basta aggiungere un punto interrogativo alla fine dei nostri quantificatori

1
$testo = ‘class=”pluto” id=”pippo”‘;
2
preg_match_all(‘/”.*?”/’, $testo, $ris);
3
// Ora troverà “pluto” e “pippo” !
Questo vale per qualsiasi quantificatore descritto in precedenza!

Le classi e gli intervalli

Le classi determinano un elenco di caratteri, di classi di caratteri o di POSIX (vedi la sezione successiva) da ricercare. Vengono racchiusi tra parentesi quadre e possono essere seguiti dai quantificatori.

1
$testo = ‘Questa è una stringa lunga lunga di esempio’;
2
preg_match_all(‘/[aiuoe]{2}/’, $testo, $ris);
3
// L’espressione ricercherà due vocali consecutive,
4
// quindi troverà “ue” e “io”
Per identificare un intervallo invece si utilizza il segno meno ( – ). Per esempio a-z identificherà tutti i caratteri minuscoli dalla a alla z, F-R i caratteri maiuscoli dalla F alla R, 0-5 i numeri da 0 a 5 e così via.

1
$testo = ‘caratteri 16sdf456 e un colore esadecimale 94fa3c ‘;
2
preg_match_all(‘/[0-9a-f]{6}/’, $testo, $ris);
3
// L’espressione ricercherà 6 caratteri che siano numeri o lettere dalla a alla f
4
// quindi troverà “94fa3c”
Il carattere ^, che se posto subito dopo la parentesi quadra aperta nega tutto l’intervallo, cioè indica di non ricercare i caratteri inclusi.

1
$testo = ‘Questa è una stringa lunga lunga di esempio’;
2
preg_match_all(‘/[^aiuoe ]{3}/’, $testo, $ris);
3
// L’espressione ricercherà 3 lettere che non siano vocali o spazi
4
// quindi troverà solamene “str”

Le classi di caratteri e i POSIX

Le classi di caratteri e i POSIX servono per specificare una serie di caratteri allo stesso tempo, senza dover scomodare i gruppi.
Da ricordarsi che i POSIX in php devono essere racchiusi da una doppia parentesi quadra.

Classi Corrisponde a Descrizione
\w
[:word:] [a-zA-Z0-9_] ricerca un carattere “parola” (w sta per word), cioè lettere, numeri e “_”
Vedi esempio »
1
$testo = “[[Le_Regex sono_belle!!!]]”;
2
preg_match_all(‘/\w+/’, $testo, $ris);
3
// Troverà “Le_Regex” e “sono_belle”
\W [a-zA-Z0-9_] ricerca un carattere che non sia \w, cioè tutto quello che non è lettere, numeri o “_”
Vedi esempio »
\d
[:digit:] [0-9] ricerca un numero (d sta per digit)
Vedi esempio »
\D [^0-9] l’opposto di \d, ricerca qualsiasi cosa che non sia un numero
Vedi esempio »
\s
[:space:] [ \t\r\n\v\f] ricerca uno spazio, comprese tabulazioni e caratteri di fine riga
Vedi esempio »
\S [^ \t\r\n\v\f] l’inverso di \s, ricerca qualsiasi cosa che non sia uno spazio, una tabulazione o dei caratteri di fine riga
Vedi esempio »
[:alnum:] [a-zA-Z0-9] Ricerca caratteri alfanumerici, senza “_”
Vedi esempio »
[:alpha:] [a-zA-Z] Ricerca caratteri alfabetici
Vedi esempio »
[:blank:] [ \t] ricerca solo spazi e tabulazioni
Vedi esempio »
[:lower:] [a-z] ricerca lettere minuscole
Vedi esempio »
[:upper:] [A-Z] ricerca lettere maiuscole
Vedi esempio »
[:graph:] [\x21-x7E] ricerca tutti i caratteri visibili a video della tabella ascii non estesa, dal numero 33 (!) al 126 (~).
Vedi esempio »
[:print:] [\x20-x7E] ricerca tutti i caratteri visibili a video della tabella ascii non estesa, dal numero 32 (spazio) al 126 (~).
Vedi esempio »
[:punct:] – ricerca tutti i caratteri di punteggiatura come -!”#$%&'()*+,.\/:;<=>[email protected][]^_`{|}~
Vedi esempio »
[:cntrl:] [\x00-x1F\x7F] ricerca solo i caratteri di controllo

I modificatori

Ogni operazione di ricerca può utilizzare vari modificatori, che, come dice il nome stesso, possono modificare i criteri di ricerca predefiniti.
Questi modificatori devono essere posizionati alla fine della stringa di ricerca, subito dopo il carattere di limitazione.
È possibile combinare più effetti accodando senza spazi i modificatori (per esempio: /imsu applicherà tutti e 4 gli effetti sotto descritti).

Mod. Descrizione
i la ricerca diventa case-insensitive, cioè maiuscole e minuscole vengono considerate uguali
Vedi esempio »
1
$testo = “Le Espressioni Regolari sono regolari?”;
2
preg_match_all(‘/regolari/i’, $testo, $ris);
3
// Troverà sia “regolari” che “Regolari”
m le ricerca verrà considerate “per riga”, cioè le ancore tipo “^” e “$” verranno applicate per ogni riga di testo
Vedi esempio »
1
$testo = ‘Espressioni Regolari
2
Espressioni in perl
3
Espressioni php’;
4
preg_match_all(‘/^Espressioni/m’, $testo, $ris);
5
// Troverà tutte e 3 le “Espressioni”
6
// e non solo la prima
s il testo viene considerato un’unica riga e “.” ora identifica anche i caratteri di fine riga, che normalmente non troverebbe
Vedi esempio »
1
$testo = ‘Espressioni Regolari
2
Espressioni in perl
3
Espressioni php’;
4
preg_match(‘/perl.Espressioni/s’, $testo);
5
// la ricerca avrà successo
u vengono abilitati i caratteri Unicode estes, come \x{10FFFFF}
Vedi esempio »
1
$testo = ‘?????????’;
2
preg_match(‘/\x{89e6}\x{624b}/u’, $testo, $ris);
3
// la ricerca avrà successo
U attiva l’opzione ungreedya tutti i quantificatori
Vedi esempio »
1
$testo = ‘class=”pluto” id=”pippo”‘;
2
preg_match_all(‘/”.*”/U’, $testo, $ris);
3
// equivale a /”.*?”/ troverà sia “pluto” che “pippo”

Le ancore

Le ancore identificano la posizione in cui ricercare il nostro testo.

Ancora Descrizione
^ identifica l’inizio della stringa; con il modificatore /m identifica l’inizio di ogni riga
Vedi esempio »
1
$testo = ‘Questo è un esempio
2
sulle Espressioni Regolari
3
nella sintassi di perl’;
4
preg_match_all(‘/^[\w]+/m’, $testo, $ris);
5
// la ricerca troverà “Questo”, “sulle” e “nella”
$ identifica la fine della stringa; con il modificatore /m identifica la fine di ogni riga
Vedi esempio »
1
$testo = ‘Questo è un esempio
2
sulle Espressioni Regolari
3
nella sintassi di perl’;
4
preg_match(‘/[\w]+$/m’, $testo, $ris);
5
// la ricerca troverà
6
// “esempio”, “Regolari”, “perl”
\A similmente a ^, identifica solo l’inizio della stringa, anche se è presente il modificatore /m
Vedi esempio »
1
$testo = ‘Questo è un esempio
2
sulle Espressioni Regolari
3
nella sintassi di perl’;
4
preg_match_all(‘/\A[\w]+/m’, $testo, $ris);
5
// la ricerca troverà “Questo”
\Z similmente a $, identifica solo la fine della stringa, anche se è presente il modificatore /m
Vedi esempio »
1
$testo = ‘Questo è un esempio
2
sulle Espressioni Regolari
3
nella sintassi di perl’;
4
preg_match_all(‘/[\w]+\Z/m’, $testo, $ris);
5
// la ricerca troverà “perl”
\b indentifica il punto tra due caratteri che siano \w a sinistra e non \w a destra
Vedi esempio »
1
$testo = ‘condor daino dingo elefante’;
2
preg_match_all(‘/\bd\w+/’, $testo, $ris);
3
// la ricerca troverà solo le parole che iniziano
4
// con la lettera d, cioè “daino” e “dingo”
\B identifica l’opposto di \b
Vedi esempio »
1
$testo = ‘condor daino dingo elefante’;
2
preg_match_all(‘/\Bd\w+/’, $testo, $ris);
3
// la ricerca troverà solo una serie di caratteri
4
// che iniziano con d la quale non sia l’inizio di
5
// una parola, in questo caso “dor”

I caratteri speciali

Chi ha un po’ di conoscenza di php o di perl avrà già avuto a che fare con quei segnaposto che identificano dei caratteri speciali, come quelli di fine riga o le tabulazioni. Tali segnaposto iniziano tutti con il carattere \
Ecco un elenco molto più che dettagliato, da sottolineare l’importanza di \Q!!!

Segnaposto Carattere corrispondente
\t tabulazione (HT, TAB)
\n fine riga (LF, NL)
\r ritorno a capo (CR)
\Q disabilita qualsiasi metacarattere presente fino a \E
molto utile per inserire delle variabili nella stringa
Vedi esempio »
1
$variabile = ‘[\w\s]+’;
2
$testo = “Questo testo [\w\s]+[\w\s]+ ha regex al suo interno!”;
3

4
$testo1 = preg_replace(‘/’.$variabile.’/’, ”, $testo);
5
// $testo1 senza sarà “[\\]+[\\]+!”
6

7
$testo2 = preg_replace(‘/\Q’.$variabile.’\E/’, ”, $testo);
8
// $testo2 invece sarà “Questo testo ha regex al suo interno!”
\E vedi sopra
\nnn carattere in forma ottale dove n è un numero da 0 a 7
\xnn carattere in forma esadecimale dove n è un numero… esadecimale
\f form feed (FF)
\a alarm/bell (BEL)
\e escape (BEL)

I gruppi

I gruppi vengono racchiusi dalle parentesi tonde e diventano essenziali nel momento della sostituzione, poiché è possibile richiamarli. Un esempio per chiarire tutto:

1
$testo = “Questo è una data formato mysql: 2010-01-28”;
2
$testo = preg_replace(‘/(\d{4})-(\d{2})-(\d{2})/’, ‘Questa è una data in formato europeo: $3/$2/$1’, $testo);
3
// ora $testo sarà “Questa è una data in formato europeo: 28/01/2010”
Come vedete l’espressione contiene tre gruppi e nella sostituzione compaiono dei dollari seguiti da un numero: questo numero rappresenta il testo trovato dal gruppo corrispondente. Così $1 identificherà il primo gruppo, $2 il secondo e così via.

Per essere fiscali è possibile utilizzare \1 e \2 invece che $1 e $2, ma ve lo sconsiglio in quanto le barre dentro una stringa devono essere precedute da un’altra barra e questo rende tutto meno leggibile

1
$testo = “Questo è una data formato mysql: 2010-01-28”;
2
$testo = preg_replace(‘/(\d{4})-(\d{2})-(\d{2})/’, ‘Questa è una data in formato europeo: \\3/\\2/\\1’, $testo);
3
// ora $testo sarà “Questa è una data in formato europeo: 28/01/2010”
Nei gruppi inoltre è possibile aggiungere un’espressione logica “OR”, cioè poter ricercare una serie di caratteri oppure un’altra

1
$testo = “Si dice ha piovuto o è piovuto in italiano?”;
2
$testo = preg_replace(‘/\s+((ha|è)\s+piovuto)\s+/’, ‘ è nevicato ‘, $testo);
3
// L’espressione cercherà “ha piovuto” oppure “è piovuto” seguiti o preceduti da spazi;
4
// $testo ora sarà “Si dice è nevicato o è nevicato in italiano?”
Questa funzione ritornerà sia “ha piovuto” che “è piovuto“. Ma fate attenzione , poiché anche in questo caso le parentesi tonde rappresentano un gruppo e quindi verrà considerato nelle variabili di sostituzione, in questo caso $1 conterrà “ha piovuto” o “è piovuto” e $2 “ha” o “è”.
Qui non sempra un problema, ma nelle espressioni più complesse ricercare cose non necessarie complica orrendamente le cose. Come fare allora? Basta utilizzare i gruppi passivi, aggiungendo un “?:” subito dopo la parentesi tonda aperta:

1
$testo = “Si dice ha piovuto o è piovuto in italiano?”;
2
$testo = preg_replace(‘/\s+((?:ha|è)\s+piovuto)\s+/’, ‘ è nevicato ‘, $testo);
3
// L’espressione cercherà “ha piovuto” oppure “è piovuto” seguiti o preceduti da spazi;
4
// $testo ora sarà “Si dice è nevicato o è nevicato in italiano?”
Se analizzate il risultato ora conterrà solamente “ha piovuto” e “è piovuto”

Le asserzioni

Ho lasciato appositamente per ultime le asserzioni, visto che la maggior parte delle persone ha molta difficoltà ad assimilarle. Però una volta imparate le userete spesso e volentieri!

Esponiamo un problema: è possibile ricercare solo quelle parole che iniziano con la lettera “c” ma che la seconda lettera non sia una vocale? Certo, basta utilizzare le asserzioni! In pratica sono degli elementi di controllo che vengono applicati alle nostre ricerche.
Inoltre tutte le asserzioni sono passive, cioè, come spiegato prima, non vengono aggiunte fra le variabili di ricerca.

È un po’ complicato da spiegare, ma vedrete che con gli esempi qui sotto tutto risulterà più chiaro!

Asserzione Descrizione
(?=pattern) asserzione lookahead positiva, cioè valida l’espressione precedente solo se la condizione patternè verificata.
Vedi esempio »
1
$testo = “cane crotalo canarino criceto cervo”;
2
preg_match_all(‘/\b(c(?=[aiuoe])\w+)/’, $testo, $ris);
3
// L’espressione cercherà le parole che iniziano con la c solo se sono seguite da una vocale,
4
// per la precisione “cane”, “canarino” e “cervo”;
(?!pattern) asserzione lookahead negativa, cioè valida l’espressione precedente solo se la condizione patternnon è verificata.
Vedi esempio »
1
$testo = “cane crotalo canarino criceto cervo”;
2
preg_match_all(‘/\b(c(?![aiuoe])\w+)/’, $testo, $ris);
3
// L’espressione cercherà le parole che iniziano con la c solo se non sono seguite da una vocale,
4
// per la precisione “crotalo” e “criceto”;
(?<=pattern) asserzione lookbehind positiva, cioè valida l’espressione successiva solo se la condizione patternè verificata.
Vedi esempio »
1
$testo = “cesto pasto fasto pesto costo”;
2
preg_match_all(‘/(\w+(?<=a)sto)/’, $testo, $ris);
3
// cercherà le parole che finiscono con “sto” la cui lettera precedente sia “a”
4
// quindi troverà “pasto” e “fasto”;
(?<!pattern) asserzione lookbehind negativa, cioè valida l’espressione successiva solo se la condizione patternnon è verificata.
Vedi esempio »
1
$testo = “cesto pasto fasto pesto costo”;
2
preg_match_all(‘/(\w+(?<!–a)sto)/’, $testo, $ris);
3
// cercherà le parole che finiscono con “sto” la cui lettera precedente non sia “a”
4
// quindi troverà “cesto”, “pesto” e “costo”;</pre–>
(?>pattern) sottoespressione indipendente, cioè l’espressione viene considerata singolarmente senza considerare altre cose.
Vedi esempio »
1
$testo = “aaaaaaaaaaaaab”;
2
preg_match(‘/(a*)ab/’, $testo, $ris);
3
// Questa ricerca avrà successo
4
preg_match(‘/(?>a*)ab/’, $testo, $ris);
5
// Questa espressione non avrà successo poichè (?>a*) verrà considerata singolarmente e raggrupperà tutte le a, quindi non potrà più trovare il successivo “ab”