[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico] [volume] [parte]


Capitolo 63.   Bash: programmazione

La programmazione con la shell Bash implica la realizzazione di file script. Alcune istruzioni sono particolarmente utili nella realizzazione di questi programmi, anche se non sono necessariamente utilizzabili solo in questa circostanza.

63.1   Caratteristiche di uno script

Nei sistemi Unix esiste una convenzione attraverso la quale si automatizza l'esecuzione dei file script. Prima di tutto, uno script è un normalissimo file di testo contenente una serie di istruzioni che possono essere eseguite attraverso un interprete. Per eseguire uno script occorre quindi avviare il programma interprete e informarlo di quale script questo deve eseguire. Per esempio, il comando

bash pippo

avvia l'eseguibile bash come interprete dello script pippo (il file pippo collocato nella directory corrente). Per evitare questa trafila, si può dichiarare all'inizio del file script il programma che deve occuparsi di interpretarlo. Per questo si usa la sintassi seguente:

#! nome_del_programma_interprete

Quindi, si attribuisce a questo file il permesso di esecuzione. Quando si tenta di avviare questo file come se si trattasse di un programma, il sistema avvia in realtà l'interprete.

Perché tutto possa funzionare, è necessario che il programma indicato nella prima riga dello script sia raggiungibile così come è stato indicato, cioè sia provvisto del percorso necessario. Per esempio, nel caso di uno script per la shell Bash (/bin/bash), la prima riga sarà la seguente:

#!/bin/bash

Il motivo per il quale si utilizza il simbolo # iniziale, è quello di permettere ancora l'utilizzo dello script nel modo normale, come argomento del programma interprete: rappresentando un commento non interferisce con il resto delle istruzioni.

Come appena accennato, il simbolo # introduce un commento che termina alla fine della riga, cioè qualcosa che non ha alcun valore per l'interprete; inoltre, le righe vuote e quelle bianche vengono ignorate nello stesso modo.

63.2   Strutture

Per la formulazione di comandi complessi si possono usare le strutture di controllo e di iterazione tipiche dei linguaggi di programmazione più comuni. Queste strutture sono particolarmente indicate per la preparazione di script di shell, ma possono essere usate anche nella riga di comando di una shell interattiva.

È importante ricordare che il punto e virgola singolo (;) viene utilizzato per indicare una separazione e può essere rimpiazzato da uno o più codici di interruzione di riga.

63.2.1   for

Il comando for esegue una scansione di elementi e in corrispondenza di questi esegue una lista di comandi.

for variabile [in valore...]
do
    lista_di_comandi
done

L'elenco di parole che segue la sigla in viene espanso, generando una lista di elementi; la variabile indicata dopo for viene posta, di volta in volta, al valore di ciascun elemento di questa lista; infine, la lista di comandi che segue do viene eseguita ogni volta (una volta per ogni valore disponibile). Se la sigla in (e i suoi argomenti) viene omessa, il comando for esegue la lista di comandi (do) una volta per ogni parametro posizionale esistente ($1, $1,...). In pratica è come se fosse stato usato: in $@.

Il valore restituito da for è quello dell'ultimo comando eseguito all'interno della lista do, oppure zero se nessun comando è stato eseguito.

Esempi

L'esempio seguente mostra uno script che, una volta eseguito, emette in sequenza gli argomenti che gli sono stati forniti.


#!/bin/bash
for i in $*
do
    echo $i
done

L'esempio seguente mostra uno script un po' più complicato che si occupa di archiviare ogni file e directory indicati come argomenti.


#!/bin/bash
ELENCO_DA_ARCHIVIARE=$*
for DA_ARCHIVIARE in $ELENCO_DA_ARCHIVIARE
do
    tar czvf ${DA_ARCHIVIARE}.tgz $DA_ARCHIVIARE
done

63.2.2   select

Il comando select permette all'utente di effettuare una scelta inserendo un valore attraverso la tastiera. select è stato ereditato dalla shell Korn.

select variabile [in valore...]
do
    lista_di_comandi
done

L'elenco di parole che segue la sigla in viene espanso, generando una lista di elementi. L'insieme delle parole espanse viene emesso attraverso lo standard error, ognuna preceduta da un numero. Se la sigla in (e i suoi argomenti) viene omessa, vengono utilizzati i parametri posizionali ($1, $2, ecc.). In pratica è come se fosse stato usato in $@.

Dopo l'emissione dell'elenco, viene mostrato l'invito contenuto nella variabile PS3 e viene letta una riga dallo standard input. Se la riga consiste del numero corrispondente a una delle parole mostrate, allora viene assegnato alla variabile indicata dopo select la parola corrispondente. Se la riga è vuota (probabilmente è stato premuto soltanto [Invio]), l'elenco e l'invito vengono emessi nuovamente. Se viene letto il codice corrispondente a EOF ([Ctrl+d]), il comando termina. Qualsiasi altro valore letto fa sì che la variabile sia posta al valore della stringa nulla. La riga letta viene salvata nella variabile REPLY. La lista di comandi che segue do viene eseguita dopo ciascuna selezione fino a che non viene incontrato un comando break o return.

Il valore restituito da select è quello dell'ultimo comando eseguito all'interno della lista do, oppure zero se nessun comando è stato eseguito.

Esempi

L'esempio seguente mostra uno script che fa apparire un menù composto dagli argomenti fornitigli; a ogni selezione mostra quello scelto.


#!/bin/bash
select i in $*
do
    echo "hai selezionato $i premendo $REPLY"
    echo ""
    echo "premi Ctrl+c per terminare"
done

63.2.3   case

Il comando case permette di eseguire una scelta nell'esecuzione di varie liste di comandi. La scelta viene fatta confrontando una parola (di solito una variabile) con una serie di modelli. Se viene trovata una corrispondenza con uno dei modelli, la lista di comandi relativa viene eseguita.

case parola in
    [modello [ | modello]... ) lista_di_comandi ;; ]
    ...    
    [*) lista_di_comandi ;; ]
esac

La parola che segue case viene espansa e quindi confrontata con ognuno dei modelli, usando le stesse regole dell'espansione di percorso (i nomi dei file). La barra verticale (|) viene usata per separare i modelli quando questi rappresentano possibilità diverse di un'unica scelta.

Quando viene trovata una corrispondenza, viene eseguita la lista di comandi corrispondente. Dopo il primo confronto riuscito, non ne vengono controllati altri dei successivi. L'ultimo modello può essere *), corrispondente a qualunque valore, che si può usare come alternativa finale in mancanza di altro.

Il valore restituito è zero se nessun modello combacia. Altrimenti, è lo stesso valore restituito dall'ultimo comando eseguito, contenuto all'interno della lista.

Esempi

L'esempio seguente mostra uno script che fa apparire un messaggio diverso a seconda dell'argomento fornitogli.


#!/bin/bash
case $1 in
    -a | -A | --alpha)    echo "alpha"    ;;
    -b)                   echo "bravo"    ;;
    -c)                   echo "charlie"  ;;
    *)                    echo "opzione sconosciuta" ;;
esac

Come si può notare, per selezionare alpha si possono utilizzare tre opzioni diverse.

63.2.4   if

Il comando if permette di eseguire liste di comandi differenti, in funzione di una o più condizioni, espresse anch'esse in forma di lista di comandi.

if lista_condizione
then
    lista_di_comandi
[elif lista_condizione
then
    lista_di_comandi]
...
[else
    lista_di_comandi]
fi

Inizialmente viene eseguita la lista che segue if che costituisce la condizione. Se il valore restituito da questa lista è zero (cioè Vero), allora viene eseguita la lista seguente then e il comando termina. Altrimenti viene eseguita ogni elif in sequenza, fino a che ne viene trovata una la cui condizione si verifica. Se nessuna condizione si verifica, viene eseguita la lista che segue else, sempre che esista.

Il valore restituito è quello dell'ultimo comando eseguito, oppure zero se non ne è stato eseguito alcuno.

Esempi

L'esempio seguente mostra uno script che fa apparire un messaggio di avvertimento se non è stato utilizzato alcun argomento, altrimenti si limita a visualizzarli.


#!/bin/bash
if [ $# = 0 ]
then
    echo "devi fornire almeno un argomento"
else
    echo $*
fi

L'esempio seguente mostra uno script attraverso il quale si tenta di creare una directory e se l'operazione fallisce viene emessa una segnalazione di errore.


#!/bin/bash
if ! mkdir deposito
then
    echo "Non è stato possibile creare la directory \"deposito\""
else
    echo "È stata creata la directory \"deposito\""
fi

63.2.5   while

Il comando while permette di eseguire un gruppo di comandi in modo ripetitivo mentre una certa condizione continua a dare il risultato Vero.

while lista_condizione
do
    lista_di_comandi
done

Il comando while esegue ripetitivamente la lista che segue do finché la lista che rappresenta la condizione continua a restituire il valore zero (Vero).

Il valore restituito dal comando è lo stesso di quello della lista che segue do, oppure zero se la condizione non si è mai verificata.

Esempi

Lo script dell'esempio seguente contiene un ciclo perpetuo, in cui viene richiesto di inserire qualcosa, ma solo se si inserisce la stringa fine si conclude l'iterazione.


#!/bin/bash
RISPOSTA="continua"
while [ $RISPOSTA != "fine" ]
do
    echo "usa la parola fine per terminare"
    read RISPOSTA
done

All'interno dei comandi composti si utilizzano spesso delle condizioni racchiuse tra parentesi quadre. L'uso delle parentesi quadre è una forma abbreviata del comando interno test.

63.2.6   until

Il comando until permette di eseguire un gruppo di comandi in modo ripetitivo mentre una certa condizione continua a dare il risultato Falso.

until lista_condizione
do
    lista_di_comandi
done

Il comando until è analogo a while, cambia solo l'interpretazione della lista che rappresenta la condizione nel senso che il risultato di questa viene invertito (negazione logica).

63.2.7   Funzioni

Attraverso le funzioni è possibile dare un nome a un gruppo di liste di comandi, in modo da poterlo richiamare come si fa per un comando interno normale. Sotto questo aspetto, le funzioni vengono impiegate normalmente all'interno di file script.

[function] nome () {
    lista_di_comandi
}

Le funzioni vengono eseguite nel contesto della shell corrente e quindi non vengono attivati altri processi per la loro interpretazione (ciò al contrario di quanto capita quando viene avviata l'interpretazione di un nuovo script).

La lista di comandi viene eseguita ogni volta che il nome della funzione è utilizzato come comando. Il valore restituito dalla funzione è quello dell'ultimo comando a essere eseguito all'interno di questa.

Quando viene eseguita una funzione, i parametri posizionali contengono gli argomenti di questa funzione e anche $# restituisce un valore corrispondente alla situazione. $0 continua a restituire il valore precedente, di solito il nome dello script.

All'interno della funzione possono essere dichiarate delle variabili locali attraverso il comando interno local.

È possibile utilizzare il comando interno return per concludere anticipatamente l'esecuzione della funzione. Al termine dell'esecuzione della funzione, i parametri posizionali riprendono il loro contenuto precedente e l'esecuzione dello script riprende dal comando seguente alla chiamata della funzione.

Le funzioni possono essere esportate e rese disponibili a una subshell utilizzando il comando interno export.

Esempi

L'esempio seguente mostra uno script che prima dichiara una funzione denominata messaggio e subito dopo la esegue semplicemente nominandola come un comando qualsiasi.


#!/bin/bash
messaggio () {
    echo "ciao,"
    echo "bella giornata vero?"
}

messaggio

Nell'esempio seguente, una funzione si occupa di emettere il riepilogo della sintassi per l'uso di un ipotetico script.


function sintassi () {
    echo "al {-latex | -html | -txt | -check}"
    echo ""
    echo "-latex        esegue la conversione in latex;"
    echo "-html         esegue la conversione in html;"
    echo "-txt          esegue la conversione in testo normale;"
    echo "-check        esegue il controllo sintattico SGML;"
}

Nell'esempio seguente, si utilizza il comando return per fare in modo che l'esecuzione della funzione termini in un punto determinato restituendo un valore stabilito. Lo scopo dello script è quello di verificare che esista il file pippo nella directory /var/log/packages/.


#!/bin/bash
function verifica() {
    if [ -e "/var/log/packages/$1" ]
    then
        return 0
    else
        return 1
    fi
}

if verifica pippo
then
    echo "il pacchetto pippo esiste"
else
    echo "il pacchetto pippo non esiste"
fi      

63.3   Espressioni aritmetiche

La shell consente di risolvere delle espressioni aritmetiche in certe circostanze. Il calcolo avviene su interi senza controllo dell'overflow, anche se la divisione per zero viene intercettata e segnalata come errore. Oltre alle espressioni puramente aritmetiche si possono risolvere espressioni logiche e binarie, anche se l'utilizzo di queste ultime non è indicato. La tabella 63.1 riporta l'elenco degli operatori aritmetici disponibili.

Tabella 63.1. Operatori aritmetici della shell Bash.

Operatore e operandi Descrizione
+op Non ha alcun effetto.
-op Inverte il segno dell'operando.
op1 + op2 Somma i due operandi.
op1 - op2 Sottrae dal primo il secondo operando.
op1 * op2 Moltiplica i due operandi.
op1 / op2 Divide il primo operando per il secondo.
op1 % op2 Modulo: il resto della divisione tra il primo e il secondo operando.
var = valore Assegna alla variabile il valore alla destra.
op1 += op2 op1 = op1 + op2
op1 -= op2 op1 = op1 - op2
op1 *= op2 op1 = op1 * op2
op1 /= op2 op1 = op1 / op2
op1 %= op2 op1 = op1 % op2

Le variabili di shell possono essere utilizzate come operandi; l'espansione di parametri e variabili avviene prima della risoluzione delle espressioni. Quando una variabile o un parametro vengono utilizzati all'interno di un'espressione, vengono convertiti in interi. Una variabile di shell non ha bisogno di essere convertita.

La forma generale per esprimere un numero è quella seguente:

[base#]n

In tal modo si può specificare esplicitamente la base di numerazione (va da un minimo di due a un massimo di 64). Se non viene espressa, si intende base 10. Per le cifre numeriche superiori al numero nove, si utilizzano le lettere minuscole, le lettere maiuscole, il simbolo _ e infine @, in questo ordine. Se la base di numerazione è inferiore o uguale a 36, non conta più la differenza tra lettere maiuscole e minuscole dal momento che non esiste la necessità di rappresentare un numero elevato di cifre. Una costante che inizia con uno zero viene interpretata come un numero ottale, mentre se inizia per 0x o 0X si considera rappresentare un numero esadecimale.

Gli operatori sono valutati in ordine di precedenza. Le sottoespressioni tra parentesi sono risolte prima.

63.4   Riferimenti particolari alle variabili

L'espansione normale delle variabili è già stata vista nella sezione 61.3.4, ma la shell Bash offre in particolare dei modi alternativi, derivati dalla shell Korn, utili particolarmente per la programmazione.

Le parentesi graffe usate negli schemi sintattici delle sezioni seguenti, fanno parte delle espressioni, come si può osservare dagli esempi.

63.4.1   Segnalazione di errore

${parametro:?parola}

${variabile:?parola}

Definisce un messaggio, rappresentato dalla parola, da emettere attraverso lo standard error nel caso il parametro o la variabile non siano stati definiti o siano pari alla stringa nulla.

Esempi

Si suppone che la variabile Nessuno non sia stata definita o sia pari alla stringa nulla.

echo "${Nessuno:?Variabile non definita}"[Invio]

bash: Nessuno: Variabile non definita

63.4.2   Valore predefinito

${parametro:-parola}

${variabile:-parola}

Definisce un valore predefinito, corrispondente alla parola indicata, nel caso che il parametro o la variabile non siano definiti o siano pari alla stringa nulla.

Esempi

echo "${99:-ciao}"[Invio]

ciao

63.4.3   Rimpiazzo

${parametro:+parola}

${variabile:+parola}

Definisce un valore alternativo, corrispondente alla parola indicata, nel caso che il parametro o la variabile siano definiti e siano diversi dalla stringa nulla.

Esempi

Pippo=""[Invio]

echo "${Pippo:+pappa}"[Invio]

Il risultato è una riga vuota.

Pippo="ciao"[Invio]

echo "${Pippo:+pappa}"[Invio]

pappa

63.4.4   Lunghezza del contenuto

Questo tipo di sostituzione riguarda solo la shell Bash.

${#parametro}

${#variabile}

Corrisponde alla lunghezza in caratteri del valore contenuto all'interno del parametro o della variabile. Se però si tratta del parametro * o @ il valore è pari al numero dei parametri posizionali presenti.

Esempi

Pippo="ciao"[Invio]

echo "${#Pippo}"[Invio]

4

63.4.5   Valore predefinito con assegnamento

${variabile:=parola}

Assegna alla variabile il valore indicato dalla parola, nel caso che la variabile non sia definita o sia pari alla stringa nulla. In pratica, rispetto alla sintassi ${variabile:-parola} si ottiene in più l'assegnamento della variabile.

63.5   Array

Oltre alle variabili scalari normali, si possono utilizzare degli array dinamici a una sola dimensione. Con questo tipo di array non è necessario stabilire la dimensione: basta assegnare un valore in una posizione qualunque e l'array viene creato. Per esempio,

elenco[3]="Quarto elemento"

crea un array contenente solo l'elemento corrispondente all'indice tre, il quale, a sua volta, contiene la frase «Quarto elemento».

Gli array della shell Bash hanno base zero, cioè il primo elemento si raggiunge con l'indice zero (ecco perché nell'esempio si fa riferimento a un quarto elemento).

È possibile creare un array anche usando il comando interno declare o local con l'opzione -a.

È possibile assegnare tutti i valori degli elementi di un array in un colpo solo. Si utilizza la notazione seguente:

array=(valore_1 valore_2 ... valore_n)

I valori indicati tra parentesi, a loro volta, possono essere espressi nella forma seguente (le parentesi quadre fanno parte dell'istruzione).

[indice]=stringa | stringa

La sintassi chiarisce che è possibile sia indicare esplicitamente l'indice dell'elemento da assegnare, sia farne a meno e quindi lasciare che sia semplicemente la posizione dei valori a stabilire l'elemento rispettivo che dovrà contenerli.

63.5.1   Espansione con gli array

Per fare riferimento al contenuto di una cella di un array si utilizza la notazione seguente (le parentesi quadre fanno parte dell'istruzione).

${array[indice]}

Se si legge un array come se fosse una variabile scalare normale, si ottiene il contenuto del primo elemento (zero). Per esempio, $pippo[0] è esattamente uguale a $pippo.

È possibile espandere gli elementi di un array tutti contemporaneamente. Per questo si utilizza il simbolo *, oppure @, al posto dell'indice. Se si utilizza l'asterisco si ottiene una sola parola, mentre utilizzando il simbolo @ si ottiene una parola per ogni elemento dell'array.

63.5.2   Lettura della dimensione

Per ottenere la dimensione di un array si utilizza una delle due notazioni seguenti (le parentesi quadre fanno parte dell'istruzione).

${#array[*]}

${#array[@]}

63.5.3   Eliminazione

Come nel caso delle variabili scalari, il comando unset permette di eliminare un array. Se però si fa riferimento a un elemento particolare di questo, si elimina solo quello, senza annullare l'intero array.

Appunti di informatica libera 2003.01.01 --- Copyright © 2000-2003 Daniele Giacomini -- daniele @ swlibero.org

Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome bash_programmazione.html

[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico]