7. Les tests

Tous les langages de programmation raisonnablement complets sont capables de tester des conditions, puis d'agir en fonction du résultat du test. Bash dispose de la commande test, de différents opérateurs à base de crochets et de parenthèses, ainsi que de contrôles if/then.

7.1. Les contrôles de test

  • Un contrôle if/then teste si l'état de sortie d'une liste de commandes vaut 0 (car 0 indique le « succès » suivant les conventions UNIX) et, dans ce cas, fait exécuter une ou plusieurs commandes.

  • Il existe une commande dédiée appelée [ (caractère spécial crochet gauche). C'est un synonyme de test, qui est intégré pour des raisons d'optimisation. Cette commande considère ses arguments comme des expressions de comparaison ou comme des tests de fichiers et renvoie un état de sortie correspondant au résultat de la comparaison (0 pour vrai, 1 pour faux).

  • Dans sa version 2.02, Bash a introduit la commande de étendue de test [[ ... ]]. Cette commande réalise les comparaisons d'une façon qui est familière aux programmeurs venant d'autres langages. Remarque : [[ en lui-même est un mot clé, pas une commande.

    Bash considère l'expression [[ $a -lt $b ]] comme un seul élément, et il renvoie un état de sortie.

  • Les expressions (( ... )) et let ... renvoient un code de sortie qui dépend du fait que les expressions arithmétiques qu'elles évaluent se résolvent en une valeur non nulle. Cette syntaxe avec évaluations arithmétiques peut par conséquent être utilisée pour effectuer des comparaisons arithmétiques.

    (( 0 && 1 ))                 # 'ET' logique
    echo $?     # 1     ***
    # Et donc ...
    let "num = (( 0 && 1 ))"
    echo $num   # 0
    # Mais ...
    let "num = (( 0 && 1 ))"
    echo $?     # 1     ***
    
    
    (( 200 || 11 ))              # 'OU' logique
    echo $?     # 0     ***
    # ...
    let "num = (( 200 || 11 ))"
    echo $num   # 1
    let "num = (( 200 || 11 ))"
    echo $?     # 0     ***
    
    
    (( 200 | 11 ))               # 'OU' bit à bit
    echo $?                      # 0     ***
    # ...
    let "num = (( 200 | 11 ))"
    echo $num                    # 203
    let "num = (( 200 | 11 ))"
    echo $?                      # 0     ***
    
    # L'expression en "let" renvoie le même statut de sortie
    #+ que l'expression arithmétique avec les doubles-parenthèses.
    
    [Attention]

    Attention

    De nouveau, il faut bien remarquer que le statut de sortie d'une expression arithmétique n'est pas un code d'erreur.

    var=-2 && (( var+=2 ))
    echo $?                   # 1
    
    var=-2 && (( var+=2 )) && echo $var
                              # Will not echo $var!
    
  • if peut tester n'importe quelle commande, pas seulement celles entourées de crochets.

    if cmp a b &> /dev/null  # Supprime la sortie.
    then echo "Les fichiers a et b sont identiques."
    else echo "Les fichiers a et b sont différents."
    fi
    
    # L'expression "if grep" très utile:
    # -------------------------------------
    if grep -q Bash fichier
      then echo "'fichier' contient au moins une occurrence du mot Bash."
    fi
    
    mot=Linux
    sequence_lettres=inu
    if echo "$mot" | grep -q "$sequence_lettres"
    # L'option "-q" de grep supprime l'affichage du résultat.
    then
      echo "$sequence_lettres trouvée dans $mot"
    else
      echo "$sequence_lettres non trouvée dans $mot"
    fi
    
    if COMMANDE_DONT_LA_SORTIE_EST_0_EN_CAS_DE_SUCCES
      then echo "La commande a réussi."
      else echo "La commande a échoué."
    fi
    
  • Les deux exemples précédents sont de Stéphane Chazelas.

Exemple 7.1. Qu'est-ce qui est vrai ?

#!/bin/bash

#  Conseil :
#  En cas de doute sur la manière dont une condition sera évaluée,
#+ testez-la plutôt avec 'if'.

echo

echo "Test de \"0\""
if [ 0 ]      # zéro
then
  echo "0 est vrai."
else
  echo "0 est faux."
fi            # 0 est vrai.

echo

echo "Test de \"1\""
if [ 1 ]      # un
then
  echo "1 est vrai."
else
  echo "1 est faux."
fi            # 1 est vrai.

echo

echo "Test de \"-1\""
if [ -1 ]     # moins un
then
  echo "-1 est vrai."
else
  echo "-1 est faux."
fi            # -1 est vrai.

echo

echo "Test de \"NULL\""
if [ ]        # NULL (condition vide)
then
  echo "NULL est vrai."
else
  echo "NULL est faux."
fi            # NULL est faux.

echo

echo "Test de \"xyz\""
if [ xyz ]    # chaîne de caractères
then
  echo "Cette chaîne de caractères pris au hasard est vraie."
else
  echo "Cette chaîne de caractères pris au hasard est fausse."
fi            # Cette chaîne de caractères pris au hasard est vraie.

echo

echo "Test de \"\$xyz\""
if [ $xyz ]   # Teste si $xyz est nul, mais...
              # c'est seulement une variable non initialisée.
then
  echo "Une variable non initialisée est vraie."
else
  echo "Une variable non initialisée est fausse."
fi            # Une variable non initialisée est fausse.

echo

echo "Test de \"-n \$xyz\""
if [ -n "$xyz" ]            # Plus correct.
then
  echo "Une variable non initialisée est vraie."
else
  echo "Une variable non initialisée est fausse."
fi            # Une variable non initialisée est fausse.

echo

xyz=          # Initialisée, mais à une valeur nulle.

echo "Test de \"-n \$xyz\""
if [ -n "$xyz" ]
then
  echo "Une variable nulle est vraie."
else
  echo "Une variable nulle est fausse."
fi            # Une variable nulle est fausse.


echo


# Quand "faux" est-il vrai?

echo "Test de \"false\""
if [ "false" ]              #  En fait, "false" n'est qu'une chaîne de
                            #+ caractères.
then
  echo "\"false\" est vrai." #+ et il est testé vrai.
else
  echo "\"false\" est faux."
fi                          # "false" est vrai.

echo

echo "Test de \"\$false\""  # De nouveau, une chaîne non initialisée.
if [ "$false" ]
then
  echo "\"\$false\" est vrai."
else
  echo "\"\$false\" est faux."
fi            # "$false" est faux.
              # Maintenant, nous obtenons le résultat attendu.


#  Qu'arriverait-t'il si nous testions la variable non initialisée "$true" ?

echo

exit 0

Exercice. Expliquez le comportement de l'Exemple 7.1, « Qu'est-ce qui est vrai ? », ci-dessus.

if [ condition-vraie ]
then
   commande 1
   commande 2
   ...
else #  Sinon...
     #  Ajoute un code par défaut à exécuter si la première condition
     #+ est fausse.
   commande 3
   commande 4
   ...
fi
[Note]

Note

Dans un test, lorsque if et then figurent sur la même ligne, l'expression if doit se terminer par un point-virgule. if et then sont des mots clés. Les mots clés (et les commandes) commençant une expression doivent être d'abord terminés pour qu'une nouvelle expression figurant sur la même ligne puisse commencer.

if [ -x "$nom_fichier" ]; then

Else if et elif

elif

elif est une forme contractée de else if. L'effet produit est l'inclusion d'une seconde boucle en if/then à l'intérieur d'une boucle déjà commencée.

if [ condition1 ]
then
   commande1
   commande2
   commande3
elif [ condition2 ]
# Idem que else if
then
   commande4
   commande5
else
   commande_par_defaut
fi

Le contrôle if test condition-vraie est l'exact équivalent de if [ condition-vraie ]. De cette façon, le crochet gauche, [, est un symbole de raccourci [32] appelant la commande test. Le crochet droit fermant, ], ne devrait donc pas être nécessaire dans un test if, cependant les dernières versions de Bash le requièrent.

[Note]

Note

La commande test est une commande interne de Bash permettant de tester les types de fichier et de comparer des chaînes de caractères. Par conséquent, dans un script Bash, test n'appelle pas le binaire externe /usr/bin/test, qui fait partie du paquet sh-utils. De même, [ n'appelle pas /usr/bin/[, qui est un lien vers /usr/bin/test.

bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found
              

Si pour une raison ou une autre vous souhaitez utiliser /usr/bin/test dans un script Bash, bous devez alors préciser le chemin complet.

Exemple 7.2. Équivalences entre test, /usr/bin/test, [ ], et /usr/bin/[

#!/bin/bash

echo

if test -z "$1"
then
  echo "Aucun argument sur la ligne de commande."
else
  echo "Le premier argument de la ligne de commande est $1."
fi

echo

if /usr/bin/test -z "$1"      # Même résultat qu'avec la commande intégrée "test".
#  ^^^^^^^^^^^^^              # Spécification du chemin complet.
then
  echo "Aucun argument sur la ligne de commande."
else
  echo "Le premier argument de la ligne de commande est $1."
fi

echo

if [ -z "$1" ]                # Identique fonctionnellement au bloc de code.
#   if [ -z "$1"                devrait fonctionner, mais...
#+  Bash répond qu'il manque le crochet fermant.
then
  echo "Aucun argument sur la ligne de commande."
else
  echo "Le premier argument de la ligne de commande est $1."
fi

echo


if /usr/bin/[ -z "$1" ]       # Encore une fois, fonctionnalité identique à ci-dessus.
# if /usr/bin/[ -z "$1"       # Fonctionne, mais donne un message d'erreur.
#                             # Note :
#                             Ce point a été corrigé dans les versions 3.x de Bash.
then
  echo "Aucun argument sur la ligne de commande."
else
  echo "Le premier argument de la ligne de commande est $1."
fi

echo

exit 0

[Note]

Note

Après un if, ni la commande test ni les crochets de test ( [ ] ou [[ ]] ) ne sont nécessaires.

repertoire=/home/bozo

if cd "$repertoire" 2>/dev/null; then   # "2>/dev/null" cache les messages d'erreur
  echo "Je suis maintenant dans $repertoire."
else
  echo "Je ne peux pas aller dans $repertoire."
fi

L'expression « if COMMANDE » renvoie l'état de sortie de la COMMANDE.

De même, une condition à l'intérieur de crochets de test peut fonctionner sans if si elle est utilisée avec une construction en liste.

var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 n'est pas égal à $var2"

home=/home/bozo
[ -d "$home" ] || echo "Le répertoire $home n'existe pas."

La syntaxe (( )) permet d'évaluer une expression arithmétique. Si l'expression vaut 0, elle renvoie un état de sortie de 1, ou « false ». Une expression différente de 0 renvoie 0 ou « true ». Ce qui est en totale contradiction avec l'utilisation des contrôles test et [ ] évoqués précédemment.

Exemple 7.3. Tests arithmétiques avec (( ))

#!/bin/bash
# Tests arithmétiques.

# La construction (( ... )) évalue et teste les expressions numériques.
# État de sortie opposé à ceux de la syntaxe [ ... ] !

(( 0 ))
echo "L'état de sortie de \"(( 0 ))\" est $?."         # 1

(( 1 ))
echo "L'état de sortie de \"(( 1 ))\" est $?."         # 0

(( 5 > 4 ))                                             # vrai
echo "L'état de sortie de \"(( 5 > 4 ))\" est $?."     # 0

(( 5 > 9 ))                                             # faux
echo "L'état de sortie de \"(( 5 > 9 ))\" est $?."     # 1

(( 5 - 5 ))                                             # 0
echo "L'état de sortie de \"(( 5 - 5 ))\" est $?."     # 1

(( 5 / 4 ))                                             # Division OK.
echo "L'état de sortie de \"(( 5 / 4 ))\" est $?."     # 0

(( 1 / 2 ))                                             # Résultat de la division < 1.
echo "L'état de sortie de \"(( 1 / 2 ))\" est $?."     # Arrondi à 0.
                                                        # 1

(( 1 / 0 )) 2>/dev/null                                 # Division par 0... illégale.
#           ^^^^^^^^^^^
echo "L'état de sortie de \"(( 1 / 0 ))\" est $?."     # 1

# Quel effet a "2>/dev/null"?
# Qu'arriverait-t'il s'il était supprimé?
# Essayez de le supprimer, et ré-exécutez le script.

exit 0



[32] Un symbole de raccourci (token en anglais) est un symbole ou une courte chaîne de caractères ayant un sens spécial (un méta-sens). Dans Bash, certains symboles comme [ et . (dot-command) sont développés en mots-clés ou en commandes.