Dopo i costrutti decisionali, i cicli e l’espansione dei parametri, è arrivato il momento di parlare di uno strumento molto potente che Bash ci mette a disposizione: le funzioni. Una funzione è un blocco di istruzioni a cui diamo un nome. Si può definire anche direttamente a riga di comando, prima ancora di parlare di script. In questo articolo vediamo come si scrive, come si passano gli argomenti e tre trabocchetti in cui si cade facilmente: le variabili globali, il return e il modo giusto di far restituire un valore.
La prima funzione
Creiamo una funzione che si chiama saluta. Le parentesi tonde dopo il nome dicono a Bash che stiamo definendo una funzione. Poi si apre un blocco con la graffa, si mettono i comandi che vogliamo (qui basta un semplice echo) e si chiude il blocco. Attenzione al punto e virgola dopo l’ultima istruzione: serve a separare più istruzioni fra loro ma anche, con una riga sola, a far capire a Bash che l’istruzione è finita. Se scriviamo tutto su una riga servono anche gli spazi dopo la graffa aperta e prima della chiusa.
saluta() { echo "Ciao dalla mia prima funzione"; }
Dando invio la funzione è registrata. La chiamiamo semplicemente con il suo nome, senza parentesi (funziona anche il completamento col tab):
saluta
L’output è:
Ciao dalla mia prima funzione
Per ridefinire una funzione basta riscriverla. Per esempio, prima del saluto facciamo pulire lo schermo:
saluta() { clear; echo "Ciao dalla mia prima funzione"; }
saluta
Ora viene eseguito il clear e poi l’echo. Una funzione serve a non dover riscrivere ogni volta una sequenza di istruzioni complicate.
Gli argomenti
Alle funzioni si passano gli argomenti esattamente come a qualsiasi comando, e dentro la funzione si leggono con le stesse variabili che abbiamo visto per gli script: $1, $2 e così via. Ridefiniamo saluta con un segnaposto per il primo argomento:
saluta() { echo "Ciao $1"; }
saluta Linux
L’output è:
Ciao Linux
Funzionano anche gli altri parametri speciali. Definiamo una funzione che si chiama conta e usa $# (il numero degli argomenti) e $@ (tutti gli argomenti):
conta() { echo "Mi hai passato $# argomenti: $@"; }
conta uno due tre quattro
L’output è:
Mi hai passato 4 argomenti: uno due tre quattro
Una funzione in uno script
Mettiamo la funzione dentro uno script. Qui il punto e virgola non serve, perché le istruzioni sono separate dagli a capo. Cosa importante: in uno script le funzioni vanno sempre dichiarate prima di usarle, quindi non possiamo chiamare saluta finché non l’abbiamo definita. Il file funzioni.sh:
#!/bin/bash
saluta() {
echo "Ciao $1"
}
saluta Linux
Lo rendiamo eseguibile e lo lanciamo:
chmod +x funzioni.sh
./funzioni.sh
L’output è quello che ci aspettavamo:
Ciao Linux
Variabili globali e locali
Primo trabocchetto. In Bash le variabili sono globali di default, al contrario di quanto siete abituati se arrivate da altri linguaggi. Questo script, global.sh, definisce una variabile nome e una funzione cambia che la modifica:
#!/bin/bash
nome="Mario"
cambia() {
nome="Luigi"
}
cambia
echo $nome
Se siete programmatori abituati a scope separati fra funzione e resto dello script vi aspettate Mario. Invece l’output è:
Luigi
La funzione ha modificato la stessa variabile globale: non c’è separazione di scope. Ma allora come si lavora su una variabile locale dentro una funzione? Esattamente al contrario di altri linguaggi: dove altrove si dichiara globale una variabile perché sia vista ovunque, in Bash la si dichiara locale se si vuole che resti dentro la funzione, con la parola chiave local. Nuovo script, local.sh:
#!/bin/bash
nome="Mario"
cambia() {
local nome="Luigi"
echo "dentro: $nome"
}
cambia
echo "fuori: $nome"
Con local abbiamo creato una copia locale della variabile, che non tocca quella globale. L’output è:
dentro: Luigi
fuori: Mario
Il return non restituisce valori
Secondo trabocchetto. Se siete affezionati alla parola chiave return, sappiate che in Bash non fa quello che vi aspettate: non serve a restituire un valore, serve a dare un codice di uscita, esattamente come gli exit code dei comandi e degli script. Zero vuol dire che è andato tutto bene, un altro valore vuol dire che qualcosa è andato storto. Facciamo la classica funzione che dice se un numero è pari:
pari() {
if (( $1 % 2 == 0 ))
then
return 0
else
return 1
fi
}
La funzione non stampa nulla, ma possiamo leggere il suo exit code con $? (l’exit code dell’ultima esecuzione):
pari 4
echo $?
pari 5
echo $?
Per il 4 l’exit code è 0, per il 5 è 1:
0
1
Proprio perché zero è “vero”, la funzione si può anche usare direttamente in un if.
Restituire un vero valore
Come si fa allora a restituire un valore e non solo un exit code? Semplicemente sfruttando quello che già sappiamo sulla cattura dell’output: la funzione scrive il risultato con echo e noi lo catturiamo con la sostituzione di comando. Una funzione che calcola il quadrato di un numero:
quadrato() {
local n=$1
echo $(( n * n ))
}
Se la lanciamo normalmente stampa il numero:
quadrato 3
L’output è:
9
Se invece catturiamo l’output in una variabile, il valore non viene stampato dalla funzione ma finisce in risultato, e lo usiamo dopo:
risultato=$(quadrato 5)
echo "Il quadrato di 5 è $risultato"
L’output è:
Il quadrato di 5 è 25
Le funzioni nelle pipe
Una funzione si comporta come qualunque altro programma, quindi può anche ricevere l’input da una pipe. Creiamo una funzione che fa un banalissimo tr, per rendere tutto maiuscolo:
maiuscolo() { tr 'a-z' 'A-Z'; }
echo ciao | maiuscolo
L’output è:
CIAO
La stringa è passata in pipe alla funzione, che con tr la rende tutta maiuscola.
Chiusura
Questo è quello che serve per cominciare: funzioni con nome, argomenti come negli script, local per non sporcare le variabili globali, return per gli exit code ed echo con la sostituzione di comando per i valori. Aprite il vostro editor preferito e giocateci un po’.
Link e risorse
- il video su YouTube
- esempi e comandi della puntata su GitHub
- Manuale di Bash (man page) — in italiano
- Funzioni Bash, Arch Wiki — esempi pratici
- BashFAQ 002, valori di ritorno — come far restituire un valore a una funzione
- Funzione (informatica) — Wikipedia in italiano
