A partire dalla terza versione di Bash - rilasciata nel 2004 - è stato introdotto il supporto alle espressioni regolari. Una espressione regolare è una sorta di codice che descrive un'insieme di stringhe. La più semplice espressione regolare che possiamo definire è quella formata da un singolo carattere, che descrive proprio un insieme costituito una sola stringa - quella contente solo tale carattere. La composizione di espressioni più complesse avviene per mezzo di alcuni operatori particolari, chiamati metacaratteri: ad esempio, il metacarattere .
descrive l'insieme di qualunque carattere dell'alfabeto, mentre *
descrive l'insieme delle stringhe corrispondenti a zero o più occorrenze dell'espressione regolare che lo precede. Il metacarattere +
, invece, descrive l'insieme delle stringhe corrispondenti a una o più occorrenze dell'espressione regolare che lo precede.
Prima di vedere in dettaglio alcuni degli operatori più utili per la composizione di espressioni regolari, dobbiamo chiarirne il funzionamento: le espressioni regolari vengono generalmente utilizzate per verificare se una stringa rispetta un determinato formato. Perciò si sfruttano all'interno del comando test
mediante l'operatore =~
. Si consideri l'esempio seguente:
$ [[ 0 =~ . ]] ; echo $?
Il comando precedente risulterà sempre vero ($?
varrà sempre 0) a prescindere dal carattere che precede l'operatore =~
, visto il significato del metacarattere .
discusso in precedenza.
Vediamo adesso alcuni esempi di funzionamento degli operatori di composizione di espressioni regolari.
L'operatore (nonchè metacarattere) ^
descrive l'insieme delle stringhe che hanno come prefisso l'espressione regolare che lo segue, mentre $
descrive quello delle stringhe che hanno come suffisso l'espressione regolare che lo precede; ad esempio:
$ [[ ciao =~ ^c ]] ; echo $?
$ [[ ciao =~ ^o ]] ; echo $?
$ [[ ciao =~ c$ ]] ; echo $?
$ [[ ciao =~ o$ ]] ; echo $?
L'espressione [a-z]
descrive l'insieme dei caratteri nell'intervallo specificato (in questo caso, tutto l'alfabeto), mentre [^a-z]
descrive quello dei caratteri non inclusi nell'intervallo specificato. È anche possibile specificare l'insieme dei caratteri al quale siamo interessati, uno di seguito all'altro all'interno di []
; ad esempio:
$ [[ c =~ [a-d] ]] ; echo $?
$ [[ c =~ [^a-d] ]] ; echo $?
$ [[ c =~ [e-z] ]] ; echo $?
$ [[ c =~ [^e-z] ]] ; echo $?
$ [[ c =~ [ace] ]] ; echo $?
All'interno di parentesi quadre è possibile anche specificare alcune classi predefinite di insiemi di caratteri:
Sintassi | Descrizione |
---|---|
[[:alnum:]] |
caratteri alfanumerici |
[[:alpha:]] |
caratteri alfabetici |
[[:blank:]] |
caratteri "vuoti" (spazi e tabulazioni) |
[[:cntrl:]] |
caratteri di controllo (codici ASCII da 000 a 037 e 177) |
[[:digit:]] |
cifre da 0 a 9 (equivalente a [[0-9]] ) |
[[:graph:]] |
insieme dei caratteri alfanumerici, cui si aggiunge anche la punteggiatura |
[[:lower:]] |
caratteri alfabetici minuscoli |
[[:print:]] |
caratteri alfanumerici, punteggiatura e spazi |
[[:punct:]] |
punteggiatura |
[[:space:]] |
caratteri di spazio (tabulazioni e "return" inclusi) |
[[:upper:]] |
caratteri alfabetici maiuscoli |
[[:xdigit:]] |
cifre esadecimali |
$ [[ A =~ [[:upper:]] ]] ; echo $?
$ [[ a =~ [[:upper:]] ]] ; echo $?
$ [[ ';' =~ [[:graph:]] ]] ; echo $?
$ [[ a =~ [[:digit:]] ]] ; echo $?
La concatenazione di espressioni regolari è a sua volta un'espressione regolare: l'operatore di composizione di due espressioni regolari è |
, corrispondente all'unione degli insiemi di caratteri descritti da ciascuna delle due espressioni regolari che lo racchiudono. Ad esempio, l'espressione ^a|b
descrive le stringhe che cominciano con a
oppure contengono b
(vengono incluse anche quelle che verificano contemporaneamente le due espressioni).
Adesso che abbiamo visto i principali comandi utili per la definizione di espressioni regolari, possiamo utilizzarle per eseguire controlli più sofisticati. Un esempio molto utile (e frequente nella pratica) è quello che consiste nell'uso delle espressioni regolari per controllare se una stringa corrisponde ad un indirizzo email valido. Per farlo, possiamo usare l'espressione regolare definita nel seguente script:
#!/bin/bash
email="pippo@pluto.com"
if [[ $email =~ [[:graph:]]+@[[:alnum:]]+\.[[:alpha:]]+ ]]; then
echo "$email è un indirizzo email valido!"
else
echo "$email NON è un indirizzo email valido!"
fi
Un supporto più esteso alle espressioni regolari viene fornito da comandi esterni a Bash, come awk
, grep
e sed
, ma la sintassi utilizzata per la loro definizione spesso varia: si consiglia perciò di controllare il file di aiuto (digitando man nome_comando
) se si desidera farne uso.
Un meccanismo analogo alle espressioni regolari offerto da Bash per effettuare pattern matching su nomi di file e stringhe sono i cosiddetti globs, cioè particolari metacaratteri che effettuano il matching quando vengono concatenati a caratteri tradizionali: la loro sintassi è molto simile a quella utilizzata per le espressioni regolari, quindi è facile fare confusione. Per controllare se una stringa è nel formato definito da un glob, si usa il tradizionale operatore =
all'interno del comando test
oppure, nel caso di nomi di file, Bash effettua l'espansione automatica quando si utilizza qualunque comando, incluso ls
(che stampa a schermo la lista dei file selezionati).
I metacaratteri supportati di default da Bash sono *
, che descrive qualsiasi stringa, ?
che descrive un singolo carattere e [...]
che descrive un qualunque carattere contenuto fra le parentesi. I glob sono implicitamente ancorati da entrambi i lati di una stringa: ciò significa che un glob deve descrivere l'intera stringa e non soltanto una parte; il glob a*
non descrive la stringa ciao
, bensì ne descrive solo il suffisso ao
.
Ecco alcuni esempi di utilizzo dei glob per selezionare un insieme specifico di file:
$ ls *
a abc b c
$ ls a*
a abc
$ ls a?c
abc
$ ls a[bd]c
abc
Bash espande ciascun blob cercando nella directory corrente i file che lo rispettano, inserendoli in una lista e utilizzandola al posto del glob: il comando ls a*
perciò risulta essere sostituito da ls a abc
, che viene poi eseguito.
Come già specificato, si possono usare i glob anche per verificare che una stringa rispetti un certo formato; ad esempio:
#!/bin/bash
filename="file.jpg"
if [[ $filename = *.jpg ]]; then
echo "$filename è un file JPG!"
else
echo "$email NON è un file JPG!"
fi
Bash supporta anche glob più complessi, chiamati glob estesi, che sono tecnicamente equivalenti alle espressioni regolari ma utilizzano una sintassi diversa. Essi sono disabilitati di default, ma possono essere abilitati tramite il comando shopt -s extglob
. Una volta abilitati è possibile utilizzare operatori più complessi: ?(list)
descrive stringhe corrispondenti a 0 o 1 occorrenza dei pattern nella lista, *(list)
stringhe corrispondenti a 0 o più occorrenze, mentre +(list)
a una o più occorrenze; @(list)
descrive stringhe che corrispondono a uno dei pattern contenuti nella lista, e !(list)
descrive qualunque stringa che non corrisponde a un pattern nella lista. La lista dei pattern contenuta nelle parentesi contiene glob semplici o estesi separati dal carattere |
. Un semplice esempio che utilizza un glob esteso è riportato di seguito.
$ ls
a.txt b.jpg c.png
$ echo !(*jpg|*png)
a.txt