| Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
|---|---|---|
| Indietro | Avanti | |
Qualsiasi linguaggio di programmazione, che a ragione possa definirsi completo, deve consentire la verifica di una condizione e quindi comportarsi in base al suo risultato. Bash possiede il comando test, vari operatori parentesi quadre, parentesi rotonde e il costrutto if/then.
Il costrutto if/then verifica se l'exit status di un elenco di comandi è 0 (perché 0 significa "successo" per convenzione UNIX) e se questo è il caso, esegue uno o più comandi.
Esiste il comando specifico [ (parentesi quadra aperta). È sinonimo di test ed è stato progettato come builtin per ragioni di efficienza. Questo comando considera i suoi argomenti come espressioni di confronto, o di verifica di file, e restituisce un exit status corrispondente al risultato del confronto (0 per vero, 1 per falso).
Con la versione 2.02, Bash ha introdotto [[ ... ]], comando di verifica estesa, che esegue confronti in un modo più familiare ai programmatori in altri linguaggi. Va notato che [[ è una parola chiave, non un comando.
Bash vede [[ $a -lt $b ]] come un unico elemento che restituisce un exit status.
Anche i costrutti (( ... )) e let ... restituiscono exit status 0 se le espressioni aritmetiche valutate sono espanse ad un valore diverso da zero. Questi costrutti di espansione aritmetica possono, quindi, essere usati per effettuare confronti aritmetici.
let "1<2" restituisce 0 (poiché "1<2" espande a "1") (( 0 && 1 )) restituisce 1 (poiché "0 && 1" espande a "0")  | 
Un costrutto if può verificare qualsiasi comando, non solamente le condizioni comprese tra le parentesi quadre.
if cmp a b &> /dev/null # Sopprime l'output. then echo "I file a e b sono identici." else echo "I file a e b sono diversi." fi # L'utilissimo costrutto "if-grep": # -------------------------------- if grep -q Bash file then echo "File contiene almeno un'occorrenza di Bash." fi parola=Linux sequenza_lettere=inu if echo "$parola" | grep -q "$sequenza_lettere" # L'opzione "-q" di grep elimina l'output. then echo "$sequenza_lettere trovata in $parola" else echo "$sequenza_lettere non trovata in $parola" fi if COMANDO_CON_EXIT_STATUS_0_SE_NON_SI_VERIFICA_UN_ERRORE then echo "Comando eseguito." else echo "Comando fallito." fi  | 
Un costrutto if/then può contenere confronti e verifiche annidate.
if echo "Il prossimo *if* è parte del costrutto del primo *if*."
  if [[ $confronto = "intero" ]]
    then (( a < b ))
  else
    [[ $a < $b ]]
  fi
then
  echo '$a è inferiore a $b'
fi | 
Dettagliata spiegazione della "condizione-if" cortesia di Stéphane Chazelas.
Esempio 7-1. Cos'è vero?
#!/bin/bash
#  Suggerimento:
#  se non siete sicuri di come certe condizioni verranno valutate,
#+ controllatele con una verifica if.
echo
echo "Verifica \"0\""
if [ 0 ]      # zero
then
  echo "0 è vero."
else
  echo "0 è falso."
fi            # 0 è vero.
echo
echo "Verifica \"1\""
if [ 1 ]      # uno
then
  echo "1 è vero."
else
  echo "1 è falso."
fi            # 1 è vero.
echo
echo "Verifica \"-1\""
if [ -1 ]     # meno uno
then
  echo "-1 è vero."
else
  echo "-1 è falso."
fi            # -1 è vero.
echo
echo "Verifica \"NULL\""
if [ ]        # NULL (condizione vuota)
then
  echo "NULL è vero."
else
  echo "NULL è falso."
fi            # NULL è falso.
echo
echo "Verifica \"xyz\""
if [ xyz ]    # stringa
then
  echo "La stringa casuale è vero."
else
  echo "La stringa casuale è falso."
fi            # La stringa casuale è vero.
echo
echo "Verifica \"\$xyz\""
if [ $xyz ]   # Verifica se $xyz è nulla, ma...
              # è solo una variabile non inizializzata.
then
  echo "La variabile non inizializzata è vero."
else
  echo "La variabile non inizializzata è falso."
fi            # La variabile non inizializzata è falso.
echo
echo "Verifica \"-n \$xyz\""
if [ -n "$xyz" ]            # Più corretto, ma pedante.
then
  echo "La variabile non inizializzata è vero."
else
  echo "La variabile non inizializzata è falso."
fi            # La variabile non inizializzata è falso.
echo
xyz=          # Inizializzata, ma impostata a valore nullo.
echo "Verifica \"-n \$xyz\""
if [ -n "$xyz" ]
then
  echo "La variabile nulla è vero."
else
  echo "La variabile nulla è falso."
fi            # La variabile nulla è falso.
echo
# Quando "falso" è vero?
echo "Verifica \"falso\""
if [ "falso" ]              #  Sembra che "falso" sia solo una stringa.
then
  echo "\"falso\" è vero."  # e verifica se è vero.
else
  echo "\"falso\" è falso."
fi            # "falso" è vero.
echo
echo "Verifica \"\$falso\""  # Ancora variabile non inizializzata.
if [ "$falso" ]
then
  echo "\"\$falso\" è vero."
else
  echo "\"\$falso\" è falso."
fi            # "$falso" è falso.
              # Ora abbiamo ottenuto il risultato atteso.
#  Cosa sarebbe accaduto se avessimo verificato 
#+ la variabile non inizializzata "$vero"?
echo
exit 0 | 
Esercizio. Si spieghi il comportamento del precedente Esempio 7-1.
if [ condizione-vera ] then comando 1 comando 2 ... else # Opzionale (può anche essere omesso). # Aggiunge un determinato blocco di codice che verrà eseguito se la #+ condizione di verifica è falsa. comando 3 comando 4 ... fi  | 
![]()  | Quando if e then sono sulla stessa riga occorre mettere un punto e virgola dopo l'enunciato if per indicarne il termine. Sia if che then sono parole chiave. Le parole chiave (o i comandi) iniziano gli enunciati e prima che un nuovo enunciato possa incominciare, sulla stessa riga, è necessario che il precedente venga terminato. 
  | 
elif è la contrazione di else if. Lo scopo è quello di annidare un costrutto if/then in un altro.
if [ condizione1 ] then comando1 comando2 comando3 elif [ condizione2 ] # Uguale a else if then comando4 comando5 else comando-predefinito fi  | 
Il costrutto if test condizione-vera è l'esatto equivalente di if [ condizione-vera ]. In quest'ultimo costrutto, la parentesi quadra sinistra , [, è un simbolo che invoca il comando test. La parentesi quadra destra di chiusura, ], non dovrebbe essere necessaria. Ciò nonostante, le più recenti versioni di Bash la richiedono.
![]()  | Il comando test è un builtin Bash che verifica i tipi di file e confronta le stringhe. Di conseguenza, in uno script Bash, test non richiama l'eseguibile esterno /usr/bin/test, che fa parte del pacchetto sh-utils. In modo analogo, [ non chiama /usr/bin/[, che è un link a /usr/bin/test. 
 
 Se, per qualche ragione, desideraste usare /usr/bin/test in uno script Bash, allora specificatene il percorso completo.  | 
Esempio 7-2. Equivalenza di test, /usr/bin/test, [ ] e /usr/bin/[
#!/bin/bash
echo
if test -z "$1"
then
  echo "Nessun argomento da riga di comando."
else
  echo "Il primo argomento da riga di comando è $1."
fi
echo
if /usr/bin/test -z "$1"      # Stesso risultato del builtin "test".
#  ^^^^^^^^^^^^^              # Specificando il percorso completo del file.
then
  echo "Nessun argomento da riga di comando."
else
  echo "Il primo argomento da riga di comando è $1."
fi
echo
if [ -z "$1" ]                #  Funzionalità identica al precedente blocco 
                              #+ di codice.
#   if [ -z "$1"                 dovrebbe funzionare, ma...
#+  Bash risponde con il messaggio d'errore di missing close-bracket.
then
  echo "Nessun argomento da riga di comando."
else
  echo "Il primo argomento da riga di comando è $1."
fi
echo
if /usr/bin/[ -z "$1" ]       # Ancora, funzionalità identica alla precedente.
# if /usr/bin/[ -z "$1"       # Funziona, ma dà un messaggio d'errore.
#                             # Nota: 
#                               Il problema è stato risolto 
#                             + nella versione Bash 3.x
then
  echo "Nessun argomento da riga di comando."
else
  echo "Il primo argomento da riga di comando è $1."
fi
echo
exit 0 | 
Il costrutto [[ ]] è la versione Bash più versatile di [ ]. È il comando di verifica esteso, adottato da ksh88.
![]()  | Non può aver luogo alcuna espansione di nome di file o divisione di parole tra [[ e ]], mentre sono consentite l'espansione di parametro e la sostituzione di comando.  | 
file=/etc/passwd if [[ -e $file ]] then echo "Il file password esiste." fi  | 
![]()  | L'utilizzo del costrutto di verifica [[ ... ]] al posto di [ ... ] può evitare molti errori logici negli script. Per esempio, gli operatori &&, ||, < e > funzionano correttamente in una verifica [[ ]], mentre potrebbero dare degli errori con il costrutto [ ] .  | 
![]()  | Dopo un if non sono strettamente necessari né il comando test né i costrutti parentesi quadre ( [ ] o [[ ]] ). 
 Per questo motivo, una condizione tra parentesi quadre può essere utilizzata da sola, senza if, se abbinata ad un costrutto lista. 
  | 
Il costrutto (( )) espande e valuta un'espressione aritmetica. Se il risultato della valutazione dell'espressione è zero, viene restituito come exit status 1, ovvero "falso". Una valutazione diversa da zero restituisce come exit status 0, ovvero "vero". Questo è in contrasto marcato con l'utilizzo di test e dei costrutti [ ] precedentemente discussi.
Esempio 7-3. Verifiche aritmetiche utilizzando (( ))
#!/bin/bash
# Verifiche aritmetiche.
# Il costrutto (( ... )) valuta e verifica le espressioni aritmetiche.
# Exit status opposto a quello fornito dal costrutto  [ ... ]!
(( 0 ))
echo "l'exit status di \"(( 0 ))\" è $?."     # 1
(( 1 ))
echo "L'exit status di \"(( 1 ))\" è $?."     # 0
(( 5 > 4 ))                                   # vero
echo "L'exit status di \"(( 5 > 4 ))\" è $?." # 0
(( 5 > 9 ))                                   # falso
echo "L'exit status di \"(( 5 > 9 ))\" è $?." # 1
(( 5 - 5 ))                                   # 0
echo "L'exit status di \"(( 5 - 5 ))\" è $?." # 1
(( 5 / 4 ))                                   # Divisione o.k.
echo "L'exit status di \"(( 5 / 4 ))\" è $?." # 0
(( 1 / 2 ))                                   # Risultato della divisione <1.
echo "L'exit status di \"(( 1 / 2 ))\" è $?." # Arrotondato a 0.
                                              # 1
(( 1 / 0 )) 2>/dev/null                       # Divisione per 0 non consentita.
#           ^^^^^^^^^^^
echo "L'exit status di \"(( 1 / 0 ))\" è $?." # 1
# Che funzione ha "2>/dev/null"?
# Cosa succederebbe se fosse tolto?
# Toglietelo, quindi rieseguite lo script.
exit 0 |