Molti script Bash sono scritti in uno stile strettamente imperativo: esegui un comando, poi un altro, e così via. A volte hai bisogno di raggruppare una sequenza di comandi che, concettualmente, svolgono un unico scopo. Ed è qui che entrano in gioco le funzioni.
Una funzione si definisce così:
my_function () {
COMMANDS
}
La coppia vuota di parentesi tonde (()) indica semplicemente che stai definendo una funzione.
Dentro non ci va nulla.
Una volta definite, le funzioni si comportano come qualsiasi altro comando (integrato o no).
Come per qualsiasi comando, puoi passare degli argomenti alle funzioni.
All'interno di una funzione, accedi agli argomenti tramite i parametri posizionali, $1, $2, ecc.
(Ricordi? Abbiamo parlato dei parametri posizionali nel concetto Variabili.)
Il parametro speciale $0 non cambia all'interno di una funzione: è sempre il nome dello script in esecuzione.
La funzione in esecuzione può accedere al proprio nome con la variabile $FUNCNAME.
Vedi 3.4.2 Parametri speciali nel manuale.
Puoi definire delle variabili all'interno di una funzione.
Se dichiari le variabili con il comando local, lo scope della variabile è limitato alla funzione corrente (e a tutte le funzioni chiamate da essa).
Altrimenti, la variabile finisce nello scope globale.
Una variabile locale può avere lo stesso nome di una variabile globale. In quel caso, la variabile locale «oscura» quella globale. Per esempio, una variabile locale dichiarata in una funzione nasconde una variabile globale con lo stesso nome: i riferimenti e le assegnazioni si riferiscono alla variabile locale, lasciando invariata quella globale. Quando la funzione termina, la variabile globale torna a essere visibile.
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
Questo produce
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
All'interno di una funzione, puoi accedere alle variabili dello scope del chiamante. Questo significa che puoi usare le variabili globali, ma anche le variabili locali dichiarate nel chiamante (o in qualche funzione che chiama il chiamante).
Tecnicamente, «globale» non è la parola giusta da usare. Per espandere una variabile all'interno di una funzione, Bash risale la pila delle chiamate, fino allo scope globale, per trovare una funzione in cui quel nome di variabile è stato dichiarato.
Questo esempio è adattato dalla sezione Funzioni della shell del manuale:
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
L'output è:
In func2, var = func1 local
In func2, var = global
Allo stesso modo, assegnare un valore a una variabile lo assegna nello scope in cui è stata dichiarata. Questa «azione a distanza» può creare codice difficile da seguire, perché non è sempre ovvio dove è stato assegnato un valore a una variabile.
La pila delle chiamate si può esaminare usando la variabile array FUNCNAME.
Una funzione, come qualsiasi comando, ha uno stato di uscita. Per impostazione predefinita, lo stato di una funzione è lo stato di uscita dell'ultimo comando eseguito.
Puoi usare il comando return per uscire da una funzione con uno stato di uscita specifico.
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
Usare return senza argomenti restituisce lo stato dell'ultimo comando eseguito.
Nota che la funzione check_password può essere semplificata così:
check_password () { [[ $1 == "secret" ]]; }
[[...]] ha uno stato di uscita: 0 per «vero», 1 per «falso».{...} deve avere una nuova riga o un punto e virgola prima della parentesi graffa di chiusura.Lo stato restituito da una funzione è solo un numero. Come può una funzione produrre output?
La funzione può stampare sullo standard output. Usa la familiare sostituzione di comandi per catturarlo:
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
Lo stato di uscita di una funzione è disponibile anche quando stai catturando l'output.
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
Le funzioni possono chiamare se stesse in modo ricorsivo. Per impostazione predefinita, non c'è alcun limite alla profondità della ricorsione.
Un esempio:
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
La profondità della ricorsione si può controllare con la variabile FUNCNEST.
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)