[successivo] [precedente] [inizio] [fine] [indice generale] [violazione GPL] [translators] [docinfo] [indice analitico] [volume] [parte]
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.
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.
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. |
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.
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 |
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.
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 |
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.
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.
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.
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 |
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.
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. |
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).
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.
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 |
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.
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.
${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.
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
${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.
$
echo "${99:-ciao}"
[Invio]
ciao
${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.
$
Pippo=""
[Invio]
$
echo "${Pippo:+pappa}"
[Invio]
Il risultato è una riga vuota.
$
Pippo="ciao"
[Invio]
$
echo "${Pippo:+pappa}"
[Invio]
pappa
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.
$
Pippo="ciao"
[Invio]
$
echo "${#Pippo}"
[Invio]
4
${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.
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.
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.
Per ottenere la dimensione di un array si utilizza una delle due notazioni seguenti (le parentesi quadre fanno parte dell'istruzione).
${#array[*]}
${#array[@]}
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]