Sai perché ritengo che il linguaggio bash, nonostante sia molto molto vicino al C, sia uno dei linguaggi più facili da comprendere ai profani? Perché gran parte della sintassi usa una forma quasi uguale al linguaggio parlato, quindi anche se non hai mai visto un linguaggio di programmazione, se leggi con attenzione del codice bash puoi facilmente intuirne il senso – chiaro, non capirai mai cosa fa lo script nella sua totalità – ma non avrai davanti a te una serie di caratteri incomprensibili.
Questo lo percepisci in particolare quando ti ritrovi davanti i cosiddetti cicli.
Cicli: Le strutture di controllo della bash
Indice Contenuti- Cicli: Le strutture di controllo della bash
- While
- For
- Until
I cicli fanno parte di quel gruppo di bash che si chiama strutture di controllo, anzi, i cicli a dir la verità sono le strutture di controllo del linguaggio bash; il perché è presto detto, tramite i cicli noi effettuiamo tutti i vari controlli che possono servire all’esecuzione di uno script, ad esempio sull’immissione dei dati immessi durante l’interazione con l’utente.
I cicli in bash sono 3, ed i loro nomi sono quanto di più esplicativo possa esistere: for, while, until.
Prima di iniziare però è doverosa una premessa, può darsi che tu abbia visto altri linguaggi in passato e quindi – naturalmente – tenderai ad accostare il funzionamento dei cicli (tipo quello del for) a quello che hanno negli altri linguaggi, questo è sbagliato perché nel bash le cose cambiano, ti consiglio quindi di uscire dagli schemi.
While
While significa letteralmente “mentre, fintanto che” quindi, con while, apriamo un ciclo di controllo che esegue le istruzioni che immettiamo nel blocco di codice fintanto che le condizioni che decidiamo noi sono vere, piccola nota: il controllo delle condizioni viene effettuato tramite il comando “test”, però tu non lo vedrai mai scritto negli esempi che seguono, questo perché il comando – in quanto tale – viene usato quando si usa la bash normalmente, negli scripit è preferibile racchiudere le condizioni tra parentesi quadre, è più leggibile. Vediamo un esempio:
1 2 3 4 5 6 7 8 9 10 11
#!/bin/bash read -p "Inserisci la lettera A" LETTERA while [ "$LETTERA" != "A" ] do echo "Non hai inserito la lettera richiesta, riprova!" read LETTERA done echo "Hai inserito la lettera A, script concluso."
In base a quanto abbiamo appena detto avremmo pure potuto aprire il ciclo con while test “$LETTERA” != “A” ecc ecc…la scelta è tua, io preferisco usare le quadre, inoltre negli script molto difficilmente troverai l’uso del comando test, in ogni caso il significato e l’effetto sono equivalenti.
Vediamo cosa significa questo ammasso di lettere:
- read -p: Questo non c’entra niente con il ciclo while, con read -p stampiamo a schermo quello che è contenuto tra doppi apici restituendo un prompt interattivo all’utente, e diciamo a bash di valorizzare ciò che l’utente immette (terminando l’immissione col tasto invio) nella variabile $LETTERA.
- while: Ecco il ciclo (che detta cosi fa pure brutto, ma noi stiamo parlando di bash non dimenticartelo), per tradurlo in italiano potremmo dire che abbiamo scritto “finchè il valore di $LETTERA è diverso da A, stampa a schermo “Non hai scritto…” e restituisci un prompt all’utente valorizzando ciò che scrive nella variabile $LETTERA, altrimenti termina il ciclo e prosegui (done)”.
Avrai notato che ho usato un uguale per dire diverso, in realtà hai visto anche che l’uguale è immediatamente preceduto da un “!”, il singolo punto esclamativo in bash rappresenta infatti una sorta di “non”, ossia una condizione negativa. Quindi con “=” indichiamo uguaglianza tra due elementi, con “!=” indichiamo la “non uguaglianza”, quindi la diversità.
Dobbiamo puntualizzare un pò di cose riguardo all’uso di while, e dei cili in generale, prima di tutto l’uso delle virgolette all’interno delle quadre, le variabili infatti devono essere sempre inserite tra doppi apici, questo perché se per caso decidessimo di valorizzare la variabile all’interno del ciclo (senza quindi dichiararla preventivamente nell’header dello script o valorizzandola come nell’esempio con read), ciò significherebbe che all’apertura del ciclo tale variabile avrebbe valore nullo, rendendo la bash incapace di gestirla, in questo caso lanciando lo script avremmo un errore del tipo
1
./script: [: !=: unary operator expected
questo perché l’uso delle parentesi quadre in bash prevede l’inserimento di operatori unari per le condizioni di verifica. Se non mettessimo la variabile tra virgolette sarebbe come scrivere “while [ != "A" ]” il che, ovviamente, è senza senso.
Quando usiamo le parentesi quadre, sia nei cicli che nelle altre funzioni della bash, è importante inserire uno spazio dopo la parentesi quadra di apertura e prima di quella di chiusura, altrimenti la bash non “capisce” come gestire correttamente le quadre. ["$LETTERA" != "A"] è quindi scorretto, l’unica forma corretta è quella che hai visto nell’esempio.
La sintassi di while può anche essere scritta sfruttando il separatore di istruzioni, che in bash è il punto e virgola:
1 2 3 4 5
while [ "$LETTERA" != "A" ]; do istruzioni done
od addirittura
1
while [ "$LETTERA" != "A" ];do istruzioni; done
quest’ultima forma è molto usata durante l’uso normale della shell, e non all’interno di uno script, ad esempio: vogliamo monitorare la crescita in termini di spazio occupato su disco di un file, potremmo quindi lanciare questi comandi in una shell:
1
:~$ while true; do ls -l file; done;
diciamo che di metodi per monitorare la crescita dello spazio occupato di un file nel tempo ce ne sono di altri, però non è questa la sede adatta per parlarne, ho fatto un esempio stupido per farti capire. Con questi comandi noi otterremo a schermo l’output di “ls -l” lanciato sul file prescelto in continuazione.
Hai visto che io qui non ho usato le quadre, perché? Perché per questo esempio non mi serve verificare una condizione, mi serve solo che il ciclo while si ripeta all’infinito, questo è fattibile con il comando built-in della shell “true” che forza il controllo di while sempre su “vero” e quindi il ciclo non si interrompe mai (se non quando premerò ctrl-c).
Un altra cosa da tenere in conto per il ciclo while è che l’uscita dal ciclo non è prevista solo in caso di mancata verifica delle condizioni, ma anche qualora uno qualsiasi dei comandi inseriti come azioni da eseguire all’interno del ciclo, esca con exit code diverso da 0.
For
Il ciclo for ci consente di ripetere un determinato blocco di codice per ogni elemento di una lista. L’estrema flessibilità della bash ci consente di definire come “lista” tanto il contenuto di un file quanto il risultato di un comando od una serie di comandi. Rifacendoci alla traduzione nel linguaggio parlato potremmo dire “per ogni elemento della lista fai queste azioni”.
Per esempio, vogliamo backuppare con tar una serie di cartelle la cui lista cambia ogni giorno su una partizione esterna, ad esempio un disco usb, la cosa più conveniente da fare è inserire la lista delle cartelle in un file di testo e darla in pasto ad un ciclo for:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/bin/bash DIRLIST="/home/alex/lista_dirs" BCKDIR="/mnt/disco_usb/backups" TARCMD=`which tar` TAROPTS="-cf" for DIR in $(cat $DIRLIST); do echo "Backing up $DIR into $BCKDIR/$DIR.tar" $TARCMD $TAROPTS $BCKDIR/$DIR.tar $DIR done
Come vedi anche per il ciclo for vale la “sintassi alternativa” col separatore “;”
For valorizza una variabile per ogni elemento della lista e poi esegue il codice incluso tra le direttive “do” e “done”, nel nostro esempio il ciclo for inizializzerà la variabile DIR valorizzandola con il primo elemento della lista creata dal comando “cat $DIRLIST”, esegue la echo e poi la compressione con tar, e poi riprende valorizzando la variabile con il secondo elemento della lista, e cosi via fino ad esaurire tutti gli elementi.
Tips: In questo esempio ho usato la sintassi “canonica”, mi riferisco alla parte che costituisce la lista, quindi quella dopo “in”; perchépersonalmente uso un altra sintassi che è perfettamente equivalente, solo che mi fa risparmiare caratteri
io questo stesso ciclo for l’avrei scritto cosi:1
for DIR in `cat $DIRLIST`; do ...
ti ricordi cosa sto usando? Sono gli apici inversi, visto che in questo caso la lista viene costituita dal risultato di un comando che la bash deve eseguire accorcio un pò la strada usando l’espasione dell’apice inverso appunto.
Cosa dobbiamo sapere su for: prima di tutto, come già detto, che vale sia la sintassi che abbiamo visto con while che quella che ho usato invece nell’esempio, cioè con il separatore di comandi “punto e virgola”.
La seconda cosa che dobbiamo sapere è che for usa un altro tipo di separatore per interpretare la lista. Questo separatore si chiama IFS ed è a tutti gli effetti una variabile d’ambiente di bash, di default è valorizzato con uno spazio bianco, quindi all’interno di una lista gli elementi verranno separati da uno spazio, da un “a capo”, o da un tab; ma noi potremmo voler separare gli elementi con un altro tipo di separatore – per esempio una virgola – quindi ci basterà inizializzare $IFS valorizzandola con una virgola, per dire a for che ogni elemento della lista sarà separato da quello seguente non da uno spazio bianco ma, appunto, da una virgola.
1 2 3 4 5 6 7
#!/bin/bash IFS=',' vals='/mnt,/var/lib/vmware/Virtual Machines,/dev,/proc,/sys,/tmp,/usr/portage,/var/tmp' for i in $vals; do echo $i; done unset IFS
Hint: Uno degli usi più comuni di for è quello di usarlo in accoppiata con il comando seq in modo da comporre la lista con una serie di numeri sequenziali, ad esempio: dobbiamo scaricare le iso della nostra distribuzione preferita, questa si compone di 3 cd e decidiamo di usare wget. Normalmente avremmo dovuto lanciare “wget http://www.sito.com/cd1.iso” aspettare la fine del download e lanciare un secondo wget, e poi ancora un terzo; in questo caso con for+seq demandiamo il lavoro alla bash lanciando un solo comando e dimenticandoci della cosa:
1 2 3 4 5
for i in `seq 1 3` do wget "http://www.sito.com/cd$i.iso" done
Until
Until è fratello di while e come ogni coppia di fratelli che si rispettino questi inevitabilmente si scornano in continuazione, until infatti funziona all’esatto opposto, la sua traduzione in italiano sarebbe “fintanto che $condizione non è vera esegui questi comandi”, l’esempio che abbiamo usato per while infatti possiamo scriverlo cosi:
1 2 3 4 5
until [ "$LETTERA" = "A" ]; do istruzioni done
Hai notato la differenza? Siccome qui stiamo usando until la condizione non deve più essere negata ed ho sostanzialmente detto alla bash “finchè $LETTERA è uguale ad a allora esegui istruzioni altrimenti esci dal ciclo”.
Solo con questi tre cicli abbiamo coperto gran parte delle cose che possiamo fare con bash, i cicli di controllo sono tutto quando si fanno script interattivi o complessi, perché servono a prevedere gli errori ed eventualmente ad indirizzare l’utente verso il corretto utilizzo dello stesso.
Tu hai già provato a scrivere qualcosa? Vuoi condividerlo o chiedere chiarimenti?