3 581
contributi
m (→Cicli) |
(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 | 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 ( | ; <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> | ||
== | ===Condizioni complesse=== | ||
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. | |||
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> | |||
Per esempio (le stringhe quotate possono essere anche variabili): | |||
<pre> | <pre> | ||
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> | ||
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. | |||
Sintassi: <code>getopts "''stringa-opzioni''" nomevariabile</code> | |||
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> | |||
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: | |||
<pre>./script.sh -c -f archivio -- file1 file2 file3</pre> | |||
<pre>./script.sh -c -f archivio file1 file2 file3</pre> | |||
<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> | |||
[[Categoria:Bash]][[Categoria:Bash_Scripting]] | [[Categoria:Bash]][[Categoria:Bash_Scripting]] |
contributi