Bash scripting - istruzioni composte: differenze tra le versioni

rimosse le funzioni e aggiunta l'istruzione "case" ed esempi per condizioni complesse (con e senza "case")
(rimosse le funzioni e aggiunta l'istruzione "case" ed esempi per condizioni complesse (con e senza "case"))
Riga 6: Riga 6:


==Cicli==
==Cicli==
Con <code>while</code> è possibile eseguire un blocco di comandi se un comando ha successo e ripeterne l'esecuzione fintanto che il comando continua ad avere successo. Proprio come <code>if</code>, <code>while</code> è un'istruzione usata spesso in congiunzione con <code>[...]</code>, utilizzato come comando di cui valutare l'exit status.
Con <code>while</code> è possibile eseguire un blocco di comandi se un comando ha successo e ripeterne l'esecuzione fintanto che il comando continua ad avere successo. Proprio come <code>if</code>, <code>while</code> è un'istruzione accompagnata spesso da <code>[...]</code>, utilizzato come comando di cui valutare l'exit status.


Sintassi base (in congiunzione con <code>[...]</code>):
Sintassi base (in congiunzione con <code>[...]</code>):
Riga 77: Riga 77:
Una ''pipe'' collega due comandi, trattando l'output generato dal primo come input per il secondo, in modo da svolgere funzioni più complesse tramite la composizione comandi che si occupano di funzioni più limitate. Ed è una delle caratteristiche di ''Unix'' che ogni comando debba svolgere un'unica funzione nel modo migliore possibile, permettendo la comunicazione con l'utente o un altro comando attraverso un flusso di testo, considerato un'interfaccia universale.
Una ''pipe'' collega due comandi, trattando l'output generato dal primo come input per il secondo, in modo da svolgere funzioni più complesse tramite la composizione comandi che si occupano di funzioni più limitate. Ed è una delle caratteristiche di ''Unix'' che ogni comando debba svolgere un'unica funzione nel modo migliore possibile, permettendo la comunicazione con l'utente o un altro comando attraverso un flusso di testo, considerato un'interfaccia universale.


L'exit status di una serie di pipe è dato dall'ultimo comando.
L'exit status di una serie di pipe è dato dall'ultimo comando. L'exit status può essere invertito come per i comandi semplici mettendo <code>!</code> davanti al primo comando della serie.


Sintassi:
Sintassi:
Riga 173: Riga 173:


; <code>;</code> : separatore di comandi, con cui un comando verrà eseguito a prescindere dal successo del precedente (in uno script è equivalente a un "a capo" e questa è la concatenazione di default)
; <code>;</code> : separatore di comandi, con cui un comando verrà eseguito a prescindere dal successo del precedente (in uno script è equivalente a un "a capo" e questa è la concatenazione di default)
; <code>&</code> : separatore che esegue il comando precedente in background (ritorna exit status sempre positivo e non riceve input da tastiera), passando al successivo
; <code>&</code> : separatore che esegue il comando precedente in background (restituisce exit status sempre positivo e non riceve input da tastiera), passando al successivo
; <code>&&</code> : operatore logico AND, che va posizionato tra due comandi, il secondo dei quali è eseguito solo se il primo ha esito positivo
; <code>&&</code> : operatore logico AND, che va posizionato tra due comandi, il secondo dei quali è eseguito solo se il primo ha esito positivo
Esempio:
Esempio:
Riga 214: Riga 214:
</pre>
</pre>


==Funzioni==
===Condizioni complesse===
Una funzione è un raggruppamento di comandi eseguito ogni volta che il nome della funzione è utilizzato come un comando. Per convenzione le definizioni delle funzioni precedono il corpo principale dello script.
Anche se '''bash''' dispone di altri metodi per unire condizioni più semplici, è possibile utilizzare la sintassi ''POSIX'', combinando il comando condizionale <code>[...]</code>, le concatenazioni con ''AND'' e ''OR'' logici (<code>&&</code> e <code>||</code>, rispettivamente), e il raggruppamento <code>{ ... ; }</code>. Inoltre gli operatori logici sono più efficienti, in quanto verrebbero eseguiti soltanto i comandi necessari a valutare la condizione, e si possono usare per evitare errori.


All'interno del blocco di comandi (il corpo della funzione), le variabili speciali relative agli argomenti passati allo script (<code>$# $1 $2 ... $* $@</code>) fanno riferimento a quelli passati alla funzione.
Per esempio:
<pre>
# -a (AND) e -o (OR) sono estensioni non presenti in POSIX, e tutta la condizione è sempre valutata
if [ "$modulo" -ne 0 -a $(($numero % $modulo)) -eq 0 ]; then    # SBAGLIATO! (se $modulo è 0)
  ...
fi
 
# meglio, più efficiente e POSIX-compatibile
# se $modulo è 0 la seconda parte (essendo un secondo comando) è saltata
if [ "$modulo" -ne 0 ] && [ $(($numero % $modulo)) = 0 ]; then  # sempre corretto
  ...
fi
</pre>
 
Verifica che una variabile contenga un intero (maggiore o minore di zero):
<pre>
if [ "$numero" -ne 0 ] 2> /dev/null || # fallisce solo se $numero è 0 oppuer se non è intero
  [ "$numero" = 0 ]                  # copre il caso in cui $numero è 0
then
    printf %s\\n "Il numero inserito (${numero}) è intero"
else
    printf %s\\n "ERRORE: il numero inserito non (${numero}) è intero!" >&2
fi
</pre>
 
Condizione più complessa, in cui si controlla che una variabile sia vuota, oppure che contenga un file regolare esistente e con permessi di lettura (<code>[ -r "$file" ]</code>):
<pre>
if [ -z "$file" ] || { [ -f "$file" ] && [ -r "$file" ] ; }; then
    fai_qualcosa "${file:-/dev/null}" # se $file è vuota, usa /dev/null
                                      # (espansione di parametro con valore di default)
else
    printf %s\\n "File \"${file}\" non esistente o non leggibile!" >&2
    exit 1
fi
</pre>
 
Per farne la negazione, se si è interessati al solo ramo <code>else</code>, bisogna aggiungere un altro raggruppamento, visto che <code>!</code> si riferisce a un comando semplice o a una concatenazione con pipe:
<pre>
if ! { [ -z "$file" ] || { [ -f "$file" ] && [ -r "$file" ] ; } ; }; then
    printf %s\\n "File \"${file}\" non esistente o non leggibile!" >&2
    exit 1
fi
</pre>
ma è in genere preferibile ripensare la condizione:
if [ -n "$file" ] && { ! [ -f "$file" ] || ! [ -r "$file" ] ; }; then
    printf %s\\n "File \"${file}\" non esistente o non leggibile!" >&2
    exit 1
fi
 
==Condizioni avanzate con pattern matching==
Anche se si possono costruire condizioni arbitrarie sfruttando le sole istruzioni <code>[...]</code> con concatenazioni, raggruppamenti ed espansioni di parametro, tramite l'istruzione composta <code>case</code> è possibile scrivere confronti complessi molto più facilmente.
 
Viene selezioanta per i confronti una stringa, di solito una variabile quotata, e diversi pattern con la stessa sintassi ammessa per l'espansione di percorso (''globbing''), eseguendo soltanto il blocco di comandi relativo alla prima corrispondenza trovata, e ignorando gli altri.
 
La shell '''bash''' ha diverse espansioni rispetto a ''POSIX'', e anche l'uso della parola riservata <code>select</code>, ma questa sezione si limita alla sintassi ''POSIX'', che è anche più comprensibile per chi è abituato ad altri linguaggi di programmazione.
 
I pattern devono consistere di un'unica stringa, o più stringhe separate dal carattere <code>|</code>, e a eccezione dei caratteri speciali per il globbing vanno quotati per evitare espansioni accidentali o pattern non voluti.
 
Sintassi:
<pre>
case "$var" in
pattern1[|pattern2|...]  ) ...
                            ;;
patternN[|patternN+1|...] ) ...
                            ;;
...
...
esac
</pre>


Sintassi della definizione:
Per esempio (le stringhe quotate possono essere anche variabili):
<pre>
<pre>
nomefunzione () {
case "$var" in
  ...
"stringa1"|"stringa2"|"stringa3" )
}
                  printf %s\\n "La variabile contiene una delle tre stringhe"
                  ;;
"stringa"[1-3] ) # equivalente, con globbing!
                  # ma non verrà mai eseguito, perché la corrispondenza sarà
                  # sempre trovata con i pattern precedenti
                  printf %s\\n "La variabile contiene una delle tre stringhe"
                  ;;
* ) # tutti gli altri (quelli che non hanno corrispondenza con i pattern dati)
    printf %s\\n "La variabile NON contiene una delle tre stringhe"
    ;;
esac
</pre>
 
Controllare il percorso di un file, e aggiungere quello relativo se manca:
<pre>
case "$file" in
/*      ) printf %s\\n "Percorso assoluto trovato"
            ;;
./*|../* ) printf %s\\n "Percorso relativo trovato"
            ;;
*        ) printf %s\\n "Percorso implicito, sostituzione!"
            file="./${file}" # aggiunto il percorso relativo
                            # per evitare errori con possibile opzioni
            ;;
esac
</pre>
 
Se si è interessati solo all'ultimo caso e senza stampa di messaggi:
<pre>
case "$file" in
/*|./*|../* ) :  # true (non fa niente)
              ;;
*          ) file="./${file}"
              ;;
esac
</pre>
</pre>
I comandi contenuti nel corpo della funzione sono eseguiti ogni volta che nomefunzione (una singola stringa senza spazi con le stesse limitazioni dei nomi delle variabili) è scritto come comando. Si noti che <code>()</code> dopo il nome della funzione serve a identificare l'istruzione composta come una definizione di funzione, e non accetta argomenti al suo interno come altri linguaggi di programmazione, ma resta sempre <code>()</code> invariata.


In '''bash''' è possibile premettere <code>function</code> al nome della funzione, anche senza bisogno di <code>()</code>, ma questa possibilità non è prevista da ''POSIX''.
Va solo ricordato che nel pattern ci sono cinque caratteri speciali, il separatore di pattern e i quattro del ''globbing'' (<code>| * ? [ ]</code>), che come per l'espansione di percorso non possono essere quotati. È bene invece che tutto il resto sia quotato, se si usano variabili o si intende usare questi caratteri per il loro valore letterale.
 
È un'istruzione molto usata per implementare un menù e per la lettura (parsing) degli argomenti passati allo script.
 
===Parsing degli argomenti===
Si effettua con il comando '''getopts''' (da non confondersi con <code>getopt</code>, un comando esterno più avanzato non ''POSIX'' utilizzata allo stesso scopo).
 
Ogni opzione deve consistere di una sola lettera, e può ammettere un solo argomento. Supporta inoltre l'uso di <code>--</code> che permette di separare le opzioni dall'eventuale lista di altri argomenti.


Nel corpo di una funzione è possibile utilizzare il modificatore <code>local</code> prima di un'assegnazione per rendere la variabile locale, ossia non accessibile fuori dal corpo della funzione. È bene evitare le variabili globali il più possibile (se non sono costanti), ricorrendo ai passaggi di parametri, per evitare la modifica accidentale di variabili esistenti fuori dalla funzione. Questo modificatore non è previsto da ''POSIX'', dove ogni variabile è globale, ma è comunque supportato da ogni moderna shell compatibile.
Sintassi: <code>getopts "''stringa-opzioni''" nomevariabile</code>


Il valore di ritorno di una funzione è quello dell'ultimo blocco eseguito. In alternativa è possibile specificarlo all'interno dei blocchi direttamente con la parola riservata <code>return</code> seguito dall'exit status (un valore qualsiasi tra 0, l'unico per "successo", e 255), facendo terminare la funzione immediatamente.
Regole:
* nomevariabile è un nome di variabile non preceduto da '''$''' a cui sarà assegnata la prima opzione trovata oppure <code>?</code> in presenza di errori;
* l'exit status del comando è negativo solo se non ci sono altre opzioni da assegnare;
* se la stringa delle opzioni inizia con il carattere <code>:</code> non vengono stampati messaggi di errore, se un argomento non è trovato;
* la stringa delle opzioni conterrà i caratteri, tutti scritti di seguito, di ciascuna opzione possibile; ma nessuna si considera obbligatorio, se lo è bisognerà specificarlo in un altro modo;
* il carattere delle opzioni che richiede un argomento ausiliario va seguito dal carattere <code>:</code> e l'argomento sarà assegnato alla variabile <code>$OPTARG</code>, se trovato, altrimenti verrà stampato un errore (se non disabilitati) e la variabile scelta conterrà il carattere <code>?</code>;
* la variabile <code>$OPTIND</code> è aggiornata a ogni esecuzione di <code>getopts</code> per tenere traccia dell'indice dell'ultimo parametro letto partendo da 1 (per esempio $OPTIND a 5 significa che sono state lette le variabili speciali <code>$1 ... $4</code>, infatti <code>$4</code> è in quinta posizione);
* tutto ciò che non è un'opzione o un argomento di un'opzione, e tutto ciò che segue <code>--</code>, è considerato una lista di argomenti. Per accederli è necessario effettuare lo shift (con il comando <code>shift</code>) di tutte le stringhe già lette, ossia: $OPTIND - 1.
 
Per esempio:
<pre>
#! /bin/sh
 
# Argomenti:
#    $0  [ -c | -x ]  { -f archivio } [--] [file1 ...]
# Opzioni:
# -c          : comprimi (implicito)
# -x          : estrai
# -f archivio : file con un argomento (e obbligatorio)
 
azione=c      # per rendere implicita l'azione, la definisco prima
 
# Risultato delle tre opzioni: "cef:"  (per getopts)
# Per disabilitare gli errori: ":cef:" (disabilita la stampa degli errori)
 
# ripeti finché trova opzioni
while getopts ":cef:" opzione  # opzione è un nome qualunque di variabile
do
    case "$opzione" in
    "c"|"x" ) azione=$opzione  # aggiorno l'azione in base all'opzione scelta
            ;;
    "f"    ) archivio=$OPTARG  # assegno l'argomento dell'opzione a una variabile
            ;;
    # NOTA: "?" è obbligatorio quotato o dopo \ perché è un carattere speciale
    "?"  ) # gestisco l'errore
            printf %s\\n "Usage: $0 [ -c | -x ]  { -f archivio } [--] file1 ..." >&2
            exit 1
            ;;
    esac
done
# quando non trova più opzioni o incontra la stringa -- esce dal ciclo
 
# verifico l'esistenza dell'opzione, che considero obbligatoria (ma che non lo è per getopts!)
if [ -z "$archivio" ]; then
    printf %s\\n "Archivio mancante!" >&2
    exit 1
fi
 
# ricavo la lista dei file, rimuovendo le $OPTIND - 1 opzioni lette
shift $(($OPTARG - 1))
# ora "$@" contiene la lista delle stringhe non corrispondenti alle opzioni (o tutte quelle dopo --)
 
# eseguo qualcosa, per esempio banalmente tar
tar "${azione}f" "$archivio" -- "$@"
</pre>


Se la funzione deve restituire un valore diverso da successo/insuccesso (o comunque per cui basterebbe l'exit status), è possibile:
Lo script supporta per esempio tutte queste chiamate (e anche altre combinazioni), che hanno lo stesso significato, perché <code>getopts</code> se ne occupa automaticamente:
* stamparlo come output, così che sia assegnabile a una variabile con l'espansione di comando, ma soltanto se non contiene caratteri non ammissibili (il carattere ASCII n. 0 e possibili "a capo" finali, che verrebbero rimossi). Si noti che questo metodo '''non''' è possibile in generale per i nomi di file, a meno che non seguano convenzioni note a priori;
<pre>./script.sh -c -f archivio -- file1 file2 file3</pre>
* stamparlo come output seguito da un carattere qualsiasi (per esempio X), così che sia assegnabile a una variabile con l'espansione di comando, rimuovendo in seguito il carattere finale in più. Con questo metodo è possibile passare nomi di file qualsiasi, ma al solito non il carattere ASCII n. 0;
<pre>./script.sh -c -f archivio file1 file2 file3</pre>
* utilizzare una variabile globale, eventualmente con il nome della funzione per evitare doppioni (per esempio: return_nomefunzione), contenente il valore da restituire nella funzione. Si noti che anche in questo caso il carattere ASCII n. 0 non può essere assegnato.
<pre>./script.sh -cf archivio file1 file2 file3</pre>
<pre>./script.sh -cfarchivio file1 file2 file3</pre>
<pre>./script.sh -f archivio -c file1 file2 file3</pre>
<pre>./script.sh -farchivio -c file1 file2 file3</pre>


{{Warningbox | Esiste anche la possibilità di ricorrere a <code>eval</code>, ma il suo uso non è trattato in questa guida per la sua potenziale '''pericolosità''': possibile <code>code injection</code> e <code>privilege escalation</code>, se l'input non è controllato (''sanitized''). Su '''bash''' è molto meglio ricorrere all'espansione di parametro per l'accesso indiretto, per accedere a una variabile il cui nome è contenuto in un'altra; e ad array associativi per gli altri casi.}}


[[Categoria:Bash]][[Categoria:Bash_Scripting]]
[[Categoria:Bash]][[Categoria:Bash_Scripting]]
3 581

contributi