7. Tests

Tout langage de programmation complet peut tester des conditions et agir suivant le 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 la construction if/then.

7.1. Constructions de tests

  • Une construction if/then teste si l'état de la sortie d'une liste de commandes vaut 0 (car 0 indique le « succès » suivant les conventions UNIX) et, dans ce cas, exécute 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 comparaisons ou comme des tests de fichiers et renvoie un état de sortie correspondant au résultat de la comparaison (0 pour vrai et 1 pour faux).

  • Avec la version 2.02, Bash a introduit la commande de test étendue [[ ... ]], réalisant des comparaisons d'une façon familière aux programmeurs venant d'autres langages. Notez que [[ est un mot clé, pas une commande.

    Bash considère [[ $a -lt $b ]] comme un seul élément, renvoyant un état de sortie.

    Les constructions (( ... )) et let ... renvoient aussi un état de sortie de 0 si les expressions arithmétiques qu'elles évaluent se résolvent en une valeur non nulle. Ces constructions d'expansion arithmétique peuvent donc être utilisées pour réaliser des comparaisons arithmétiques.

    let "1<2" renvoie 0 (car "1<2" se transforme
                en "1")
    (( 0 && 1 )) renvoie 1 (car "0 && 1" donne "0")
    
  • Un if peut tester n'importe quelle commande, pas seulement des conditions entourées par des 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
    
    # La construction "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é dans $mot"
    else
      echo "$sequence_lettres non trouvé dans $mot"
    fi
    
    if COMMANDE_DONT_LA_SORTIE_EST_0_EN_CAS_DE_SUCCES
    then echo "Commande réussie."
    else echo "Commande échouée."
    fi
    
  • Une construction if/then peut contenir des comparaisons et des tests imbriqués.

    if echo "Le *if* suivant fait partie de la
    comparaison du premier *if*."
    
      if [[ $comparaison = "integer" ]]
        then (( a < b ))
      else
        [[ $a < $b ]]
      fi
    
    then
      echo '$a est plus petit que $b'
    fi
    

    L'explication détaillée du « if-test » provient de Stéphane Chazelas.

Exemple 7.1. Où est le vrai?

#!/bin/bash

#  Astuce :
#  Si vous n'êtes pas sûr de la façon dont une certaine condition sera évaluée,
#+ testez-la avec un 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 "Chaîne de caractères au hasard est vrai."
else
  echo "Chaîne de caractères au hasard est faux."
fi            # Chaîne de caractères au hasard est vrai.

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 vrai."
else
  echo "Une variable non initialisée est faux."
fi            # Une variable non initialisée est faux.

echo

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

echo


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

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


echo


# Quand "faux" est-il vrai?

echo "Test de \"false\""
if [ "false" ]              #  Il semble que "false" ne soit 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, « Où est le vrai? », ci-dessus.

if [ condition-vraie ]
then
   commande 1
   commande 2
   ...
else
   # Optionnel (peut être oublié si inutile).
   # Ajoute un code par défaut à exécuter si la condition originale se révèle
   # fausse.
   commande 3
   commande 4
   ...
fi
[Note]

Note

Quand if et then sont sur la même ligne lors d'un test, un point-virgule doit finir l'expression if. if et then sont des mots clés. Les mots clés (et les commandes) commençant une expression doivent être terminés avant qu'une nouvelle expression sur la même ligne puisse commencer.

if [ -x "$nom_fichier" ]; then

Else if et elif

elif

elif est une contraction pour else if. Le but est de faire tenir une construction if/then dans une autre construction déjà commencée.

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

La construction if test condition-vraie est l'exact équivalent de if [ condition-vraie ]. De cette façon, le crochet gauche, [, est un raccourci appelant la commande test. Le crochet droit fermant, ], ne devrait donc pas être nécessaire dans un test if , néanmoins, 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 fichiers et de comparer des chaînes de caractères. Donc, 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, alors indiquez le chemin complet.

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

#!/bin/bash

echo

if test -z "$1"
then
  echo "Pas d'arguments 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 que la commande intégrée "test".
#  ^^^^^^^^^^^^^              # Spécification du chemin complet.
then
  echo "Pas d'arguments 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'un crochet fermant manque.
then
  echo "Pas d'arguments 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 :
#                               Ceci a été corrigé dans Bash, version 3.x.
then
  echo "Pas d'arguments 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

La construction « if COMMANDE » renvoie l'état de sortie de la COMMANDE.

De manière identique, 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 construction (( )) évalue une expression arithmétique. Si l'expression vaut 0, elle renvoie un code de sortie de 1, ou « false ». Une expression différente de 0 renvoie 0, ou « true ». Ceci est en totale contradiction avec l'utilisation des constructions test et [ ] évoquées précédemment.

Exemple 7.3. Tests arithmétiques en utilisant (( ))

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

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

(( 0 ))
echo "Le code de sortie de \"(( 0 ))\" est $?."         # 1

(( 1 ))
echo "Le code de sortie de \"(( 1 ))\" est $?."         # 0

(( 5 > 4 ))                                             # vrai
echo "Le code de sortie de \"(( 5 > 4 ))\" est $?."     # 0

(( 5 > 9 ))                                             # faux
echo "Le code de sortie de \"(( 5 > 9 ))\" est $?."     # 1

(( 5 - 5 ))                                             # 0
echo "Le code de sortie de \"(( 5 - 5 ))\" est $?."     # 1

(( 5 / 4 ))                                             # Division OK.
echo "Le code de sortie de \"(( 5 / 4 ))\" est $?."     # 0

(( 1 / 2 ))                                             # Résultat de la division < 1.
echo "Le code de sortie de \"(( 1 / 2 ))\" est $?."     # Arrondie à 0.
                                                        # 1

(( 1 / 0 )) 2>/dev/null                                 # Division par 0... illégale.
#           ^^^^^^^^^^^
echo "Le code 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