Les structures de contrôle

Rappel sur les codes de retour de programmes UNIX

Le noyau UNIX a prévu qu’un programme pouvait, après son exécution, transmettre une valeur à celui qui l’a lancé. En dernière instruction, un programme exécute exit(n), où n est la valeur à transmettre, et l’appelant exécute wait(&status), où status est une variable dans laquelle le noyau UNIX met la valeur transmise.

Ces valeurs ainsi transmises servent de code de retour des programmes: elles servent à donner une indication sur la façon dont s’est déroulée l’exécution du programme. Par convention, la valeur 0 signifie que l’exécution du programme s’est déroulée sans erreur. Une valeur différente de zéro signifie qu’il y a eu une erreur, la valeur étant un code choisi par le programmeur de l’application.

La gestion des codes de retour par le shell

Code de retour d’un programme

Quand le shell lance l’exécution d’un programme en avant plan, il attend la fin de l’exécution par un wait(&status). Il est ainsi prévenu du code de retour du programme. Le shell affecte ce code de retour à la variable ?, que l’on peut manipuler comme n’importe quelle variable.

Faisons un essai avec la commande grep, qui rend une indication d’erreur quand elle ne trouve pas la chaîne qu’on lui demande de rechercher :

> grep toto /etc/passwd
> echo $?
1
> grep bob /etc/passwd
bob:Kk43cgXNNuYSo:40:226:Serge Rouveyrol,113B,4879,:/users/bob:/bin/sh
> echo $?
0

Quand le shell lance l’exécution d’un programme en arrière-plan, il n’attend pas la fin de l’exécution et ne peut donc pas  connaître le code de retour. Dans ce cas, la valeur de la variable ? reflète la manière dont le lancement en programme d’arrière-plan s’est passé:

  • Si le lancement a pu être réalisé (les fichiers de redirection des entrées-sorties existaient bien, etc…), la variable ? vaut 0.
  • Si le lancement n’a pu être réalisé, la variable ? vaut 1.
> grep toto /etc/passwd &
12275
> echo $?
0
> grep toto < /jj
/jj: cannot open
> echo $?
1

Code de retour d’un pipe-line

Un pipe-line est un ensemble de commandes connectées par des pipes. Par exemple : > grep -i bob /etc/passwd | wc -l

Le code de retour d’un pipe-line est le code de retour de la dernière commande du pipe-line.

Code de retour d’une séquence de commandes

Le code de retour d’une séquence de commandes est le code de retour de la dernière commande de la séquence.

La structure &&

Deux pipe-lines peuvent être connectés par la structure &&. Cela signifie :

Si le premier pipe-line rend un code de retour de 0, le second est exécuté, sinon il n’est pas exécuté.

La structure ||

Deux pipe-lines peuvent être connectés par la structure ||. Cela signifie : Si le premier pipe-line rend un code de retour différent de 0, le second est exécuté, sinon il n’est pas exécuté.

Cette structure est bien adaptée à l’émission conditionnelle de messages d’erreurs.

Exemple :

grep $USER /etc/passwd || echo « $USER » « n’existe pas »

Remplace avantageusement :

if grep $USER /etc/passwd
then
:
else
echo "$USER" "n'existe pas"
fi

Le code de retour d’une structure || est le code de retour du dernier pipe-line exécuté.

La structure for

Attention il faut être rigoureux quant à la syntaxe : il faut aller à la ligne de la manière indiquée ci-dessous.

for nom in ( liste-de-mots )
do liste-de-pipe-lines
done

Dans liste-de-pipe-lines les commandes peuvent être séparées par des points virgules ou par des retour chariot.

La partie in (liste-de-mots ) est optionnelle. Si elle est omise, le shell la remplace par « $@ » (=ensemble des paramètres sur la ligne de commandes, chacun étant protégé par ‘’ ‘’).

Ecriture d’un for sur une seule ligne :

for nom in liste-de-mots ; do liste-de-pipe-lines ; done

La structure for itère l’exécution de liste-de-pipe-lines. A chaque pas de l’itération, la variable nom prend comme valeur le mot suivant de liste-de-mots.

Exemples :

for fichier in essai1.c essai2.c essai3.c
do
       echo je compile $fichier
       cc -c $fichier
done
for fichier in *.c; do cc -c $fichier; done

A l’aide de la commande seq (qui affiche une séquence de nombre)

for i in $(seq 10)
do
       echo $i
done

La structure if

Il y a plusieurs formes possibles : avec ou sans partie else, partie else combinée avec un if pour faire un elif (else if).

 if conditions
       then liste-de-pipe-lines
fi

ou:

if conditions
       then liste-de-pipe-lines
       else liste-de-pipe-lines
fi

ou:

if conditions
       then liste-de-pipe-lines
       elif liste-de-pipe-lines
             then liste-de-pipe-lines
             else liste-de-pipe-lines
fi

Ecriture d’un if sur une seule ligne:

if conditions ; then liste-de-pipe-lines ; fi
if conditions ; then liste-de-pipe-lines else ; liste-de-pipe-lines ; fi

La conditions après le if est exécutée, si elle rend un code de retour nul, la liste-de-pipe-lines de la partie then est exécutée, sinon, la liste-de-pipe-lines de la partie else est exécutée.

Dans la pratique, la liste-de-pipe-lines après le if est généralement réduite à une seule commande.

Exemple :

if [ $langue = "francais" ]
       Then echo bonjour
       else echo hello!
fi

La structure case

case mot in
liste-de-modèles ) liste-de-pipe-lines ;;
liste-de-modèles ) liste-de-pipe-lines ;;
esac

Dans les listes-de-modèles les modèles sont séparés par le caractère |

Les modèles sont les mêmes que ceux qui servent de modèles de noms de fichiers. Les métacaractères sont * ? [ ] avec la même sémantique. Un mot est dit conforme à une liste-de-modèles sil est conforme à un des modèles de la liste. Le caractère | a donc le sens d’un « ou ».

Le mot est comparé successivement à chaque liste-de-modèles. A la première liste-de-modèles pour laquelle le mot correspond, on exécute la liste-de-pipe-lines correspondante.

Exemples :

case $langue in
francais) echo Bonjour ;;
anglais) echo Hello ;;
espagnol) echo Buenos Dias ;;
esac
case $param in
0|1|2|3|4|5|6|7|8|9 ) echo $param est un chiffre ;;
[0-9]* ) echo $param est un nombre ;;
[a-zA-Z]* ) echo $param est un nom ;;
* ) echo $param de type non prevu ;;
esac

La structure while

while liste-de-pipe-lines
     do liste-de-pipe-lines
done

La structure while itère :

  • exécution de la liste-de-pipe-lines du while
  • si celle ci rend un code de retour nul il y a exécution de la liste-de-pipe-lines du do,
  • sinon la boucle se termine.

De la même manière que pour la structure if, la liste-de-pipe-lines de la partie while est généralement réduite à une seule commande.

Exemples :

L’exemple ci-dessous réalise une boucle de 10 itérations :

i=1
while test $i -le 10
       do
       ...
       i=`expr $i + 1`
done

La structure until

until conditions
       do liste-de-pipe-lines
done

La structure until itère l’exécution de la liste-de-pipe-lines du until ; si celle-ci rend un code de retour nul, il y a exécution de la liste-de-pipe-lines du do, sinon la boucle se termine.

De la même manière que pour la structure if, la liste-de-pipe-lines de la partie until est généralement réduite à une seule commande.

Break et continue

Il est possible de sortir d’une boucle for, while ou until à l’aide de la commande interne break. (Pour une bonne logique et compréhension de votre programmation ceci est fortement déconseillé). Il est possible d’indiquer un paramètre numérique permettant de sortir de plusieurs boucles à la fois.

Exemple :

while true
       do
             read ligne
             if [ $ligne = stop ]
                    then
                           break
                    else
                           ...
             fi
done

Redirection des entrées-sorties

Il est possible de rediriger les entrées-sorties d’une structure conditionnelle (if ou case) ou de boucle (for, while, until).

Exemple :

for i in fic1 fic2
       do
       cc -c $i.c
done 2> erreurs

Chaque commande peut avoir ses entrées-sorties redirigées indépendamment :

for i in fic1 fic2
       do
       echo "Je traite $i" > /dev/tty
       cc -c $i.c
done 2> erreurs

Pipe

Il est possible de piper les mêmes structures soit en entrée soit en sortie.

Exemple :

cat noms | while read nom
do
       sort $nom -o $nom
done

Exécution en arrière-plan

Il est possible d’exécuter les structures de contrôle en arrière-plan.

for i in fic1 fic2
       do
       cc -c $i.c
done &

Attention, quand on redirige ou que l’on pipe les entrées-sorties ou que l’on met dans l’arrière-plan une telle structure, celle-ci est exécutée dans un sous-shell. Attention donc si on utilise des variables :

nbligne=0
grep hello data | while read ligne
       do
       nbligne=`expr $nbligne + 1`
done

A la fin, la variable nbligne vaut toujours 0 ! Il y a eu incrémentation de la variable nbligne du sous-shell créé pour exécuter le while puis disparition de cette variable lors de la fin du

while qui a entraîné la mort du sous-shell.

Print Friendly, PDF & Email