10.3. Contrôle de boucles

Commandes affectant le comportement des boucles

break, continue

Les commandes de contrôle de boucle break et continue [37] correspondent exactement à leur contre-partie dans d'autres langages de programmation. La commande break termine la boucle (en sort), alors que continue fait un saut à la prochaine itération (répétition) de la boucle, oubliant les commandes restantes dans ce cycle particulier de la boucle.

Exemple 10.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 10.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 10.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 10.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

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



[37] 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.