Introduzione allo Shell Scripting |
In questo capitolo si daranno le basi sintattiche della shell Bash, dando ampio spazio alla trattazione delle variabili e dei parametri. Verranno anche presi in considerazione argomenti quali gli operatori più importanti della shell Bash e le condizioni.
Uno dei modi migliori di introdurre gli elementi sintattici degli script di shell è partire dai commenti. In Bash, come in altre shell Unix, i commenti vengono individuati dal carattere #. Per la precisione, tutto ciò che segue ed è sulla stessa riga di # è da considerarsi un commento. Ad esempio le linee seguenti rappresentano commenti:
# Questo è un commento che si estende su una linea # # Questo è un commento # piazzato su due linee # funzione() # questo è un commento piazzato dopo una funzione #Una caratteristica essenziale di uno shell script è quella di dover sempre iniziare con un uno strano simbolo: #! (che non è un commento!). Questa combinazione di caratteri viene chiamata Shee-Bang e serve a specificare quale deve essere l'interprete delle istruzioni che seguono. Nel nostro caso, il primo rigo di ogni script sarà:
#!/bin/bash # Shee-Bang seguito dal percorso completo dell'interprete # # Seguono le istruzioni dello script ...
Prima di procedere oltre, chiariamo che con ``parola'' si intende una qualsiasi sequenza di uno o più caratteri che Bash interpreta come una singola entità. Ciò detto, Osserviamo che Bash riserva alcune parole (Per la maggior parte comandi) per attribuir loro un significato speciale. Queste sono:
[nico@deepcool nico]$ echo if if [nico@deepcool nico]$ echo function functionUn altro modo per utilizzare le parole elencate consiste nel racchiuderle tra virgolette (Anche apici, si tratta comunque di quoting) o nel farle precedere dal carattere `` ''(Escaping). In questo modo, la shell non andrà incontro ad alcuna ambiguità nell'interpretare le istruzioni.
I tempi sono ormai sufficientemente maturi per cimentarci in un primo
script, proviamo con il classico ``Ciao mondo'':
#!/bin/bash # # Ciao mondo con la shell Bash EXIT_SUCCESS=0 echo -e "Ciao mondo\n" exit $EXIT_SUCCESS
Lo script2.2 inizia, come necessario, con #!/bin/bash, seguito da due commenti. Successivamente viene definita la variabile EXIT_SUCCESS assegnandole il valore 0. La riga seguente fa uso di un comando presente all'interno della shell; echo serve a stampare sullo schermo del testo (per ragioni di portabilità echo è fornito anche come comando esterno, tramite il pacchetto GNU Shell Utils). È stato usato con l'opzione -e che consente di interpretare correttamente la sequenza di escape n , che rappresenta un ritorno a capo. Per maggiori informazioni, si digiti in un terminale man echo. Lo script termina con la riga exit $EXIT_SUCCESS. Anche exit è un comando interno alla shell (Bash built-in command) e serve in un certo senso a stabilire una comunicazione tra lo script e l'ambiente in cui viene eseguito. Una volta terminato, infatti, il nostro script dirà alla shell che tutto è andato per il verso giusto (codice 0). È convenzione che il valore di ritorno di uno script, o qualsiasi altro programma, sia 0 in caso di riuscita, un qualsiasi altro numero in caso d'errore2.3. Ad esempio, se un comando non viene trovato, il valore di ritorno del processo è 127; se il comando viene trovato, ma non è eseguibile, il valore di ritorno è 126.
In questo primo script abbiamo incontrato alcune novità, una di queste
è rappresentata dalla definizione di una variabile. Questo argomento
sarà oggetto della prossima sezione.
Spesso capita di cucinare e di conservare le pietanze in contenitori per poi usarle in futuro. La shell fornisce ``contenitori'' analoghi, le variabili, nelle quali è possibile memorizzare valori per usarli in futuro o per maneggiarli più agevolmente.
EXIT_SUCCESS=0In fase di assegnazione, occorre fare attenzione a non lasciare spazi tra il nome della variabile il segno = ed il valore, in caso contrario la shell interpreterebbe = come un operatore (cfr. pag. , sez. 2.4). Un altro modo di assegnare una variabile fa uso del comando read , un'altro comando interno della Bash. Ad esempio, il seguente codice legge dalla tastiera un valore e lo assegna alla variabile $VAR:
read VARSi noti che nel dichiarare una variabile occorre sempre usarne il nome privo del simbolo ``$'', che va invece usato quando si deve far riferimento ad essa. Chiariamo la questione con un esempio:
#!/bin/bash # # Dichiarazione di variabili EXIT_SUCCESS=0 NOME=Nico echo "Il mio nome è $NOME, il tuo?" read TUO_NOME echo "Ciao $TUO_NOME, buon divertimento con la shell!" exit $EXIT_SUCCESS
Lo script inizia con il solito #!/bin/bash (d'ora in poi non lo faremo più notare) e subito dopo vengono definite le variabili $EXIT_SUCCESS e $NOME2.4.
L'istruzione echo, successivamente, stampa sullo schermo il
testo Il mio nome è Nico, il tuo? ed attende finché non viene
scritto qualcosa e poi premuto il tasto return(RET). L'input
letto dalla tastiera con read viene assegnato alla variabile
$TUO_NOME che viene usata per stampare un messaggio di
cortesia. Lo script ritorna alla shell il valore 0.
Le variabili che abbiamo incontrato fino ad ora cessano di esistere non appena lo script termina. Nonostante ciò, esistono alcune variabili, dette d'ambiente che conservano nel tempo il loro valore. Tali variabili sono accessibili all'utente attraverso la parola chiave export2.5 in modo da garantirne un uso corretto. Occorre comunque notare che, anche usando export all'interno di uno script su di una variabile d'ambiente, i cambi a tale variabile rimarranno locali allo script e non si estenderanno alla shell che lo ha eseguito. Ciò accade perché uno script può esportare variabili solo ai suoi processi figli, cioè alle istruzioni in esso presenti. Risulta ovvio a questo punto che le variabili d'ambiente sono variabili impostate primordialmente dalla shell o altri programmi (Ad esempio login e telnet) e sono fruibili dai suoi processi figli (i comandi, gli script, ...).
Una lista di variabili globali può essere ottenuta digitando in
console l'istruzione set. Alcune delle variabili più comuni
sono2.6:
Cerchiamo di saperne di più.
#!/bin/bash # # Uso delle variabili d'ambiente EXIT_SUCCESS=0 echo "Informazioni sul sistema:" echo -e "hostname:\t$HOSTNAME" echo -e "hardware:\t$HOSTTYPE" echo -e "OS:\t$OSTYPE\n" echo "Informazioni sull'utente:" echo -e "logname:\t$LOGNAME" echo -e "homedir:\t$HOME" echo -e "shell:\t$SHELL" echo -e "path:\t$PATH\n" echo -e "Directory di esecuzione: $PWD\n" echo "Esportiamo un nuovo PATH..." export PATH=${PATH}:/usr/games echo -e "...il nuovo PATH è:\n$PATH" exit $EXIT_SUCCESS
export NOME_VARIABILE=valore_della_variabileo, equivalentemente
NOME_VARIABILE=valore_della_variabile export NOME_VARIABILEUn'altra novità è rappresentata dall'uso della scrittura ${PATH}. In realtà, questo è il vero modo per riferirsi ad una variabile, in genere si preferisce la più comoda forma $PATH, ma questa deve essere subito abbandonata quando si corre il rischio di incappare in ambiguità.
Come si può vedere dall'output dello script, il contenuto di
$PATH sembra essere variato, ma se, finito lo script,
digitiamo in terminale echo $PATH, possiamo renderci conto
che il cambio della variabile è rimasto confinato nello ``spazio'' di
esecuzione dello script.
Proviamo ora una versione leggermente modificata e più breve dello
script precedente.
#!/bin/bash # # ancora sulle variabili EXIT_SUCCESS=0 echo "Informazioni sul sistema:" echo -e "hostname:\t$(hostname)" echo -e "hardware:\t$(uname -m)" exit $EXIT_SUCCESS
Immaginiamo di lanciare un nostro script in maniera un po' differente,
supponiamo di farlo scrivendo in un terminale:
[nico@deepcool intro]$ ./script param1 param2 param3Come sfrutterà lo script queste informazioni aggiuntive? Ritornerà un errore?
#!/bin/bash # # Utilizzo dei parametri posizionali EXIT_SUCCESS=0 echo "Hai inserito ${1}, $2 e $3 per un totale di $# parametri" exit $EXIT_SUCCESS
Proviamo ad eseguire lo script:
[nico@deepcool intro]$ ./script param1 param2 param3 Hai inserito param1, param2 e param3 per un totale di 3 parametriSembra funzionare bene, ma cosa succederebbe se inserissimo quattro parametri anziché tre? Per il momento lasciamo in sospeso questa domanda, promettendo di occuparcene più in avanti nel capitolo 3.
I parametri disponibili all'interno di uno script non si esauriscono
di certo qui, alcuni ulteriori sono2.9:
È importante chiarire la differenza che corre tra $* e
$@; la prima scrittura fornisce tutti i parametri
posizionali visti come un'unica parola, mentre la seconda fornisce
tutti i parametri posizionali ognuno come singola parola. Questa
distinzione sarà importante in futuro.
In precedenza, abbiamo già incontrato la forma ${VARIABILE} (sezione 2.2.2) ed abbiamo affermato che questo è il modo più generale per riferirsi ad una variabile. Tuttavia, il simbolo ${ ... } ha un ruolo ben più importante, poiché rappresenta la forma più generale per consentire alla shell di espandere parametri. Nel caso di ${VARIABILE} il parametro è la variabile $VARIABILE e l'espansione è il suo valore.
Vediamo ora alcune delle altre forme di espansione offerte dalla shell
Bash.
Valutare una condizione è una caratteristica essenziale per ogni
linguaggio di programmazione, pertanto verranno elencati alcuni degli
operatori più importanti della shell Bash.
Il calcolo di operazioni aritmetiche può essere effettuato utilizzando
il comando expr , o il comando interno
let , oppure racchiudendo l'espressione
da valutare nel simbolo $(( ... )) (Espansione aritmetica). Inoltre, possono anche essere usata
la forma (( ... )), che non produce alcun output, ma valuta
solamente l'espressione aritmetica contenuta al suo interno.
#!/bin/bash # # Esecuzione di calcoli EXIT_SUCCESS=0 echo $(( 2 / 3 )) echo $(( 5 + 5 / 2 )) echo $(( $(( 5 + 5 )) / 2 )) echo $(( 2 ** 8 )) exit $EXIT_SUCCESS
Si tenga presente che sono legali anche le forme ``alla C''
+=, -=, *=, /= e %=;
queste devono essere interpretate come:
VARIABILE_1(OP)=$VARIABILE_2 equivale a VARIABILE_1=$(( $VARIABILE_1 (OP) $VARIABILE_2 ))dove (OP) è uno tra +, -, *, / e %.
#!/bin/bash # # Esecuzione di calcoli (2) EXIT_SUCCESS=0 VARIABILE=10 echo $VARIABILE (( VARIABILE+=10 )) echo $VARIABILE (( VARIABILE/=10 )) echo $VARIABILE exit $EXIT_SUCCESS
L'esempio illustra l'uso delle forme (OP)= nonché quello di (( ... )). La forma (( ... )), come detto, non produce alcuna espansione, ma si limita a modificare il valore di $VARIABILE.
Per usare < e > occorre fare attenzione a precederli
con il carattere di escape ``
''o racchiuderli tra
virgolette (O apici), la shell altrimenti li interpreterebbe come
operatori di input/output, come vedremo nella sezione 2.4.5
Gli operatori su file sono per la maggior parte operatori unari (accettano un solo input), tranne gli ultimi 3 di questa lista, che sono binari. Ritornano tutti un valore booleano (vero o falso) a seconda che il test effettuato abbia avuto successo o meno.
Consideriamo il comando
[nico@deepcool nico]$ cat /etc/passwd | grep bash root:x:0:0:root:/root:/bin/bash nico:x:500:500:Domenico Delle Side:/home/nico:/bin/bashed analizziamo cosa è accaduto. cat è un programma che stampa sullo standard output (lo schermo nel nostro caso) il contenuto dei file passati come argomento. grep, invece, serve per ricercare una particolare espressione all'interno di un testo che può essere contenuto in un file oppure scritto sullo standard input. La sequenza di comandi data, dunque, si può riassumere dicendo che attraverso |, il contenuto del file /etc/passwd viene passato a grep che cerca poi all'interno del testo la parola bash.
[nico@deepcool nico]$ mail nico < /etc/passwdfa comparire per incanto nella mia casella locale di posta il contenuto del file /etc/passwd.
[nico@deepcool nico]$ man bash > bash [nico@deepcool nico]$ ls -lh bash -rw-rw-r-- 1 nico nico 293k Jul 24 19:59 bash [nico@deepcool nico]$ man bash >> bash [nico@deepcool nico]$ ls -lh bash -rw-rw-r-- 1 nico nico 586k Jul 24 19:59 bash [nico@deepcool nico]$ man bash > bash [nico@deepcool nico]$ ls -lh bash -rw-rw-r-- 1 nico nico 293k Jul 24 19:59 bashAnalizziamo ciò che è accaduto. Il primo comando man bash > bash reindirizza lo standard output del comando man all'interno del file /home/nico/bash; dato che tale file non esisteva in precedenza, > si occupa di crearlo. Il risultato finale è, come è possibile vedere attraverso il comando ls -lh, un file di testo da 293 Kb contenente la pagina manuale di bash.
Prima di trattare questo argomento, occorre fare una piccola trattazione sulla rappresentazione binaria dell'informazione.
All'interno di un calcolatore, l'informazione è rappresentata a
livello di tensione che può ``accendere'' o ``spegnere'' determinati
circuiti. Di conseguenza, ogni informazione può essere
convenientemente espressa in termini di due stati che descrivono la
situazione in cui si trovano degli ipotetici interruttori che azionano
tali circuiti. Per convenzione, si indicano queste configurazioni con
1 (acceso) e 0 (spento).
Ogni informazione presente in un calcolatore è dunque rappresentata da
sequenze di numeri del tipo 1011001011010001, in cui ogni
istanza di 1 o 0 rappresenta un bit
, ovvero l'unità di memoria elementare di un calcolatore.
Ad esempio, anche i numeri naturali vengono rappresentati sotto forma
binaria (0 = 0000, 1 = 0001, 2 = 0010,
3 = 0011, 4 = 0100, 5 = 0101, 6 =
0110, 7 = 0111, 8 = 1000, 9 = 1001,
ecc...).
Gli operatori su bit consentono di eseguire operazioni direttamente su
queste quantità. Elenchiamo di seguito quelli offerti dalla shell
Bash:
#!/bin/bash # # Potenze del due tramite operatori su bit. EXIT_SUCCESS=0 echo "Inserisci l'esponente della potenza di 2 che vuoi conoscere" echo "(vale solo per esponenti positivi): " read ESPONENTE echo "2^($ESPONENTE) vale: $(( 2 << $(( $ESPONENTE - 1 )) ))" exit $EXIT_SUCCESS
Uno delle caratteristiche più utili delle shell Unix è la possibilità di poter concatenare dei comandi e condizionare agevolmente l'esecuzione di uno in luogo dell'altro. Questa possibilità è offerta da una serie di operatori elencati di seguito.
Dopo aver passato in rassegna i più importanti operatori offerti dalla shell Bash, vediamo come utilizzarli con maggiore dettaglio.
Innanzitutto, occorre capire come è possibile valutare una condizione,
cioè come eseguire dei test. Niente di più facile, esiste il comando
interno test che da alla shell la possibilità di valutare la
veridicità della condizione che gli si fornisce. Accanto a
test, esiste anche il simbolo [ ... ] che consente
alla shell di valutare la condizione interna. Esiste anche una terza
via per valutare condizioni ed è fornita da [[ ... ]],
analogo ai precedenti e con qualche facilitazione aggiunta (consultare
la pagina manuale o il manuale info di Bash per maggiori
informazioni), tuttavia è presente solo sulle versioni più recenti di
Bash (dalla versione 2.02). Inoltre, è meglio non mettere
troppa carne al fuoco; in questo momento sarete così presi da questa
interessantissima ed avvincente guida che rischiereste di bruciarla,
un vero peccato!
Come primo esempio, vediamo come è possibilire utilizzare gli
operatori logici e le differenze che corrono tra loro. Prima di tutto
occorre esaminare come si compongono le condizioni con AND,
OR e gli operatori di lista. Un AND logico è vero
se e solo se sono vere tutte le condizioni che lo compongono, mentre
un OR è falso se e solo se sono false tutte le condizioni che
lo compongono (quindi vero negli altri casi).
#!/bin/bash # # Uso degli operatori logici EXIT_SUCCESS=0 [ -e $HOME/.bashrc ] && echo "Nella mia home è presente .bashrc" [ ! -e $HOME/non_esisto ] && echo "Il file non_esisto non esiste!" [ ! -e $HOME/.bashrc ] || echo "Nella mia home è presente .bashrc" [ -e $HOME/non_esisto ] || echo "Il file non_esisto non esiste!" exit $EXIT_SUCCESS
Per ogni riga di codice, a meno di situazioni strane, l'esempio stamperà sullo schermo il relativo messaggio. Vediamo in dettaglio cosa accade. verifica la presenza del file .bashrc nella home directory di chi lo esegue, poiché questa guida è basata sulla shell Bash, è ragionevole supporre che questo file esista, dunque la condizione sarà vera. In questa situazione, l'operatore && si preoccuperà di eseguire il comando seguente echo ``Nella mia home è presente .bashrc'' , che stamperà sullo schermo il messaggio ritornando un valore di successo. La seconda condizione ( ) valuterà la non esistenza, nella home directory dell'esecutore, del file non_esisto; non è un nome di file comune quindi non dovrebbe esistere pertanto negando la condizione attraverso l'operatore ! otterremo una condizione vera e verrà pertanto stampato sullo schermo il messaggio successivo, come prima.
Sappiamo già che in $HOME esiste .bashrc, dunque il
contrario è falso, pertanto nella riga successiva, l'operatore
|| consentirà l'esecuzione del comando successivo, stampando
il messaggio sullo schermo. Analogamente, il test successivo è falso
e verrà nuovamente stampato un messaggio sullo schermo.
#!/bin/bash # # Uso degli operatori logici (2) EXIT_SUCCESS=0 [ -e $HOME/.bashrc -a ! -e $HOME/non_esisto ] && \ echo "Nella mia home esiste .bashrc e non c'è non_esisto" [ -e $HOME/non_esisto -o -e $HOME/.bashrc ] && \ echo "Nella mia home non c'è non_esisto, ma si trova .bashrc" exit $EXIT_SUCCESS
In questo esempio capiamo la differenza che corre rispettivamente tra gli operatori ``-a - &&'' e ``-o - ||''. Infatti, -a e -o sono operatori che collegano logicamente delle condizioni all'interno di un test, mentre && e || collegano ``logicamente'' dei comandi2.10. In altre parole è una condizione composta tramite l'operatore logico -a (AND) e risulta vera quando tutte le sotto-condizioni che la compongono sono vere. Allo stesso modo, anche è una condizione composta, questa volta tramite l'operatore logico -o (OR) e risulta vera quando almeno una delle due sotto-condizioni è vera.
Gli operatori && e ||, invece, collegano comandi
in base al loro valore di ritorno, come abbiamo visto nell'esempio
precedente.
Introduzione allo Shell Scripting |