Les scripts shell - Instructions de contrôle

Objectifs pédagogiques

Dans ce chapitre, vous allez apprendre à utiliser des structures de tests, des structures conditionnelles et des boucles.

Tester des variables, des fichiers ;
Utiliser une structure conditionnelle ;
Faire des boucles while, until, select, for.

script, shell, bash, structure, boucle

Connaissances :
Niveau technique :
Temps de lecture : 20 minutes

Lorsqu’elles se terminent, toutes les commandes exécutées par le shell renvoient un code de retour (également appelé code de statut ou de sortie).

Les tests

  • Si la commande s’est correctement exécutée, la convention veut que le code de statut ait pour valeur zéro.

  • Si la commande a rencontré un problème lors de son exécution, son code de statut aura une valeur différente de zéro.
    Les raisons peuvent être nombreuses : manque de droits d’accès, absence de fichier, saisie incorrecte, etc.

Il faut se référer au manuel de la commande man commande pour connaître les différentes valeurs du code de retour prévues par les développeurs.

Le code de retour n’est pas visible directement, mais est enregistré dans une variable spéciale : $?.

$ mkdir repertoire
$ echo $?
0
$ mkdir /repertoire
mkdir: impossible de créer le répertoire
$ echo $?
1
$ commande_qui_n_existe_pas
commande_qui_n_existe_pas : commande introuvable
$ echo $?
127

L’affichage du contenu de la variable $? avec la commande echo se fait immédiatement après la commande que l’on souhaite évaluer car cette variable est mise à jour après chaque exécution d’une commande, d’une ligne de commandes ou encore d’un script.

Puisque la valeur de $? change après chaque exécution de commande, il est préférable de mettre sa valeur dans une variable qui sera utilisée par la suite, pour un test ou afficher un message.

$ ls fichier_absent
ls: impossible d'accéder à 'fichier_absent': Aucun fichier ou dossier de ce type
$ RETOUR=$?
$ echo $?
0
$ echo $RETOUR
2

Il est également possible de créer des codes de retour dans un script. Il suffit pour cela d’ajouter un argument numérique à la commande exit.

$ bash        # pour éviter d'être déconnecté après le « exit 2 »
$ exit 2
$ echo $?
2

Outre la bonne exécution d’une commande, le shell offre la possibilité d’exécuter des tests sur de nombreux motifs :

  • Fichiers : existence, type, droits, comparaison ;

  • Chaînes de caractères : longueur, comparaison ;

  • Numériques entiers : valeur, comparaison.

Le résultat du test :

  • $?=0 : le test s’est correctement exécuté et est vrai ;

  • $?=1 : le test s’est correctement exécuté et est faux ;

  • $?=2 : le test ne s’est pas correctement exécuté.

Tester le type d’un fichier

Syntaxe de la commande test pour un fichier
test [-d|-e|-f|-L] fichier
Table 1. Options de la commande test sur les fichiers
Option Observation

-e

Teste si le fichier existe

-f

Teste si le fichier existe et est de type normal

-d

Teste si le fichier existe et est de type répertoire

-L

Teste si le fichier existe et est de type lien symbolique

-b

Teste si le fichier existe et est de type spécial mode bloc

-c

Teste si le fichier existe et est de type spécial mode caractère

-p

Teste si le fichier existe et est de type tube

-S

Teste si le fichier existe et est de type socket

-t

Teste si le fichier existe et est de type terminal

-r

Teste si le fichier existe et est accessible en lecture

-w

Teste si le fichier existe et est accessible en écriture

-x

Teste si le fichier existe et est est exécutable

-g

Teste si le fichier existe et est a un SGID positionné

-u

Teste si le fichier existe et est a un SUID positionné

-s

Teste si le fichier existe et est non vide (taille > 0 octets)

Comparer deux fichiers

La commande test peut également comparer des fichiers :

Syntaxe de la commande test pour la comparaison de fichiers
test fichier1 [-nt|-ot|-ef] fichier2
Table 2. Options de la commande test pour la comparaison de fichiers
Option Observation

-nt

Teste si le premier fichier est plus récent que le second

-ot

Teste si le premier fichier est plus ancien que le second

-ef

Teste si le premier fichier est un lien physique du second

Tester une variable

Syntaxe de la commande test pour les variables
test [-z|-n] $variable
Table 3. Options de la commande test pour les variables
Option Observation

-z

Teste si la variable est vide

-n

Teste si la variable n’est pas vide

Tester une chaîne de caractères

Syntaxe de la commande test pour les chaînes de caractères
test chaîne1 [=|!=] chaîne2

Exemple :

$ test "$var" = "Hello world !"
$ echo $?
0
Table 4. Options de la commande test pour les variables
Option Observation

=

Teste si la première chaîne de caractères est égale à la seconde

!=

Teste si la première chaîne de caractères est différente de la seconde

<

Teste si la première chaîne de caractères est avant la seconde dans l’ordre ASCII

>

Teste si la première chaîne de caractères est après la seconde dans l’ordre ASCII

Comparaison de numériques entiers

Syntaxe de la commande test pour les entiers
test "num1" [-eq|-ne|-gt|-lt] "num2"

Exemple :

$ var=1
$ test "$var" -eq "1"
$ echo $?
0
$ var=2
$ test "$var" -eq "1"
$ echo $?
1
Table 5. Options de la commande test pour les entiers
Option Observation

-eq

Teste si le premier nombre est égal au second

-ne

Teste si le premier nombre est différent au second

-gt

Teste si le premier nombre est supérieur au second

-lt

Teste si le premier nombre est inférieur au second

Les numériques étant traités par le shell comme des caractères (ou chaînes de caractères) classiques, un test sur un caractère peut renvoyer le même résultat qu’il soit traité en tant que numérique ou non.

$ test "1" = "1"
$ echo $?
0
$ test "1" -eq "1"
$ echo $?
0

Mais le résultat du test n’aura pas la même signification :

  • Dans le premier cas, il signifiera que les deux caractères ont la même valeur dans la table ASCII.

  • Dans le second cas, il signifiera que les deux nombres sont égaux.

Combinaison de tests

La combinaison de tests permet d’effectuer plusieurs tests en une seule commande. Il est possible de tester plusieurs fois le même argument (fichier, chaîne ou numérique) ou des arguments différents.

test option1 argument1 [-a|-o] option2 argument 2
$ ls -lad /etc
drwxr-xr-x 142 root root 12288 sept. 20 09:25 /etc
$ test -d /etc -a -x /etc
$ echo $?
0
Table 6. Options de combinaison de tests
Option Observation

-a

ET : Le test sera vrai si tous les motifs le sont.

-o

OU : Le test sera vrai si au moins un motif l’est.

Les tests peuvent ainsi être groupé avec des parenthèses ( ) pour leur donner une priorité.

(TEST1 -a TEST2) -a TEST3

Le caractère ! permet d’effectuer le test inverse de celui demandé par l’option :

$ test -e /fichier   # vrai si fichier existe
$ ! test -e /fichier # vrai si fichier n'existe pas

Les opérations numériques

La commande expr effectue une opération avec des entiers numériques.

Syntaxe de la commande expr
expr num1 [+] [-] [\*] [/] [%] num2

Exemple :

Exemple d’utilisation de la commande expr
$ expr 2 + 2
4

Attention à bien encadrer le signe d’opération par une espace, vous obtiendrez un message d’erreur en cas d’oubli.
Dans le cas d’une multiplication, le caractère joker * est précédé par \ pour éviter une mauvaise interprétation.

Table 7. Opérateurs de la commande expr
Opérateur Observation

+

Addition

-

Soustraction

\*

Multiplication

/

Quotient de la division

%

Modulo de la division

La commande typeset

La commande typeset -i déclare une variable comme un entier.

Exemple :

Exemple d’utilisation de la commande typeset
$ typeset -i var1
$ var1=1+1
$ var2=1+1
$ echo $var1
2
$ echo $var2
1+1

La commande let

La commande let teste si un caractère est numérique.

Exemple :

Exemple d’utilisation de la commande let
$ var1="10"
$ var2="AA"
$ let $var1
$ echo $?
0
$ let $var2
$ echo $?
1

La commande let ne retourne pas un code retour cohérent lorsqu’elle évalue le numérique 0.

$ let 0
$ echo $?
1

La commande let permet également d’effectuer des opérations mathématiques :

$ let var=5+5
$ echo $var
10

let peut être substituée par $(( )).

$ echo $((5+2))
7
$ echo $((5*2))
10
$ var=$((5*3))
$ echo $var
15

Structures conditionnelles

Si la variable $? permet de connaître le résultat d’un test ou de l’exécution d’une commande, elle ne peut qu’être affichée et n’a aucune incidence sur le déroulement d’un script.

Mais nous pouvons nous en servir dans une condition. Si le test est bon alors je fais cette action sinon je fais telle autre action.

Syntaxe de l’alternative conditionnelle if
if commande
then
    commande si $?=0
else
    commande si $?!=0
fi

La commande placée après le mot if peut être n’importe quelle commande puisque c’est son code de retour ($?) qui sera évalué. Il est souvent pratique d’utiliser la commande test pour définir plusieurs actions en fonction du résultat de ce test (fichier existe, variable non vide, droits en écriture positionnés).

Utiliser une commande classique (mkdir, tar, …) permet de définir les actions à effectuer en cas de succès ou les messages d’erreur à afficher en cas d’échec.

Exemples d’utilisation de la structure conditionnelle if
if test -e /etc/passwd
then
    echo "Le fichier existe"
else
    echo "Le fichier n'existe pas"
fi

if mkdir rep
then
    cd rep
fi

La commande test peut être substituée par [[ condition_de_test ]].

Ainsi :

if test -e /etc/passwd
then
    echo "Le fichier existe"
else
    echo "Le fichier n'existe pas"
fi

peut devenir :

if [[ -e /etc/passwd ]]
then
    echo "Le fichier existe"
else
    echo "Le fichier n'existe pas"
fi

Si le bloc else commence par une nouvelle structure if, il est possible de fusionner else et if :

[…]
else
  if test -e /etc/
[…]

[…]
# est équivalent à
elif test -e /etc
[…]

La structure if / then / else / fi évalue la commande placée après if :

  • Si le code retour de cette commande est 0 (vrai) le shell exécutera les commandes placées après then ;

  • Si le code retour est différent de 0 (faux) le shell exécutera les commandes placées après else.

Le bloc else est facultatif.

Il existe un besoin d’effectuer certaines actions uniquement si l’évaluation de la commande est vraie et n’avoir rien à faire si elle est fausse.

Le mot fi ferme la structure.

Lorsqu’il n’y a qu’une seule commande à exécuter dans le bloc then, il est possible d’utiliser une syntaxe plus simple.

La commande à exécuter si $? est vrai est placée après && tandis que la commande à exécuter si $? est faux est placée après || (facultatif).

Par exemple :

$ test -e /etc/passwd && echo "Le fichier existe" || echo "Le fichier n'existe pas"
$ mkdir repert && echo "Le répertoire est créé"

Il est possible d’évaluer et de remplacer une variable avec une structure plus légère que if.

Cette syntaxe met en œuvre les accolades :

  • Affiche une valeur de remplacement si la variable est vide :

${variable:-valeur}
  • Affiche une valeur de remplacement si la variable n’est pas vide :

${variable:+valeur}
  • Affecte une nouvelle valeur à la variable si elle est vide :

${variable:=valeur}

Exemples :

$ nom=""
$ echo ${nom:-linux}
linux
$ echo $nom

$ echo ${nom:=linux}
linux
$ echo $nom
linux
$ echo ${nom:+tux}
tux
$ echo $nom
linux

Structure alternative conditionnelle case

Une succession de structures if peut vite devenir lourde et complexe. Lorsqu’elle concerne l’évaluation d’une même variable, il est possible d’utiliser une structure conditionnelle à plusieurs branches. Les valeurs de la variable peuvent être précisées ou appartenir à une liste de possibilités.

Les caractères jokers sont utilisables.

La structure case … in / esac évalue la variable placée après case et la compare aux valeurs définies.

À la première égalité trouvée, les commandes placées entre ) et ;; sont exécutées.

La variable évaluée et les valeurs proposées peuvent être des chaînes de caractères ou des résultats de sous-exécutions de commandes.

Placé en fin de structure, le choix * indique les actions à exécuter pour toutes les valeurs qui n’ont pas été précédemment testées.

Syntaxe de l’alternative conditionnelle case
case $variable in
  valeur1)
    commandes si $variable = valeur1
    ;;
  valeur2)
    commandes si $variable = valeur2
    ;;
  [..]
  *)
    commandes pour toutes les valeurs de $variable != de valeur1 et valeur2
    ;;
esac

Lorsque la valeur est sujette à variation, il est conseillé d’utiliser les caractères jokers [] pour spécifier les possibilités :

[Oo][Uu][Ii])
  echo "oui"
  ;;

Le caractère | permet aussi de spécifier une valeur ou une autre :

"oui" | "OUI")
  echo "oui"
  ;;

Boucles

Le shell bash permet l’utilisation de boucles. Ces structures permettent l’exécution d’un bloc de commandes plusieurs fois (de 0 à l’infini) selon une valeur définie statiquement, dynamiquement ou sur condition :

  • while

  • until

  • for

  • select

Quelle que soit la boucle utilisée, les commandes à répéter se placent entre les mots do et done.

La structure boucle conditionnelle while

La structure while / do / done évalue la commande placée après while.

Si cette commande est vrai ($? = 0), les commandes placées entre do et done sont exécutées. Le script retourne ensuite au début évaluer de nouveau la commande.

Lorsque la commande évaluée est fausse ($? != 0), le shell reprend l’exécution du script à la première commande après done.

Syntaxe de la structure boucle conditionnelle while
while commande
do
  commande si $? = 0
done

Exemple :

Exemple d’utilisation de la structure conditionnelle while
while test -e /etc/passwd
do
  echo "Le fichier existe"
done

Si la commande évaluée ne varie pas, la boucle sera infinie et le shell n’exécutera jamais les commandes placées à la suite du script. Cela peut être volontaire, mais aussi être une erreur.

Il faut donc faire très attention à la commande qui régit la boucle et trouver un moyen d’en sortir.

Pour sortir d’une boucle while, il faut faire en sorte que la commande évaluée ne soit plus vraie, ce qui n’est pas toujours possible.

Il existe des commandes qui permettent de modifier le comportement d’une boucle :

  • exit

  • break

  • continue

La commande exit

La commande exit termine l’exécution du script.

Syntaxe de la commande exit
exit [n]

Exemple :

Exemple d’utilisaton de la commande exit
$ bash        # pour éviter d'être déconnecté après le « exit 1 »
$ exit 1
$ echo $?
1

La commande exit met fin au script immédiatement. Il est possible de préciser le code de retour du script en le précisant en argument (de 0 à 255). Sans argument précisé, c’est le code de retour de la dernière commande du script qui sera transmise à la variable $?.

Cette commande est utile dans le cas d’un menu proposant la sortie du script dans les choix possibles.

La commande break / continue

La commande break permet d’interrompre la boucle en allant à la première commande après done.

La commande continue permet de relancer la boucle en revenant à la première commande après do.

while test -d /
do
  echo "Voulez-vous continuer ? (oui/non)"
  read rep
  test $rep = "oui" && continue
  test $rep = "non" && break
done

Les commandes true / false

La commande true renvoie toujours vrai tandis que la commande false renvoie toujours faux.

$ true
$ echo $?
0
$ false
$ echo $?
1

Utilisées comme condition d’une boucle, elles permettent soit d’exécuter une boucle infinie soit de désactiver cette boucle.

Exemple :

while true
do
  echo "Voulez-vous continuer ? (oui/non)"
  read rep
  test $rep = "oui" && continue
  test $rep = "non" && break
done

La structure boucle conditionnelle until

La structure until / do / done évalue la commande placée après until.

Si cette commande est fausse ($? != 0), les commandes placées entre do et done sont exécutées. Le script retourne ensuite au début évaluer de nouveau la commande.

Lorsque la commande évaluée est vraie ($? = 0), le shell reprend l’exécution du script à la première commande après done.

Syntaxe de la structure boucle conditionnelle until
until commande
do
  commande si $? != 0
done

Exemple :

Exemple d’utilisation de la structure conditionnelle until
until test -e /etc/passwd
do
  echo "Le fichier n'existe pas"
done

La structure choix alternatif select

La structure select / do / done permet d’afficher rapidement un menu avec plusieurs choix et une demande de saisie.

À chaque élément de la liste correspond un choix numéroté. À la saisie, la valeur choisie est affectée à la variable placée après select (créée à cette occasion).

Elle exécute ensuite les commandes placées entre do et done avec cette valeur.

  • La variable PS3 contient le message d’invitation à entrer le choix ;

  • La variable REPLY va permettre de récupérer le numéro du choix.

Il faut une commande break pour sortir de la boucle.

La structure select est très utile pour de petits menus simples et rapides. Pour personnaliser un affichage plus complet, il faudra utiliser les commandes echo et read dans une boucle while.

Syntaxe de la structure boucle conditionnelle select
PS3="Votre choix :"
select variable in var1 var2 var3
do
  commandes
done

Exemple :

Exemple d’utilisation de la structure conditionnelle select
PS3="Votre choix : "
select choix in café thé chocolat
do
  echo "Vous avez choisi le $REPLY : $choix"
done

ce qui donne à l’exécution :

1) Café
2) Thé
3) Chocolat
Votre choix : 2
Vous avez choisi le choix 2 : thé
Votre choix :

La structure boucle sur liste de valeurs for

La structure for / do / done affecte le premier élément de la liste à la variable placée après for (créée à cette occasion).

Elle exécute ensuite les commandes placées entre do et done avec cette valeur. Le script retourne ensuite au début affecter l’élément suivant de la liste à la variable de travail.

Lorsque le dernier élément a été utilisé, le shell reprend l’exécution à la première commande après done.

Syntaxe de la structure boucle sur liste de valeurs for
for variable in liste
do
  commandes
done

Exemple :

Exemple d’utilisation de la structure conditionnelle for
for fichier in /home /etc/passwd /root/fic.txt
do
  file $fichier
done

Toute commande produisant une liste de valeurs peut être placée à la suite du in à l’aide d’une sous-exécution.

  • Avec la variable IFS contenant $' \t\n', la boucle for prendra chaque mot du résultat de cette commande comme liste d’éléments sur laquelle boucler .

  • Avec la variable IFS contenant $'\t\n' (c’est-à-dire sans espace), la boucle for prendra chaque ligne du résultat de cette commande.

Cela peut être les fichiers d’un répertoire. Dans ce cas, la variable prendra comme valeur chacun des mots des noms des fichiers présents :

for fichier in $(ls -d /tmp/*)
do
  echo $fichier
done

Cela peut être un fichier. Dans ce cas, la variable prendra comme valeur chaque mot contenu dans le fichier parcouru, du début à la fin :

$ cat mon_fichier.txt
première ligne
seconde ligne
troisième ligne
$ for LIGNE in $(cat mon_fichier.txt); do echo $LIGNE; done
première
ligne
seconde
ligne
troisième
ligne

Pour lire ligne par ligne un fichier, il faut modifier la valeur de la variable d’environnement IFS.

$ IFS=$'\t\n'
$ for LIGNE in $(cat mon_fichier.txt); do echo $LIGNE; done
première ligne
seconde ligne
troisième ligne

Tester vos connaissances

Toute commande renvoie obligatoirement un code de retour à la fin de son exécution :
Vrai
Faux

Un code de retour à 0 indique une erreur d’exécution :
Vrai
Faux

Le code de retour est stocké dans la variable $@ :
Vrai
Faux

La commande test permet de :
Tester le type d’un fichier
Tester une variable
Comparer des numériques
Comparer le contenu de 2 fichier

La commande expr :
Concatène 2 chaînes de caractères
Effectue des opérations mathématiques
Affiche du texte à l’écran

La syntaxe de la structure conditionnelle ci-dessous vous semble-t’elle correcte ? Expliquez pourquoi.
Vrai
Faux

if commande
    commande si $?=0
else
    commande si $?!=0
fi

Que sigifie la syntaxe suivante : ${variable:=valeur}
Affiche une valeur de remplacement si la variable est vide
Affiche une valeur de remplacement si la variable n’est pas vide
Affecte une nouvelle valeur à la variable si elle est vide

La syntaxe de la structure alternative conditionnelle ci-dessous vous semble-t’elle correcte ? Expliquez pourquoi.
Vrai
Faux

case $variable in
  valeur1)
    commandes si $variable = valeur1
  valeur2)
    commandes si $variable = valeur2
  *)
    commandes pour toutes les valeurs de $variable != de valeur1 et valeur2
    ;;
esac

Parmi les propositions ci-dessous, laquelle n’est pas une structure pour faire une boucle :
while
until
loop
for

La commande true renvoit toujours 1:
Vrai
Faux