10.3. Controllo del ciclo

Comandi inerenti al comportamento del ciclo

break, continue

I comandi di controllo del ciclo break e continue [1] corrispondono esattamente ai loro analoghi degli altri linguaggi di programmazione. Il comando break interrompe il ciclo (esce), mentre continue provoca il salto all'iterazione (ripetizione) successiva, tralasciando tutti i restanti comandi di quel particolare passo del ciclo.

Esempio 10-20. Effetti di break e continue in un ciclo

#!/bin/bash

LIMITE=19  # Limite superiore

echo
echo "Visualizza i numeri da 1 fino a 20 (saltando 3 e 11)."

a=0

while [ $a -le "$LIMITE" ]
do
 a=$(($a+1))

 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  #  Esclude  3 e 11.
 then
   continue    #  Salta la parte restante di questa particolare
               #+ iterazione del ciclo.
 fi

 echo -n "$a " # Non visualizza 3 e 11.
done

# Esercizio:
# Perché il ciclo visualizza fino a 20?

echo; echo

echo Visualizza i numeri da 1 a 20, ma succede qualcosa dopo il 2.

##################################################################

# Stesso ciclo, ma sostituendo 'continue' con 'break'.

a=0

while [ "$a" -le "$LIMITE" ]
do
 a=$(($a+1))

 if [ "$a" -gt 2 ]
 then
   break  # Salta l'intero ciclo.
 fi

 echo -n "$a "
done

echo; echo; echo

exit 0

Il comando break può avere un parametro. Il semplice break conclude il ciclo in cui il comando di trova, mentre un break N interrompe il ciclo di livello N.

Esempio 10-21. Interrompere un ciclo ad un determinato livello

#!/bin/bash
# break-levels.sh: Interruzione di cicli.

# "break N" interrompe i cicli al livello N.

for cicloesterno in 1 2 3 4 5
do
  echo -n "Gruppo $cicloesterno:   "
  
  #---------------------------------
  for ciclointerno in 1 2 3 4 5
  do
    echo -n "$ciclointerno "

    if [ "$ciclointerno" -eq 3 ]
    then
      break  # Provate break 2 per vedere il risultato.
             # ("Interrompe" entrambi i cicli, interno ed esterno).
    fi
  done
  #---------------------------------

  echo
done  

echo

exit 0

Il comando continue, come break, può avere un parametro. Un semplice continue interrompe l'esecuzione dell'iterazione corrente del ciclo e dà inizio alla successiva. continue N salta tutte le restanti iterazioni del ciclo in cui si trova e continua con l'iterazione successiva del ciclo N di livello superiore.

Esempio 10-22. Proseguire ad un livello di ciclo superiore

#!/bin/bash
# Il comando "continue N", continua all'Nsimo livello.

for esterno in I II III IV V           # ciclo esterno
do
  echo; echo -n "Gruppo $esterno: "

  # ----------------------------------------------------
  for interno in 1 2 3 4 5 6 7 8 9 10  # ciclo interno
  do

    if [ "$interno" -eq 7 ]
    then
      continue 2  #  Continua al ciclo di 2 livello, cioè il
                  #+ "ciclo esterno". Modificate la riga precedente
                  #+ con un semplice "continue" per vedere il
                  #+ consueto comportamento del ciclo.
    fi

    echo -n "$interno "  # 7 8 9 10 non verranno mai visualizzati.
  done
  # ----------------------------------------------------

done

echo; echo

# Esercizio:
# Trovate un valido uso di "continue N" in uno script.

exit 0

Esempio 10-23. Uso di continue N in un caso reale

# Albert Reiner fornisce un esempio di come usare "continue N":
# ---------------------------------------------------------

#  Supponiamo di avere un numero elevato di job che devono essere
#+ eseguiti, con tutti i dati che devono essere trattati contenuti in un
#+ file, che ha un certo nome ed è inserito in una data directory. 
#+ Ci sono diverse macchine che hanno accesso a questa directory e voglio 
#+ distribuire il lavoro su tutte queste macchine. Per far questo, 
#+ solitamente, utilizzo nohup con il codice seguente su ogni macchina:

while true
do
  for n in .iso.*
  do
    [ "$n" = ".iso.opts" ] && continue
    beta=${n#.iso.}
    [ -r .Iso.$beta ] && continue
    [ -r .lock.$beta ] && sleep 10 && continue
    lockfile -r0 .lock.$beta || continue
    echo -n "$beta: " `date`
    run-isotherm $beta
    date
    ls -alF .Iso.$beta
    [ -r .Iso.$beta ] && rm -f .lock.$beta
    continue 2
  done
  break
done

#  I dettagli, in particolare sleep N, sono specifici per la mia
#+ applicazione, ma la struttura generale è:

while true
do
  for job in {modello}
  do
    {job già terminati o in esecuzione} && continue
    {marca il job come in esecuzione, lo esegue, lo marca come eseguito}
    continue 2
  done
  break        # Oppure `sleep 600' per evitare la conclusione.
done

#  In questo modo lo script si interromperà solo quando non ci saranno
#+ più job da eseguire (compresi i job che sono stati aggiunti durante
#+ il runtime). Tramite l'uso di appropriati lockfile può essere
#+ eseguito su diverse macchine concorrenti senza duplicazione di
#+ calcoli [che, nel mio caso, occupano un paio d'ore, quindi è
#+ veramente il caso di evitarlo]. Inoltre, poiché la ricerca
#+ ricomincia sempre dall'inizio, è possibile codificare le priorità
#+ nei nomi dei file. Naturalmente, questo si potrebbe fare senza
#+ `continue 2', ma allora si dovrebbe verificare effettivamente se
#+ alcuni job sono stati eseguiti (in questo caso dovremmo cercare
#+ immediatamente il job successivo) o meno (in quest'altro dovremmo
#+ interrompere o sospendere l'esecuzione per molto tempo prima di
#+ poter verificare un nuovo job).

Attenzione

Il costrutto continue N è difficile da capire e complicato da usare, in modo significativo, in qualsiasi contesto. Sarebbe meglio evitarlo.

Note

[1]

Sono builtin di shell, mentre altri comandi di ciclo, come while e case, sono parole chiave.