scripting Bash: i cicli negli script Bash

In ogni linguaggio di programmazione poter eseguire ripetutamente un blocco di istruzioni è una caratteristica fondamentale: ci fa risparmiare un sacco di tempo e di fatica. Bash non fa eccezione e ha tre cicli più uno: while, until, for e, come bonus, select. In questo articolo li vediamo uno per uno, con gli script di esempio.

Due famiglie di cicli

Prima un ripasso di informatica generale. I cicli si dividono in due grandi famiglie.

  • Cicli a numero di iterazioni predeterminato: so già in anticipo quante volte girano. Il classico esempio è il for.
  • Cicli a numero di iterazioni indeterminato: girano finché una condizione esterna è vera (o falsa), quindi non so in anticipo quante volte verranno eseguiti. Qui ci sono while e until.

Questi ultimi si dividono ancora in due sottofamiglie: quelli che prima eseguono il blocco e poi verificano la condizione (“fai questo finché la condizione è vera”) e quelli che prima verificano la condizione (“finché la condizione è vera, fai questo”). Nel secondo caso, se la condizione è già falsa all’inizio, il ciclo non viene mai eseguito. I cicli while e until di Bash appartengono a questa seconda specie.

Il ciclo while

La struttura è questa: finché il comando dopo while ha exit status 0 (cioè “è vero”), si esegue il blocco compreso fra do e done.

while comando
do
  altri comandi
done

Per l’esempio ci serve la variabile di sistema RANDOM, che contiene un numero casuale ogni volta che la leggiamo. Creiamo un ciclo che gira un numero casuale di volte. Salviamo questo script, per esempio come while.sh:

#!/bin/bash

while [ $RANDOM -ge 10000 ]
do
  echo "aspetta"
  sleep 1
done
echo "finito"

La condizione sta fra parentesi quadre, come abbiamo visto nella puntata sui costrutti decisionali: -ge significa “greater or equal”, maggiore o uguale. Il valore 10000 è completamente a caso. Il comando sleep mette in pausa lo script per un secondo, altrimenti i giri verrebbero eseguiti tutti di fila. Finché il numero casuale è maggiore o uguale a 10000 scrive “aspetta” e dorme; quando ne esce uno inferiore la condizione diventa falsa, il ciclo termina e viene stampato “finito”.

Si rende eseguibile e si lancia così:

chmod +x while.sh
./while.sh

Ogni volta si ottiene un risultato diverso, perché i numeri generati sono sempre diversi. Utilità pratica: nulla, ma mostra bene come funziona il ciclo.

Uscire da un ciclo: exit, break, continue

Ci sono modi per uscire da un ciclo senza aspettare che la condizione diventi falsa:

  • exit esce dallo script;
  • break esce dal ciclo e lo script prosegue con quello che c’è dopo;
  • continue salta il resto del giro e ricomincia dall’inizio del ciclo.

Per provarli usiamo due comandi molto particolari: true restituisce sempre vero e false restituisce sempre falso. Con while true abbiamo quindi un ciclo infinito, e dentro mettiamo la lettura di una scelta dell’utente per uscirne:

#!/bin/bash

while true
do
  echo "vuoi continuare? (s/n)"
  read sn
  [[ $sn = "s" ]] && continue
  [[ $sn = "n" ]] && break
done
echo "ciao"

Se rispondiamo s il ciclo continua, se rispondiamo n il break lo interrompe e lo script esce. Con qualunque altra risposta, come vedete, il ciclo ricomincia comunque.

Il ciclo until

La sintassi è identica a quella di while, ma al contrario: il blocco viene eseguito finché il comando dopo until restituisce falso.

until comando
do
  altri comandi
done

Per vedere bene come le condizioni esterne influenzano questo genere di cicli, facciamo uno script che scrive qualcosa finché una certa cartella non è vuota (nel video avevo scritto la condizione al contrario e me ne sono accorto al volo: qui è corretta, until esce quando l’elenco dei file è vuoto). Prima creiamo la cartella con un file dentro:

mkdir subdir
touch subdir/esempio

Poi lo script, che chiamo until.sh:

#!/bin/bash

until [ -z "$(ls subdir)" ]
do
  echo "la cartella non è vuota"
  sleep 1
done
echo "la cartella è vuota"

L’opzione -z dà vero se la stringa è vuota. Lanciamolo:

chmod +x until.sh
./until.sh

Lo script rileva che la cartella non è vuota e continua a scriverlo. Ora apriamo un secondo terminale e cancelliamo il file:

rm subdir/esempio

Dall’altra parte lo script smette di girare, perché ha trovato la cartella vuota. Non potevamo sapere in anticipo quante volte il ciclo sarebbe stato eseguito: dipendeva da un evento esterno.

Il ciclo for

Ultimo dei tre principali. Il for itera su una lista di elementi, e quindi è un ciclo deterministico: la lista è decisa prima di cominciare e non cambia durante l’esecuzione. È utilissimo per fare operazioni su elenchi di cose, per esempio sul contenuto di una cartella.

for variabile in lista
do
  comandi
done

Un esempio che mette insieme le cose già viste: usiamo l’espansione delle parentesi graffe per generare un range da 1 a 5.

#!/bin/bash

for i in {1..5}
do
  echo $i
done

Stampa i numeri da 1 a 5. Funziona con altri tipi di range, e si può aggiungere una pausa fra un giro e l’altro per vederlo girare meglio. Ecco un esempio che crea tre file nella cartella subdir, ciascuno con dentro un valore casuale:

#!/bin/bash

for i in {1..3}
do
  echo $RANDOM > subdir/file$i
  sleep 1
done

Se poi guardiamo dentro subdir troviamo i tre file, ognuno col suo numero casuale:

head subdir/file*

Il for si usa tantissimo anche sul risultato di un comando. Questo elenca tutto quello che c’è in /tmp:

for file in $(ls -d /tmp/*)
do
  echo $file
done

La variabile IFS

Il for spezza la lista in elementi usando il separatore contenuto nella variabile IFS (Internal Field Separator). Normalmente IFS contiene spazio, tab e a capo, quindi la lista viene separata in tutti questi punti. Se vogliamo cambiare questo comportamento, per esempio perché i nomi dei file contengono spazi, dobbiamo modificare IFS.

Il ciclo select

Il più uno: select è la cenerentola dei cicli di Bash, ma è comodo per costruire piccoli menù. Funziona in modo particolare. Prima di tutto si aspetta una variabile PS3 (non c’entra la PlayStation) che contiene il prompt da proporre all’utente. Poi, come il for, vuole una lista:

#!/bin/bash

PS3="scegli: "
select scelta in a b c
do
  echo "hai scelto $REPLY: $scelta"
done

Il menù viene generato automaticamente. Nella variabile REPLY finisce quello che l’utente ha digitato, mentre nella variabile del ciclo (qui scelta) finisce l’opzione corrispondente. Se digitiamo il numero di una voce, REPLY contiene il numero e scelta contiene la voce. Dato che il menù viene riproposto a ogni giro, serve una condizione di uscita, per esempio un break associato a una voce “esci”: altrimenti si resta intrappolati nel ciclo. Quando succede, l’unica è premere Ctrl+C, che uccide lo script e ci riporta al terminale.

Chiusura

Questi sono i cicli principali della shell Bash. Da qui in avanti useremo lo scripting Bash sempre più spesso, quindi se siete rimasti indietro con le puntate recuperatele: ogni episodio contiene le conoscenze necessarie per proseguire con i prossimi.

Link e risorse

Sostieni il canale

PER SOSTENERE IL CANALE BASTANO UN LIKE E UN’ISCRIZIONE!

questi link sono affiliati Amazon, acquistando i libri che ti consiglio sostieni il canale!