Adesso che conosciamo diversi modi di specificare condizioni e manipolare variabili è giunto il momento di introdurre i costrutti condizionali più classici, che ci permetteranno di comporre script sempre più complessi.
Bash include il costrutto tipico di ogni linguaggio di programmazione, ovvero l'if-then-else. L'if
in Bash è un comando composto che valuta il risultato di un test o comando ($?
) e si dirama in base al suo valore, vero (0
) o falso (1
). Viene seguito da una clausola then
contenente la lista dei comandi da eseguire nel caso in cui il test (o comando) ritorni 0
. Ad essa possono seguire una o più clausole opzionali elif
, ognuna associata ad un test addizionale e seguita a sua volta da un'ulteriore then
, cui segue una lista di comandi. Infine può essere presente un'opzionale clausola else
, seguita da una lista di comandi che dovranno essere eseguiti nel caso in cui né il test originale né ciascuno di quelli utilizzati nelle clausole elif
siano veri. Il comando termina quindi con il comando fi
per segnalare la fine del costrutto.
Vediamo di chiarire come vengono specificate le condizioni e i comandi associati ai diversi risultati, con l'aiuto di alcuni esempi. Utilizzeremo la sintassi più flessibile del comando test
, ma è possibile specificare le condizioni anche utilizzando la sintassi basata sullo standard POSIX, come abbiamo discusso nella lezione precedente.
Iniziamo con un semplice test su file; lo script che segue controlla l'esistenza del file originale.txt e - nel caso di risposta affermativa - lo copia con il nome di copia.txt utilizzando il comando cp
e mostrando un messaggio di successo.
#!/bin/bash
if [[ -e originale.txt ]] ; then
cp originale.txt copia.txt
echo "Copia riuscita."
fi
L'indentazione dei due comandi all'interno dell'if
non è obbligatoria, ma agevola la leggibilità del codice, quindi è sempre consigliabile.
L'esempio specificato utilizza la condizione -e
che risulta vera se il file che la segue esiste; nel caso in cui ciò si verifichi, i due comandi che seguono la clausola then
vengono eseguiti, altrimenti il blocco if
viene ignorato. Aggiungendo la clausola else
al precedente esempio, è possibile offrire un feedback testuale anche nel caso in cui il file non esista, in questo modo:
#!/bin/bash
if [[ -e originale.txt ]] ; then
cp originale.txt copia.txt
echo "Copia riuscita."
else
echo "Il file non esiste."
fi
Poiché il costrutto if
costituisce anch'esso un comando, è possibile anche utilizzarlo all'interno di un altro if
di livello superiore; vediamo un esempio.
#!/bin/bash
if [[ -e originale.txt ]] ; then
cp originale.txt copia.txt
echo "Copia riuscita."
else
if [[ -e nuovo.txt ]] ; then
echo "originale.txt non esiste, ma nuovo.txt sì."
cp nuovo.txt copia.txt
echo "Copia riuscita."
else
echo "Non esiste né originale.txt né nuovo.txt."
fi
fi
La logica dei test dell'esempio appena descritto fa sì che, nel caso in cui il file originale.txt esista, le prime due righe vengano eseguite, altrimenti vengono eseguiti i comandi contenuti all'interno della prima clausola else
. Il comando in essa contenuto è un ulteriore if
, che controlla l'esistenza del file nuovo.txt, copiandolo in caso affermativo o stampando un messaggio altrimenti.
Per rendere il codice precedente più leggibile e strutturato, si può introdurre una clausola elif
, rendendo superflua la presenza di un doppio if
, in questo modo:
#!/bin/bash
if [[ -e originale.txt ]] ; then
cp originale.txt copia.txt
echo "Copia riuscita."
elif [[ -e nuovo.txt ]] ; then
echo "originale.txt non esiste, ma nuovo.txt sì."
cp nuovo.txt copia.txt
echo "Copia riuscita."
else
echo "Non esiste né originale.txt né nuovo.txt."
fi
Ci sono casi in cui la presenza di troppe clausule elif
limita la leggibilità del codice, ad esempio quando vogliamo leggere un input non strutturato fornito dall'utente e prendere una decisione conseguente, in genere per ogni opzione fornita si dovrà inserire una clausola elif
con una condizione del tutto identica alla precedente, cambiando solamente il valore dell'espressione da identificare. È in casi come questi che è molto utile utilizzare il costrutto case
, che permette una più agile definizione di condizioni sul valore di una variabile, come si nota dall'esempio seguente:
#!/bin/bash
echo "Inserisci un numero da 1 a 10: "
read opt
case $opt in
1)
echo "Hai inserito uno!" ;;
2)
echo "Hai inserito due!" ;;
3)
echo "Hai inserito tre!" ;;
4)
echo "Hai inserito quattro!" ;;
5)
echo "Hai inserito cinque!" ;;
6)
echo "Hai inserito sei!" ;;
7)
echo "Hai inserito sette!" ;;
8)
echo "Hai inserito otto!" ;;
9)
echo "Hai inserito nove!" ;;
10)
echo "Hai inserito dieci!" ;;
*)
echo "Non hai inserito un numero da 1 a 10..." ;;
esac
La sintassi è molto intuitiva e il risultato molto leggibile: viene letto e memorizzato l'input dell'utente nella variabile $opt
per essere poi usata come argomento del costrutto case
. Ogni opzione seguita da una parentesi chiusa )
determina il valore che deve assumere la variabile perché i comandi che la seguono - terminati da ;;
- vengano eseguiti. Nel caso in cui la variabile non contenga nessuno dei valori specificati, il blocco case
viene ignorato, e per questo è sempre utile terminarlo con il pattern *
, che determina i comandi da eseguire nel caso in cui nessuna delle precedenti opzioni sia stata identificata come contenuta nella variabile.
Per concludere, vale la pena evidenziare che le opzioni specificate in una clausola case
possono essere espresse come pattern, utilizzando la sintassi specificata da Bash. Chi volesse approfondire questo aspetto può fare riferimento alla pagina di aiuto, digitando il comando man bash
.