10.4. Verifiche ed alternative

I costrutti case e select, tecnicamente parlando, non sono cicli, dal momento che non iterano l'esecuzione di un blocco di codice. Come i cicli, tuttavia, hanno la capacità di dirigere il flusso del programma in base alle condizioni elencate dall'inizio alla fine del blocco.

Controllo del flusso del programma in un blocco di codice

case (in) / esac

Il costrutto case è l'equivalente di scripting di shell di switch del C/C++. Permette di dirigere il flusso del programma ad uno dei diversi blocchi di codice, in base alle condizioni di verifica. È una specie di scorciatoia di enunciati if/then/else multipli e uno strumento adatto per creare menu.

case "$variabile" in

 "$condizione1" )
 comando...
 ;;

 "$condizione2" )
 comando...
 ;;

esac

Nota

  • Il "quoting" delle variabili non è obbligatorio, dal momento che la suddivisione delle parole non ha luogo.

  • Ogni riga di verifica termina con una parentesi tonda chiusa ).

  • Ciascun blocco di istruzioni termina con un doppio punto e virgola ;;.

  • L'intero blocco case termina con esac (case scritto al contrario).

Esempio 10-24. Impiego di case

#!/bin/bash
# Verificare intervalli di caratteri.

echo; echo "Premi un tasto e poi invio."
read Tasto

case "$Tasto" in
  [[:lower:]]   ) echo "Lettera minuscola";;
  [[:upper:]]   ) echo "Lettera maiuscola";;
  [0-9]         ) echo "Cifra";;
  *             ) echo "Punteggiatura, spaziatura, o altro";;
esac      #  Sono permessi gli intervalli di caratteri se
          #+ compresi tra [parentesi quadre]
          #+ o nel formato POSIX tra [[doppie parentesi quadre.

#  La prima versione di quest'esempio usava, per indicare
#+ gli intervalli di caratteri minuscoli e maiuscoli, le forme
#+ [a-z] e [A-Z].
#  Questo non è più possibile nel caso di particolari impostazioni 
#+ locali e/o distribuzioni Linux.
#  POSIX consente una maggiore portabilità.
#  Grazie a Frank Wang per averlo evidenziato.

# Esercizio:
# ---------
#  Così com'è, lo script accetta la pressione di un solo tasto, quindi
#+ termina. Modificate lo script in modo che accetti un input continuo,
#+ visualizzi ogni tasto premuto e termini solo quando viene digitata una "X".
#  Suggerimento: racchiudete tutto in un ciclo "while".

exit 0

Esempio 10-25. Creare menu utilizzando case

#!/bin/bash

# Un database di indirizzi non molto elegante

clear # Pulisce lo schermo.

echo "          Elenco Contatti"
echo "          ------ --------"
echo "Scegliete una delle persone seguenti:"
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo

read persona

case "$persona" in
# Notate l'uso del "quoting" per la variabile.

  "E" | "e" )
  # Accetta sia una lettera maiuscola che minuscola.
  echo
  echo "Roland Evans"
  echo "4321 Floppy Dr."
  echo "Hardscrabble, CO 80753"
  echo "(303) 734-9874"
  echo "(303) 734-9892 fax"
  echo "revans@zzy.net"
  echo "Socio d'affari & vecchio amico"
  ;;
# Attenzione al doppio punto e virgola che termina ogni opzione.

  "J" | "j" )
  echo
  echo "Mildred Jones"
  echo "249 E. 7th St., Apt. 19"
  echo "New York, NY 10009"
  echo "(212) 533-2814"
  echo "(212) 533-9972 fax"
  echo "milliej@loisaida.com"
  echo "Ex fidanzata"
  echo "Compleanno: Feb. 11"
  ;;

# Aggiungete in seguito le informazioni per Smith & Zane.

          * )
   #  Opzione predefinita.
   #  Un input vuoto (tasto INVIO) o diverso dalle scelte
   #+ proposte, viene verificato qui.
   echo
   echo "Non ancora inserito nel database."
  ;;

esac

echo

#  Esercizio:
#  ---------
#  Modificate lo script in modo che accetti input multipli,
#+ invece di terminare dopo aver visualizzato un solo indirizzo.

exit 0

Un uso particolarmente intelligente di case è quello per verificare gli argomenti passati da riga di comando.

#!/bin/bash

case "$1" in 
"") echo "Utilizzo: ${0##*/} <nomefile>"; exit $E_ERR_PARAM;; 
                             # Nessun parametro da riga di comando,
                             # o primo parametro vuoto.
#  Notate che ${0##*/} equivale alla sostituzione di parametro
#+ ${var##modello}. Cioè $0.

-*) NOMEFILE=./$1;;   #  Se il nome del file passato come argomento
                      #+ ($1) inizia con un trattino, lo sostituisce
                      #+ con ./$1 di modo che i comandi successivi
                      #+ non lo interpretino come un'opzione.

* ) NOMEFILE=$1;;     # Altrimenti, $1.
esac

Ecco un esempio ancor più chiaro di gestione dei parametri passati da riga di comando:

#! /bin/bash


while [ $# -gt 0 ]; do    # Finché ci sono parametri . . .
  case "$1" in
    -d|--debug)
              # "-d" o "--debug" parametro?
              DEBUG=1
              ;;
    -c|--conf)
              FILECONF="$2"
              shift
              if [ ! -f $FILECONF ]; then
                echo "Errore: il file indicato non esiste!"
                exit $E_ERR_FILECONF     # Errore di file non trovato.
              fi
              ;;
  esac
  shift       # Verifica la serie successiva di parametri.
done

#  Dallo script "Log2Rot" di Stefano Falsetto,
#+ parte del suo pacchetto "rottlog".
#  Usato con il consenso dell'autore.

Esempio 10-26. Usare la sostituzione di comando per creare la variabile di case

#!/bin/bash
#  case-cmd.sh: usare la sostituzione di comando per creare la variabile 
#+ di "case".

case $( arch ) in   # "arch" restituisce l'architettura della macchina.
                    # Equivale a 'uname -m'...
i386 ) echo "Macchina con processore 80386";;
i486 ) echo "Macchina con processore 80486";;
i586 ) echo "Macchina con processore Pentium";;
i686 ) echo "Macchina con processore Pentium2+";;
*    ) echo "Altro tipo di macchina";;
esac

exit 0

Un costrutto case può filtrare le stringhe in una ricerca che fa uso del globbing.

Esempio 10-27. Una semplice ricerca di stringa

#!/bin/bash
# match-string.sh: semplice ricerca di stringa

verifica_stringa ()
{
  UGUALE=0
  NONUGUALE=90
  PARAM=2     # La funzione richiede 2 argomenti.
  ERR_PARAM=91

  [ $# -eq $PARAM ] || return $ERR_PARAM

  case "$1" in
  "$2") return $UGUALE;;
  *   ) return $NONUGUALE;;
  esac

}  


a=uno
b=due
c=tre
d=due


verifica_stringa $a     # numero di parametri errato
echo $?                 # 91

verifica_stringa $a $b  # diverse
echo $?                 # 90

verifica_stringa $b $d  # uguali
echo $?                 # 0


exit 0

Esempio 10-28. Verificare un input alfabetico

#!/bin/bash
# isalpha.sh: Utilizzare la struttura "case" per filtrare una stringa.

SUCCESSO=0
FALLIMENTO=-1

isalpha ()  #  Verifica se il *primo carattere* della stringa
            #+ di input è una lettera.
{
if [ -z "$1" ]                # Nessun argomento passato?
then
  return $FALLIMENTO
fi

case "$1" in
[a-zA-Z]*) return $SUCCESSO;;  # Inizia con una lettera?
*        ) return $FALLIMENTO;;
esac
}             # Confrontatelo con la funzione "isalpha ()" del C.


isalpha2 ()   # Verifica se l'*intera stringa* è composta da lettere.
{
  [ $# -eq 1 ] || return $FALLIMENTO

  case $1 in
  *[!a-zA-Z]*|"") return $FALLIMENTO;;
               *) return $SUCCESSO;;
  esac
}

isdigit ()    # Verifica se l'*intera stringa* è formata da cifre.
{             # In altre parole, verifica se è una variabile numerica.
  [ $# -eq 1 ] || return $FALLIMENTO

  case $1 in
  *[!0-9]*|"") return $FALLIMENTO;;
            *) return $SUCCESSO;;
  esac
}


verifica_var ()  # Front-end per isalpha ().
{
if isalpha "$@"
then
  echo "\"$*\" inizia con un carattere alfabetico."
  if isalpha2 "$@"
  then     # Non ha significato se il primo carattere non è alfabetico.
    echo "\"$*\" contiene solo lettere."
  else
    echo "\"$*\" contiene almeno un carattere non alfabetico."
  fi
else
  echo "\"$*\"  non inizia con una lettera."
                 # Stessa risposta se non viene passato alcun argomento.
fi

echo

}

verifica_cifra ()# Front-end per isdigit ().
{
if isdigit "$@"
then
  echo "\"$*\" contiene solo cifre [0 - 9]."
else
  echo "\"$*\" contiene almeno un carattere diverso da una cifra."
fi

echo

}

a=23skidoo
b=H3llo
c=-Cosa?
d=Cosa?
e=`echo $b`      # Sostituzione di comando.
f=AbcDef
g=27234
h=27a34
i=27.34

verifica_var $a
verifica_var $b
verifica_var $c
verifica_var $d
verifica_var $e
verifica_var $f
verifica_var     # Non viene passato nessun argomento, cosa succede?
#
verifica_cifra $g
verifica_cifra $h
verifica_cifra $i


exit 0           # Script perfezionato da S.C.

# Esercizio:
# ---------
#  Scrivete la funzione 'isfloat ()' che verifichi i numeri in virgola
#+ mobile. Suggerimento: la funzione è uguale a 'isdigit ()', ma con
#+ l'aggiunta della verifica del punto decimale.
select

Il costrutto select, adottato dalla Shell Korn, è anch'esso uno strumento per creare menu.

select variabile [in lista]
do
 comando...
 break
done

Viene visualizzato un prompt all'utente affinché immetta una delle scelte presenti nella variabile lista. Si noti che select usa, in modo predefinito, il prompt PS3 (#? ). Questo può essere modificato.

Esempio 10-29. Creare menu utilizzando select

#!/bin/bash

PS3='Scegli il tuo ortaggio preferito: '# Imposta la stringa del prompt.

echo

select verdura in "fagioli" "carote" "patate" "cipolle" "rape"
do
  echo
  echo "Il tuo ortaggio preferito sono i/le $verdura."
  echo "Yuck!"
  echo
  break  # Cosa succederebbe se non ci fosse il "break"?
done

exit 0

Se viene omesso in lista allora select usa l'elenco degli argomenti passati da riga di comando allo script ($@) o alla funzione in cui il costrutto select è inserito.

Lo si confronti con il comportamento del costrutto

for variabile [in lista]

con in lista omesso.

Esempio 10-30. Creare menu utilizzando select in una funzione

#!/bin/bash

PS3='Scegli il tuo ortaggio preferito: '

echo

scelta_di()
{
select verdura
# [in lista] omesso, quindi 'select' usa gli argomenti passati alla funzione.
do
  echo
  echo "Il tuo ortaggio preferito: $verdura."
  echo "Yuck!"
  echo
  break
done
}

scelta_di fagioli riso carote ravanelli pomodori spinaci
#         $1      $2   $3     $4        $5       $6
#         passati alla funzione scelta_di()

exit 0

Vedi anche Esempio 34-3.