Questa volta non parliamo di un comando specifico. L’argomento è di informatica generale, non legato solo a Linux: le espressioni regolari. Servono nella programmazione, negli script e in tanti altri scenari, e sono un prerequisito per affrontare comandi come sed in modo avanzato. La filosofia del canale è che le conoscenze necessarie a capire un video siano già state spiegate nei video precedenti, quindi era il momento di farle.
Cosa sono
Le espressioni regolari sono un linguaggio formale per descrivere dei pattern di testo, per trovare corrispondenze (match) o per estrarre sottoparti del testo.
Per fare gli esperimenti uso regex101.com, un sito gratuito molto comodo per studiare e costruire espressioni regolari. La sua parte più utile è il box delle spiegazioni, che descrive in italiano semplice quello che l’espressione fa man mano che la scrivi. C’è anche un selettore per scegliere il dialetto di espressione regolare, utile quando devi adattarla a uno specifico linguaggio. Gli esempi qui sotto li ho provati anche con grep -oE da terminale.
Il punto
La prima espressione è il punto: significa un carattere qualsiasi. Sulla stringa Ciao mondo trova una corrispondenza per ogni singolo carattere:
.
I quantificatori
Il punto è un selettore qualitativo. Ora aggiungo uno quantitativo, l’asterisco, che significa da zero a infinite volte:
.*
Qualsiasi carattere, un numero qualsiasi di volte: corrisponde all’intera stringa Ciao mondo.
I gruppi di caratteri
Un gruppo (tra parentesi quadre) è un insieme di caratteri trattato come un unico oggetto. Se voglio solo i numeri di una frase, uso un gruppo con le cifre e un quantificatore:
[0123456789]*
Trova i numeri, ma anche le stringhe vuote, perché l’asterisco ammette zero ripetizioni. Se voglio almeno uno, uso il più (da 1 a infinite volte):
[0123456789]+
Un insieme così frequente si scrive come range:
[0-9]+
Lo stesso vale per le lettere, per esempio da a a z:
[a-z]+
Attenzione: per le espressioni regolari la a minuscola è diversa dalla A maiuscola, e una lettera accentata come la è è un carattere a parte. Per tutte le lettere si uniscono i range, e si aggiunge l’accentata se serve:
[a-zA-Z0-9]+
Così si trovano tutte le sequenze alfanumeriche, e restano esclusi spazi e virgole.
Inizio E fine della stringa
L’accento circonflesso (^) fuori da un gruppo indica l’inizio della stringa. Per catturare la prima parola:
^[a-zA-Z0-9]+
Il dollaro indica la fine della stringa. Per l’ultima parola:
[a-zA-Z0-9]+$
Usando entrambi si verifica che l’intera stringa sia fatta solo di un certo tipo di caratteri. Per esempio, solo cifre:
^[0-9]+$
Con 12345 c’è corrispondenza, con una stringa che contiene altro no.
Quantificatori espliciti E escape
Voglio verificare che una stringa sia una data nel formato gg/mm/aaaa. Parto dall’inizio, poi due cifre. Per dire esattamente due uso le graffe:
^[0-9]{2}
Poi la barra. La barra delimita l’espressione in regex101, quindi va protetta (escape) con una barra rovesciata. Poi altre due cifre, un’altra barra e quattro cifre per l’anno, e infine il dollaro per chiudere:
^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$
Una data come 12/05/2024 corrisponde. Una data con l’anno a due cifre no. Nel terminale, con grep -E, la barra non va protetta:
echo "12/05/2024" | grep -E '^[0-9]{2}/[0-9]{2}/[0-9]{4}$'
Gruppi negati
L’accento circonflesso ha un altro significato dentro un gruppo: nega. Voglio tutto ciò che precede la virgola, cioè da inizio stringa fino alla virgola. Quindi inizio stringa, poi “qualsiasi carattere che non sia virgola”, ripetuto:
^[^,]*
Gruppi di cattura
L’ultimo concetto è il gruppo di cattura, indicato dalle parentesi tonde. A seconda del programma in cui scrivi l’espressione, il contenuto del gruppo viene catturato in una variabile che puoi usare nel tuo programma Python, nello script Bash o dove vuoi.
Cattura la parola prima della virgola:
^([^,]+)
Complico: voglio catturarla solo se la stringa contiene anche la parola crudele, in qualunque posizione dopo. Uso il punto con l’asterisco per dire “qualsiasi cosa” prima e dopo:
^([^,]+).*crudele.*
La stringa intera corrisponde e il gruppo cattura Ciao. Senza la parola crudele non c’è corrispondenza. Da terminale, con sed:
echo "Ciao, crudele mondo" | \
sed -E 's/^([^,]+).*crudele.*/\1/'
L’output:
Ciao
I gruppi di cattura servono insomma a estrarre parti della stringa, anche dentro un match più generale.
Estrarre un indirizzo email
Esercizio classico: estrarre l’indirizzo email da una frase. Ho bisogno di un gruppo di cattura che contenga lettere, numeri e punto (da proteggere con la barra rovesciata, altrimenti sarebbe il punto speciale), in quantità maggiore di uno. Poi la chiocciola, poi un altro gruppo di caratteri alfanumerici e punti, in quantità maggiore di uno, per semplificare:
([a-zA-Z0-9\.]+)@([a-zA-Z0-9\.]+)
Da terminale:
echo "scrivimi a mario.rossi@example.com grazie" | \
grep -oE '[a-zA-Z0-9.]+@[a-zA-Z0-9.]+'
L’output:
mario.rossi@example.com
Dentro un gruppo tra parentesi quadre, il punto è già un punto letterale e non serve proteggerlo.
In sintesi
- . qualsiasi carattere; * zero o più; + uno o più;
- [a-z] un gruppo di caratteri, [^,] il gruppo negato;
- ^ inizio stringa, $ fine stringa;
- {n} esattamente n volte;
- (…) gruppo di cattura;
- la barra rovesciata protegge i caratteri speciali.
C’è molto altro, con scorciatoie e gruppi predefiniti, ma la difficoltà vera è partire: apri regex101 o un altro builder e comincia a giocarci.
Link e risorse
- il video su YouTube
- esempi e comandi della puntata su GitHub
- Espressione regolare — su Wikipedia
- regex101 — per provare le espressioni
- regex(7) — la man page
- Espressioni regolari in grep — manuale GNU
Sostieni il canale
PER SOSTENERE IL CANALE BASTANO UN LIKE E UN’ISCRIZIONE!
questi link sono affiliati Amazon, acquistando i libri che ti consiglio sostieni il canale!
