Bash scripting - istruzioni composte: differenze tra le versioni

m
correzione
m (correzione)
 
(17 versioni intermedie di 2 utenti non mostrate)
Riga 1: Riga 1:
{{Bash scripting}}
{{Bash scripting}}
=Istruzioni composte=
__TOC__
Sono istruzioni composte tutte le istruzioni che possono contenere al loro interno altre istruzioni. Per esempio <code>if</code> e <code>for</code> sono due istruzioni composte:
Sono istruzioni composte tutte le istruzioni che possono contenere al loro interno altre istruzioni. Tipicamente sono introdotte e terminate da parole chiave (''keywords''), in quanto definiscono regole speciali al loro interno, spesso facendo uso anche di parole riservate che agiscono come istruzioni.
 
Per esempio <code>if</code> e <code>for</code> sono due istruzioni composte (e due ''keywords''):
* <code>if</code> è già stata trattata assieme all'uso di <code>[ ... ]</code> nei [[Bash scripting - comandi essenziali#Condizioni | comandi essenziali]], utile anche per le condizioni del ciclo <code>while</code>;
* <code>if</code> è già stata trattata assieme all'uso di <code>[ ... ]</code> nei [[Bash scripting - comandi essenziali#Condizioni | comandi essenziali]], utile anche per le condizioni del ciclo <code>while</code>;
* <code>for</code> è già stato trattato nell'[[Bash scripting - variabili - stringhe#Assegnazione con ciclo | assegnazione con ciclo]] nella sezione delle Variabili, con la sintassi tipica di ''for each'' di alcuni linguaggi di programmazione e l'unica prevista da ''POSIX''.
* <code>for</code> è già stato trattato nell'[[Bash scripting - variabili - stringhe#Assegnazione con ciclo | assegnazione con ciclo]], con la sintassi tipica di ''for each'' di alcuni linguaggi di programmazione e l'unica prevista da [[POSIX]].


==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 24: Riga 26:
   printf %s "Inserisci intero > 0 e premi INVIO: "
   printf %s "Inserisci intero > 0 e premi INVIO: "
   # assegna l'input dell'utente a $num
   # assegna l'input dell'utente a $num
   read num
   read -r num
done
done
printf %s\\n "Hai inserito: ${num}"
printf %s\\n "Hai inserito: ${num}"
Riga 32: Riga 34:
<pre>
<pre>
i=1
i=1
while [ $i -le $n ]
while [ "$i" -le "$n" ]
do
do
     ...
     ...
Riga 54: Riga 56:
<pre>
<pre>
i=1
i=1
while [ $i -le $n ]
while [ "$i" -le "$n" ]
do
do
     if [ espressione-booleana ]; then
     if [ espressione-booleana ]; then
Riga 74: Riga 76:
E non è una svista la mancanza di '''$''' davanti al nome delle variabili all'interno di <code>((...))</code>. Tale sintassi è supportata anche nell'espansione aritmetica intera, ma non è prevista da ''POSIX'', e quindi non è mai stata usata in questa guida.
E non è una svista la mancanza di '''$''' davanti al nome delle variabili all'interno di <code>((...))</code>. Tale sintassi è supportata anche nell'espansione aritmetica intera, ma non è prevista da ''POSIX'', e quindi non è mai stata usata in questa guida.


==Composizione di comandi con pipe==
== Composizione di comandi con pipe ==
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 di 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 84: Riga 86:


I più comuni comandi esterni impiegati con una pipe (per informazioni sulle loro opzioni si lascia alla lettura della loro pagina di manuale):
I più comuni comandi esterni impiegati con una pipe (per informazioni sulle loro opzioni si lascia alla lettura della loro pagina di manuale):
* <code>cat</code> per concatenare più file;
* <code>cat</code> e <code>tac</code> per concatenare più file, stampandoli in ordine iniziando dalla prima riga oppure in ordine inverso dall'ultima;
* <code>head</code> e <code>tail</code> per leggere la parte iniziale e finale di un file;
* <code>head</code> e <code>tail</code> per leggere la parte iniziale oppure finale di un file;
* <code>grep</code> per filtrare le righe di un file in base a un dato pattern;
* <code>grep</code> per filtrare le righe di un file in base a un dato pattern;
* <code>sed</code> per modificare le parti corrispondenti a un dato pattern con altre stringhe;
* <code>sed</code> per modificare le parti corrispondenti a un dato pattern con altre stringhe;
Riga 97: Riga 99:
<pre>
<pre>
# filtra le occorrenze di video nel ring buffer del kernel
# filtra le occorrenze di video nel ring buffer del kernel
dmesg | grep -i video   # -i per ignore-case (ignora maiuscole/minuscole)
dmesg | grep -i video   # -i per ignore-case (ignora maiuscole/minuscole)


# stampa il gateway utilizzato
# stampa il gateway utilizzato
Riga 105: Riga 107:
</pre>
</pre>


===Output dei comandi e carattere ASCII n. 0===
=== Output dei comandi e carattere ASCII n. 0 ===
Una variabile non può contenere il carattere ASCII n. 0, che è usato per indicare la fine della stringa. Non potendo gestire direttamente il carattere ASCII n. 0, questo non può essere presente nell'espansione di un comando, ma dev'essere riservato ai soli comandi mediante l'uso di una o più pipe.
Una variabile non può contenere il carattere ASCII n. 0, che è usato per indicare la fine della stringa. Non potendo gestire direttamente il carattere ASCII n. 0, questo non può essere presente nell'espansione di un comando, ma dev'essere riservato ai soli comandi mediante l'uso di una o più pipe.


Riga 116: Riga 118:
Si noti che mentre è rimosso dalle espansioni, nelle assegnazioni normali viene interpretato come carattere di fine stringa, con il risultato che anche tutto ciò che segue viene tralasciato.
Si noti che mentre è rimosso dalle espansioni, nelle assegnazioni normali viene interpretato come carattere di fine stringa, con il risultato che anche tutto ciò che segue viene tralasciato.


====Nomi di file con caratteri non comuni====
Una possibilità è memorizzare l'output in una codifica alternativa, per esempio base64, esadecimale o ottale, richiamando un opportuno comando esterno, così da utilizzare soltanto caratteri ammissibili:
<pre>
# comando con output arbitrario
output_base64=$(comando | base64) # trasformo in base64 e assegno alla variabile
# e lo posso riaccedere riusando base64
printf %s\\n "$output_base64" |  # lo invio a base64 per ritrasformarlo
    base64 -d |                  # decodifico nel formato originale
    altro_comando                # lo invio come input a un altro comando
</pre>
È ovviamente un metodo molto inefficiente, ma l'unico permesso per memorizzare in una variabile. In alcuni casi potrebbe essere preferibile una codifica esadecimale o ottale, ancora più inefficiente quanto a spazio occupato, ma più semplici da manipolare dalla shell.
 
==== Nomi di file con caratteri non comuni ====
Questo carattere è utile perché nemmeno i file possono averlo nel proprio nome, mentre invece permettono caratteri jolly (*, ?, ...), come già visto con le espansioni di percorso, e potrebbero contenere perfino il carattere "a capo".
Questo carattere è utile perché nemmeno i file possono averlo nel proprio nome, mentre invece permettono caratteri jolly (*, ?, ...), come già visto con le espansioni di percorso, e potrebbero contenere perfino il carattere "a capo".


L'espansione di percorso funziona normalmente, anche in presenza di "a capo", ma potrebbero sorgere problemi sfruttando l'espansione di comando. Per esempio con il comando esterno <code>find</code>, utilizzato per effettuare ricerche in modo ricorsivo, e che di default restituisce i file trovati stampandoli uno per riga, assumendo implicitamente che non contengano il carattere "a capo".
L'espansione di percorso funziona normalmente, anche in presenza di "a capo", ma potrebbero sorgere problemi sfruttando l'espansione di comando. Per esempio con il comando esterno <code>find</code>, utilizzato per effettuare ricerche in modo ricorsivo, che di default restituisce i file trovati stampandoli uno per riga, assumendo implicitamente che non contengano il carattere "a capo".


Uno script a titolo esemplificativo:
Uno script a titolo esemplificativo:
Riga 145: Riga 158:
</pre>
</pre>


====Comando find e carattere ASCII n. 0====
==== Comando find e carattere ASCII n. 0 ====
Un abbinamento comune al comando esterno GNU <code>find</code> (dotato dell'opzione -print0) è il comando esterno GNU <code>xargs</code> (dotato dell'opzione -0):
Un abbinamento comune al comando esterno GNU <code>find</code> (dotato dell'opzione -print0) è il comando esterno GNU <code>xargs</code> (dotato dell'opzione -0):
<pre>
<pre>
Riga 156: Riga 169:
<pre>
<pre>
find /percorso -opzione1 ... -opzioneN -print0 |
find /percorso -opzione1 ... -opzioneN -print0 |
     while read -d $'\000' file
     while read -r -d $'\000' file
     do
     do
         # posso usare "$file"
         # posso usare "$file"
Riga 169: Riga 182:
</pre>
</pre>


==Concatenazione e blocchi di comandi==
== Concatenazione e blocchi di comandi ==
Più comandi, anche contenenti redirezioni, possono essere concatenati per formarne di più complessi. L'operatore di concatenazione può essere anche l'ultimo di una riga, per facilitare la leggibilità del codice.
Più comandi, anche contenenti redirezioni, possono essere concatenati per formarne di più complessi. L'operatore di concatenazione può essere anche l'ultimo di una riga, per facilitare la leggibilità del codice.


; <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 204: Riga 217:
; <code>( ... )</code> : esegue il blocco di comandi in una subshell (le variabili vengono ripristinate al loro valore precedente, alla fine del blocco).
; <code>( ... )</code> : esegue il blocco di comandi in una subshell (le variabili vengono ripristinate al loro valore precedente, alla fine del blocco).


===Catturare l'exit status===
=== Catturare l'exit status ===
Per catturare lo stato d'uscita di un comando appena eseguito è sufficiente espandere la variabile speciale <code>$?</code>, come già visto. Tuttavia in caso di fallimento del comando, il controllo effettuato via <code>$?</code> avverrebbe soltanto '''dopo''' un blocco con errore (si veda la parte introduttiva sul debug).
Per catturare lo stato d'uscita di un comando appena eseguito è sufficiente espandere la variabile speciale <code>$?</code>, come già visto. Tuttavia in caso di fallimento del comando, il controllo effettuato via <code>$?</code> avverrebbe soltanto '''dopo''' un blocco con errore (si veda la parte introduttiva sul [[Bash scripting - introduzione#Debug integrato | debug]]).


Per evitare che un blocco abbia un exit status diverso da zero, si possono usare le concatenazioni <code>&&</code> e <code>||</code> (oppure un <code>if</code>):
Per evitare che un blocco abbia un exit status diverso da zero, si possono usare le concatenazioni <code>&&</code> e <code>||</code> (oppure un <code>if</code>):
Riga 214: Riga 227:
</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]], concatenando più comandi <code>[...]</code> tramite gli operatori logici ''AND'' e ''OR'' logici (<code>&&</code> e <code>||</code>, rispettivamente) e raggruppandoli con <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>
 
Esempi di condizioni complesse sull'esistenza dei file, in genere utilizzate dall'istruzione <code>if</code>:
<pre>
# Controlla che il file regolare esista e sia leggibile
[ -f "$var" ] && [ -r "$var" ]
 
# Controlla che il file regolare esista e sia eseguibile
# (permesso di lettura + esecuzione)
[ -f "$var" ] && [ -r "$var" ] && [ -x "$var" ]
 
# Controlla che la directory esista e sia possibile ottenere una lista del suo contenuto
# (per sapere se un file esiste nella directory)
[ -d "$var" ] && [ -r "$var" ]
 
# Controlla che la directory esista e sia possibile accedere al suo contenuto
# (leggendo o scrivendo i file, se i loro permessi lo consentono)
[ -d "$var" ] && [ -x "$var" ]
 
# Controlla che la directory esista e sia possibile ottenere una lista e accedere al suo contenuto
[ -d "$var" ] && [ -r "$var" ] && [ -x "$var" ]
 
# Controlla che la directory esista e sia possibile creare e cancellare dei file noti
# (permesso di accesso + scrittura)
[ -d "$var" ] && [ -x "$var" ] && [ -w "$var" ]
 
# Controlla che la directory esista e sia possibile cancellarne il contenuto (non noto)
# (permesso di lettura, per ottenere una lista del suo contenuto, + permessi del caso precedente)
[ -d "$var" ] && [ -r "$var" ] && [ -x "$var" ] && [ -w "$var" ]
</pre>
 
Uso di una condizione più complessa, in cui si controlla che una variabile sia vuota, oppure che contenga un file regolare esistente e con permessi di scrittura:
<pre>
if [ -z "$file" ] || { [ -f "$file" ] && [ -w "$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 modificabile!" >&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" ] && [ -w "$file" ] ; } ; }; then
    printf %s\\n "File \"${file}\" non esistente o non modificabile!" >&2
    exit 1
fi
</pre>
ma è in genere preferibile ripensare la condizione:
<pre>
if [ -n "$file" ] && { ! [ -f "$file" ] || ! [ -w "$file" ] ; }; then
    printf %s\\n "File \"${file}\" non esistente o non modificabile!" >&2
    exit 1
fi
</pre>
 
== 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 selezionata 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>
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>
 
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.
 
'''bash''' permette anche l'uso di condizioni avanzate con le parole chiave (''keywords'') <code>[[</code><code> ... </code><code>]]</code> (non ''POSIX'').<br/>
Supportano la sintassi base del comando interno <code>[ ... ]</code>, quella che è stata qui presentata, ma al loro interno permettono:
* concatenazioni con <code>&&</code> e <code>||</code>;
* l'uso di <code>!</code> per invertire il valore della condizione;
* di cambiare la precedenza degli operatori con <code>(...)</code>;
* pattern matching con <code>==</code> e <code>!=</code> per la stringa a destra dell'operatore tramite i caratteri speciali del globbing (<code>* ? [ ]</code>), al solito se non quotati e non preceduti dal carattere di escape <code>\</code>;
* confronti lessicografici tra stringhe con <code><</code> e <code>></code> (NOTA: non esistono "<=" e ">="). In '''bash''' (non ''POSIX'') esistono anche con <code>[...]</code>, ma con quel comando vanno quotate o precedute dal carattere di escape <code>\</code> (ossia: <code>\<</code> e <code>\></code>) perché sono caratteri speciali e soltanto le parole chiave possono permettere regole speciali al loro interno;
* l'uso di espressioni regolari estese con <code>=~</code> nel pattern matching per la stringa alla destra dell'operatore.
Si noti che soltanto gli ultimi due operatori non hanno un equivalente ''POSIX'', e l'ordine lessicografico dipende dalle impostazioni locali, e potrebbe restituire risultati diversi su sistemi con impostazioni diverse. Va ricordato che, anche se si applicano regole speciali per le ''keywords'', è '''sbagliato''' non quotare le stringhe o le variabili, salvo si voglia utilizzare il pattern matching su tutta la parte non quotata:
<pre>
[[ "$var" == "${prefisso}"* ]]  # successo se $var inizia con $prefisso
[ -z "${var##${prefisso}*}" ]  # equivalente (POSIX)
 
[[ "$var" == *"${stringa}"* ]]  # successo se $var contiene $stringa
[ -z "${var##*${stringa}*}" ]  # equivalente (POSIX)
 
[[ "$var" != *[!A-Za-z0-9]* ]]  # successo se $var contiene solo caratteri alfanumerici (solo lettere base e numeri)
[ -n "${var##*[!A-Za-z0-9]*}" ] # equivalente (POSIX)
</pre>


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.
=== Esempio: controllo sul percorso di un file ===
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>


Sintassi della definizione:
Se si è interessati solo all'ultimo caso e senza stampa di messaggi:
<pre>
<pre>
nomefunzione () {
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''.
=== Parsing degli argomenti ===
Si effettua con il comando '''getopts''' (da non confondersi con <code>getopt</code>, un comando esterno più avanzato non ''POSIX'' utilizzato 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 accedervi è necessario effettuare lo shift (con il comando <code>shift</code>) di tutte le stringhe già lette, ossia: $OPTIND - 1.
 
==== Esempio: script con parsing degli argomenti ====
<pre>
#! /bin/bash
 
# 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: "cxf:"  (per getopts)
# Per disabilitare gli errori: ":cxf:" (disabilita la stampa degli errori)
 
# ripeti finché trova opzioni
while getopts ":cxf:" 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


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.
# verifico l'esistenza dell'opzione che considero obbligatoria (ma che non lo è per getopts!)
# e per l'azione di estrazione che l'archivio esista e sia un file regolare
if [ -z "$archivio" ] || { [ "$azione" = "x" ] && ! [ -f "$archivio" ] ; }; then
    printf %s\\n "Archivio mancante!" >&2
    exit 1
fi


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.
# ricavo la lista dei file, rimuovendo le $OPTIND - 1 opzioni lette
shift $(($OPTIND - 1))
# ora "$@" contiene la lista delle stringhe non corrispondenti alle opzioni (o tutte quelle dopo --)


Se la funzione deve restituire un valore diverso da successo/insuccesso (o comunque per cui basterebbe l'exit status), è possibile:
# eseguo qualcosa, per esempio banalmente tar
* 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;
tar "${azione}f" "$archivio" -- "$@" || {
* 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;
    printf %s\\n "Comando fallito!" >&2
* 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.
    exit 2  # per distinguerlo dall'exit status di opzione sbagliata/mancante
}
exit 0
</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.}}
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
$ ./script.sh -c -f archivio file1 file2 file3
$ ./script.sh -cf archivio file1 file2 file3
$ ./script.sh -cfarchivio file1 file2 file3
$ ./script.sh -f archivio -c file1 file2 file3
$ ./script.sh -farchivio -c file1 file2 file3
</pre>


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

contributi