Kosmous beyond the clouds: Sviluppo, Formazione e Consulenza Informatica

Le funzioni nella Bash

Una delle caratteristiche più interessanti della Bash sono le funzioni.

Una funzione Bash è, esattamente come in qualunque linguaggio di programmazione, un blocco di codice che svolge un certo compito e che può essere richiamato da qualunque punto dello script. Ma non solo, e vedremo perchè.

Per iniziare, è possibile vedere quali funzioni sono definite per la shell corrente usando il comando

declare -F 
o
declare -f 
cha stampa non solo il nome ma anche la definizione (il codice) delle funzioni.

Caratteristica interessante delle funzioni è che vengono eseguite nella stessa shell dello script chiamante (esattamente come uno script eseguito col comando source): nessun nuovo processo viene creato.
Questo rende preferibile la creazione di una funzione piuttosto che di uno script in certi casi.

La visibilità della funzione è limitata alla shell che la esegue, ma può essere esportata alle shell figlie come una variabile:
export -f funzione 
Poichè le funzioni vengono eseguite nella shell corrente, possono accedere a qualunque variabile valida per la shell e ogni modifica operata all'ambiente della shell all'interno di una funzione diventano permanenti per la shell anche dopo che la funzione ha restituito il controllo al chiamante.

Le funzioni hanno inoltre una precedenza maggiore rispetto agli script nell'esecuzione della shell.
Quando un comando viene passato alla shell, questa infatti cerca il comando tra, nell'ordine:
  1. Aliases
  2. Parole chiave
  3. Funzioni
  4. Comandi built-ins
  5. Script ed eseguibili (ricercati nel PATH)

Questo significa, ad esempio, che una eventuale funzione ls() sovrascriverebbe il comando Unix ls (da farsi quindi con cautela...).

La dichiarazione di una funzione deve avvenire prima dell'utilizzo della stessa, con la seguente sintassi:
function foo {
    commandi
    return
}
oppure
foo () {
    commandi
    return 
}
Non vi sono differenze tra le due forme.
La definizione di una funzione può essere cancellata col comando

unset -f funzione 

Al momento della definizione, la funzione ed il codice in essa contenuto vengono caricati in memoria.

Per eseguire la funzione è suffiente chiamarla, eventualmente passandogli i parametri:
foo $1 ... $n
Va notato che i parametri posizionali della funzione vengono richiamati con gli stessi nomi dei parametri dello script: questo significa che all'interno della funzione i parametri dello script vengono sovrascritti da quelli della funzione (questo obbliga, di passaggio, ad una buona pratica di programmazione: assegnare i parametri dello script a variabili dal nome "parlante").

Il comando return provoca l'uscita dalla funzione restituendo il controllo al chiamante, cui viene restituito un codice d'uscita che può essere passato esplicitamente come parametro a return: in caso contrario verrà restituito il codice d'uscita dell'ultimo comando eseguito.
E' possibile uscire dalla funzione anche utilizzando exit, con le stesse modalità viste per return.
La differenza è che exit provocherà l'uscita dallo script. Questi comandi d'uscita sono facoltativi: la funzione esce comunque ritornando al chiamante al raggiungimento della parentesi graffa di chiusura: il valore restutuito è in questo caso il codice d'uscita dell'ultimo comando eseguito (deve esserci uno spazio tra l'ultimo commando e la parentesi perchè questa venga interpretata come segnale di uscita dalla funzione).

All'interno della funzione è possibile dichiarare delle variabili locali, utilizzando il modificatore local prima del nome della variabile.
local my_var
in questo modo la variabile non avrà visibilità al di fuori del blocco di codice della funzione.

Caratteristica estremamente interessante, una volta dichiarata una funzione può essere richiamata dalla shell esattamente come un alias o uno script, semplicemente inserendo il nome della funzione al prompt della shell.

Per fare questo è sufficiente "caricare" in memoria la funzione.
Questo può essere fatto:
  • inserendo le funzioni nel ~/.bash_profile o nel /etc/profile
  • caricando il file contenente la dichiarazione della funzione in uno dei file di cui sopra (col comando source nomefile o . nomefile)
  • eseguendo dalla shell il file con la funzione:
    source nomefile
    o
    . nomefile
Una volta caricata la funzione (in uno qualunque di questi tre modi) al prompt della shell è possibile inserire il nome della funzione come fosse uno script a sè stante.
Ricordate l'ordine di ricerca della shell?
Una funzione cd sovrascrive sia il comando buil-in cd che l'eseguibile unix dallo stesso nome, ma viene sovrascritto a sua volta da un eventuale alias di nome cd.
Naturalmente il comando originario cd può comunque essere richiamato utilizzando il path completo.



Le funzioni e la redirezione dell'I/O

Una caratteristica della shell che rende particolarmente utili ed interessanti le funzioni è la gestione della redirezione dell'I/O.

Supponiamo di dover eseguire una funzione per ognuna delle oltre 100 righe di un file.
Si potrebbe usare una subshell, ma sarebbe meglio non creare altri processi ed eseguire il codice all'interno di un solo processo.
La bash ci viene incontro con ben 4 modi ( e forse anche altri...) per fare questo.
Il primo, il più riutilizzabile, usando una funzione:
foo () {
	commandi
}
foo <  /path/to/my_file
La funzione si comporta esattamente come uno script (ma a differenza di questo non crea un nuovo processo): ha il proprio set di descrittori di I/O che possono essere manipolati a piacimento.

C'è un altro modo per farlo, sempre usando una funzione, che è però meno generico:
foo () {
	commandi
} < /path/to/my_file
In questo modo ogni volta che la funzione viene richiamata elaborera il file /path/to/my_file.

Lo stesso può essere fatto senza usare una funzione, ma solo un blocco di codice anonimo
{
	commandi
} < /path/to/my_file
o anche solo utilizzando inserendo la redirezione dell'I/O alla fine di un ciclo:
while ...; do
	commandi
done < /path/to/my_file


Un semplice esempio: una funzione split come quella del Perl

Per concludere, un semplice esempio di funzione riutilizzabile: una implementazione in bash della funzione Perl split, che suddivide una stringa in elementi, usando come separatori dei caratteri passati come argomento, e registra gli elementi ottenuti in un array.

La funzione (anzi, le funzioni) fa parte di uno script per l'installazione di Oracle su Linux, che potete trovare sia nella sezione Risorse che in quella Download.

Ma veniamo al codice, che si compone di tre funzioni:
# fa il vero lavoro di divisione degli elementi (split)
function sp(){
    let length=tmp-1
    position=0
    area[$i]=${stringa1:$position:$length}
    stringa1=${stringa1#*-}
    tmp=`expr index $stringa1 -`
    let i=$i+1;
    if [ $tmp != 0 ]; then
        sp
    else
        area[$i]=$stringa1
    fi
}
                                                         
# wrapper per sp(), costruisce la stringa di partenza e chiama sp
function _split(){
    stringa=$1
    length=0
    i=0
    stringa1=`echo -n $stringa | sed -e 's/[._]/-/g'`
    tmp=`expr index $stringa1 -`
    if [ $tmp != 0 ]; then
        sp
    fi
}
                                                        
# wrapper per _split, e' l'interfaccia verso lo script
function check_version(){
    area=null
    area1=null
    _split $1
    for p in `seq 0 $i`; do
        area1[$p]=${area[$p]}
    done
    _split $2
    for p in `seq 0 $i`; do
        if [ ${area1[$p]} -gt ${area[$p]} ]; then
            return 1
        elif [ ${area1[$p]} -lt ${area[$p]} ]; then
            return 0
        fi
    done
    return 1
}
check_version() è la funzione che fa da interfaccia verso il chiamante: prende come parametro le due stringhe da confrontare e passa la prima a _split().
_split() sostituisce i vari caratteri separatori con uno solo (i caratteri separatori sono fissi, ma è facile parametrizzarli) e se trova almeno una occorrenza del carattere (cioè se la stringa contiene almeno due elementi) la passa a sp (), altrimenti esce.
sp() splitta la stringa e registra gli elementi ottenuti in un array ($area, dichiarato in check_version()) e ritorna al chiamante.

Lo stesso lavoro viene eseguito per la seconda stringa ed alla fine check_version() confronta in un ciclo i due array ottenuti, uscendo non appena una delle due stringhe si riveli essere maggiore dell'altra: il valore restituito è 0 se la prima stringa è minore della seconda, 1 in caso contrario (che comprende anche l'eguaglianza tra le due stringhe di versione).

La chiamata alla funzione è semlice: supponiamo di aver bisogno di verificare la versione del kernel
# la versione minima del kernel valida 
min_ker="2.4.7-10"
# il secondo parametro deve essere la versione di controllo
check_version "$( uname -r )" "$min_ker" 	
if [ ! $? ]; then
    echo "Versione del Kernel non adatta. 
    Installare un kernel versione $min_ker o superiore" 
    exit 1
else
    echo "Versione del kernel: OK" 
fi
Se la versione del kernel è uguale o superiore a $min_ker check_version() restituisce 1 e lo script continua l'esecuzione; in caso contrario viene restituito il codice 0 e lo script termina segnalando l'errore.

Vi invitiamo ad inviarci commenti, eventuali segnalazioni di errore e quant'altro.
Commenti (1)2003-06-27

10/08/2017 - no scrive:
Add To Cart viagra no prescription chea Soft often the first treatment tried for erectile dysfunction in men and .