mercoledì 30 gennaio 2008

Controllo del numero di argomenti di uno script shell

In molti esercizi di programmazione shell (Bourne Shell) proposti nei corsi di Sistemi Operativi dell'Università degli Studi di Modena e Reggio Emilia si richiede agli studenti di testare il numero di argomenti passati da linea di comando. Il controllo, chiaramente, dovrebbe essere implementato in ogni buon programma (non solo in quelli Shell) per assicurare che lo stesso possa eseguire con le corrette informazioni. Ovviamente, questo controllo da solo non è sufficiente, poiché deve essere seguito da un controllo sul tipo (ossia la validità) degli argomenti stessi. Tuttavia in questo articolo mi concentro solo sul controllo relativamente il numero degli argomenti.

La Bourne Shell (così come la Bash) utilizzano la variabile speciale $# per memorizzare il numero di argomenti passati da riga di comando. In particolare $# memorizza il numero di argomenti realmente passati allo script, ossia il numero di argomenti posizionali che seguono il nome dello script stesso (questo si differenzia dal C, dove il primo argomento è sempre il nome del programma stesso).

Molte soluzioni proposte ai corsi di cui sopra implementano il controllo sul numero di argomenti con un costrutto case:

case $# in
0 | 1 | 2 | 3) echo "Troppi pochi argomenti"; exit 1;;
4) ;;
*) echo "Errore nel numero di argomenti"; exit 2;;
esac
Nell'esempio riportato si fa il caso di uno script che accetta esattamente 4 parametri. In questo caso occorre scrivere un ramo del case per un numero di parametri inferiori a 4 (0,1,2,3), uno per il caso corretto (un ramo che non fa nulla) e uno per gli altri casi (che per esclusione risultano essere quelli con $# maggiore di 4).

Questo tipo di soluzione è a mio avviso errata e non dovrebbe essere proposta per i seguenti motivi:
  • è una soluzione goffa: se il numero esatto di parametri fosse alto, ad esempio 10, occorrerebbe enumerare singolarmente tutti i valori inferiori. Infatti, l'utilità del '*' è usata solo per un numero di parametri troppo evelato, ma non esiste il suo duale per pochi parametri;
  • è una soluzione lunga da scrivere: prevede la scrittura di almeno tre rami del case, quando le condizioni da testare sono solo due (maggiore e minore);
  • è una soluzione incline ad errori: inserire un ramo nel case che non opera nessuna operazione è pericolosa e mina la manutenibilità dello script stesso. Operazioni vitali potrebbero erroneamente finire nel ramo vuoto, o ci si potrebbe dimenticare del ';;' che segna la fine del case.
La soluzione basata su if risulta come segue:

if test $# -le 3
then
echo "Troppi pochi argomenti"
exit 1
else
if test $# -gt 4
then
echo "Troppi argomenti"
exit 2
fi
fi

Apparentemente questa soluzione è più lunga della precedente, ma è una pura questione stilistica, visto che sarebbe possibile comprimere il codice come segue:

if test $# -le 3; then  echo "Troppi pochi argomenti";  exit 1;
else
if test $# -gt 4; then echo "Troppi argomenti"; exit 2; fi
fi

così da risparmiare una riga rispetto al case. Ad ogni modo, le cose importanti da notare nella soluzione che sfrutta l'if sono:
  • è una soluzione flessibile: è possibile preimpostare con una variabile il numero esatto di argomenti attesi e lasciare inalterato il test;
  • è una soluzione compatta;
  • modella bene il caso che si vuole testare: troppi pochi parametri (if) o troppi parametri (else);
  • non richiede nessuna enumerazione dei casi non utilizzati (0,1,2,3);
  • non presenta rami vuoti, se il test viene superato il programma prosegue normalmente.
Per rendere ancora più flessibile la soluzione, è possibile usare la forma:


ARGC=4
if test $# -lt $ARGC; then echo "Troppi pochi argomenti"; exit 1;
else
if test $# -gt $ARGC; then echo "Troppi argomenti"; exit 2; fi
fi

Così facendo, eventuali evoluzioni dello script che prevedano un diverso numero di argomenti richiederanno la sola modifica del valore in ARGC.

Da ultimo, si tenga presenta che tutte le soluzioni che prevedono un if sono da usarsi quando il numero delle casistiche da trattare non è eccessivamente elevato, mentre il case (o lo switch di Java/C) è da preferirsi quando si devono implementare comportamenti diversi in risposta a molte casistiche.

Nessun commento: