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
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 s’il 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.