11.3. Contrôle des boucles

Tournez cent tours, tournez mille tours,

Tournez souvent et tournez toujours...

-- Verlaine, « Chevaux de bois »

Commandes affectant le comportement des boucles

break, continue

Les commandes de contrôle de boucle break et continue [52] correspondent exactement à leur contre-partie dans d'autres langages de programmation. La commande break termine la boucle (en sort), tandis que continue provoque un saut jusqu'à la prochaine itération de la boucle, dans tenir compte des commandes qui restent dans ce cycle particulier de la boucle.

Exemple 11.20. Effets de break et continue dans une boucle

#!/bin/bash

LIMITE=19  # Limite haute.

echo
echo "Affiche les nombres de 1 à 20 (mais pas 3 et 11)."

a=0

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

 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # Exclut 3 et 11.
 then
   continue  # Continue avec une nouvelle itération de la boucle.
 fi

 echo -n "$a " # Ceci ne s'exécutera pas pour 3 et 11.
done 

# Exercice :
# Pourquoi la boucle affiche-t'elle jusqu'au 20 ?

echo; echo

echo "Affiche les nombres de 1 à 20, mais quelque chose se passe après 2."

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

# Même boucle, mais en substituant 'continue' avec 'boucle'.

a=0

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

 if [ "$a" -gt 2 ]
 then
   break  # Ne continue pas le reste de la boucle.
 fi

 echo -n "$a "
done

echo; echo; echo

exit 0

La commande break peut de façon optionnelle prendre un paramètre. Un simple break termine seulement la boucle interne où elle est incluse mais un break N sortira de N niveaux de boucle.

Exemple 11.21. Sortir de plusieurs niveaux de boucle

#!/bin/bash
# break-levels.sh: Sortir des boucles.

# "break N" sort de N niveaux de boucles.

for boucleexterne in 1 2 3 4 5
do
  echo -n "Groupe $boucleexterne:   "

  # ----------------------------------------------------------
  for boucleinterne in 1 2 3 4 5
  do
    echo -n "$boucleinterne "

    if [ "$boucleinterne" -eq 3 ]
    then
      break  # Essayez   break 2   pour voir ce qui se passe.
             # (Sort des boucles internes et externes.)
    fi
  done
  # ----------------------------------------------------------

  echo
done  

echo

exit 0

La commande continue, similaire à break, prend un paramètre de façon optionnelle. Un simple continue court-circuite l'itération courante et commence la prochaine itération de la boucle dans laquelle elle se trouve. Un continue N termine toutes les itérations à partir de son niveau de boucle et continue avec l'itération de la boucle N niveaux au-dessus.

Exemple 11.22. Continuer à un plus haut niveau de boucle

#!/bin/bash
# La commande "continue N" continue jusqu'au niveau de boucle N.

for exterieur in I II III IV V           # Boucle extérieure
do
  echo; echo -n "Groupe $exterieur : "

  # ----------------------------------------------------------
  for interieur in 1 2 3 4 5 6 7 8 9 10  # Boucle intérieure
  do

    if [ "$interieur" -eq 7 ]
    then
      continue 2  #  Continue la boucle au deuxième niveau, c'est-à-dire la
                  #+ boucle extérieure.
                  #  Remplacez la ligne ci-dessus avec un simple "continue"
                  #  pour voir le comportement normal de la boucle.
    fi  

    echo -n "$interieur "  # 7 8 9 10 ne s'afficheront jamais.
  done  
  # ----------------------------------------------------------

done

echo; echo

# Exercice :
# Parvenir à un emploi utile pour "continue N" dans un script.

exit 0

Exemple 11.23. Utiliser continue N dans une tâche courante

# Albert Reiner donne un exemple pour l'utilisation de "continue N" :
# -------------------------------------------------------------------

#  Supposez que j'ai un grand nombre de jobs à exécuter, avec des données à 
#+ traiter dans des fichiers dont le nom correspond à un certain modèle
#+ et qui font tous partie d'un même répertoire.
#+ Plusieurs machines accèdent à ce répertoire et je veux distribuer le
#+ travail entre ces différentes machines. Alors, j'exécute ce qui suit
#+ avec nohup sur toutes les machines :

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

#  Les détails, en particulier le sleep N, sont spécifiques à mon
#+ application mais le modèle général est :

while true
do
  for job in {modèle}
  do
    {job déjà terminé ou en cours d'exécution} && continue
    {indiquez que ce job est en cours d'exécution, exécutez le job, indiquez-le comme terminé}
    continue 2
  done
  break        # Ou quelque chose comme `sleep 600' pour éviter la fin.
done

#  De cette façon, le script s'arrêtera seulement quand il n'y aura plus de jobs
#+ à faire (en incluant les jobs qui ont été ajoutés à l'exécution). À travers
#  l'utilisation de fichiers verrous appropriés, il peut être exécuté sur
#+ plusieurs machines en même temps sans duplication des calculs [qui ont
#+ demandé quelques heures dans mon cas, donc je veux vraiment éviter ceci].
#+ De plus, comme la recherche recommence toujours au début, vous pouvez
coder des priorités dans les noms des fichiers. Bien sûr, vous pouvez le
#+ faire sans `continue 2' mais alors vous devrez vérifier réellement si
#+ un job s'est terminé (pour rechercher immédiatement le prochain
#+ job) ou non (auquel cas nous arrêtons le programme ou l'endormissons
#+ pour un long moment le temps que vous cherchions un autre job)..

[Attention]

Attention

L'expression continue N est difficile à comprendre et complexe à utiliser dans tous les contextes. Il est probablement raisonnable de l'éviter.



[52] Ce sont des commandes intégrées du shell, alors que les autres commandes de boucle, telles que while et case, sont des mots clés.