10.2. Choix if

10.2.1. Fonctionnement

La commande interne if implante le choix alternatif.


Syntaxe :     if suite_de_commandes1
                     then
                       suite_de_commandes2
                   [ elif suite_de_commandes ; then suite_de_commandes ] ...
                   [ else suite_de_commandes ]
                   fi

Le fonctionnement est le suivant : suite_de_commandes1 est exécutée ; si son code de retour est égal à 0, alors suite_de_commandes2 est exécutée sinon c'est la branche elif ou la branche else qui est exécutée, si elle existe.

Exemple : programme shell rm1

#!/bin/bash
#	@(#)	rm1

if rm "$1" 2>/dev/null
  then echo "$1" a ete supprime
  else echo "$1" n\'a pas ete supprime
fi
$ >toto    => création du fichier toto
$
$ rm1 toto
toto a ete supprime
$
$ rm1 toto
toto n'a pas ete supprime
$

Lorsque la commande rm1 toto est exécutée, si le fichier toto est effaçable, le fichier est effectivement supprimé, la commande unix rm renvoie un code de retour égal à 0 et c’est la suite de commandes qui suit le mot-clé then qui est exécutée ; le message toto a ete supprime est affiché sur la sortie standard.

Si toto n’est pas effaçable, l’exécution de la commande rm échoue ; celle-ci affiche un message sur la sortie standard pour les messages d’erreur que l’utilisateur ne voit pas car celle-ci a été redirigée vers le puits /dev/null, puis renvoie un code de retour différent de 0. C’est la suite de commandes qui suit else qui est exécutée : le message toto n’a pas ete supprime s’affiche sur la sortie standard.

Les mots if, then, else, elif et fi sont des mots-clés. Par conséquent, pour indenter une structure if suivant le « style langage C », on pourra l’écrire de la manière suivante :


if suite_de_commandes1 ; then
      suite_de_commandes2
else
      suite_de_commandes
fi

La structure de contrôle doit comporter autant de mots-clés fi que de if (une branche elif ne doit pas se terminer par un fi) :


if ...                              if ...
  then ...                          then ...
  elif ...                            else if ...
        then ...                               then ...
fi                                            fi
                                    fi 

Dans une succession de if imbriqués où le nombre de else est inférieur au nombre de then, le mot-clé fi précise l’association entre les else et les if :


if ...
  then ...
    if ...
      then ...
    fi
  else ...
fi

10.2.2. Commande composée [[

La commande interne composée [[ est souvent utilisée avec la commande interne composée if. Elle permet l’évaluation d’expressions conditionnelles portant sur des objets aussi différents que les permissions sur une entrée, la valeur d’une chaîne de caractères ou encore l’état d’une option de la commande interne set.

Syntaxe : [[ expr_cond ]]

Les deux caractères crochets doivent être collés et un caractère séparateur doit être présent de part et d’autre de expr_cond. Les mots [[ et ]] sont des mots-clés.

Le fonctionnement de cette commande interne est le suivant : l’expression conditionnelle expr_cond est évaluée et si sa valeur est Vrai, alors le code de retour de la commande interne [[ est égal à 0. Si sa valeur est Faux, le code de retour est égal à 1. Si expr_cond est mal formée ou si les caractères crochets ne sont pas collés, une valeur différente est retournée.

La commande interne [[ offre de nombreuses expressions conditionnelles ; c’est pourquoi, seules les principales formes de expr_cond seront présentées, regroupées par catégories.

  • Permissions :

    • -r entrée vraie si entrée existe et est accessible en lecture par le processus courant.

    • -w entrée vraie si entrée existe et est accessible en écriture par le processus courant.

    • -x entrée vraie si le fichier entrée existe et est exécutable par le processus courant ou si le répertoire entrée existe et le processus courant possède la permission de passage.

    $ echo coucou > toto
    $ chmod 200 toto
    $ ls -l toto
    --w------- 1 sanchis sanchis 7 20 janv. 09:33 toto
    $
    $ if [[ -r toto ]]
    > then cat toto
    > fi
    $    => aucun affichage donc toto n’existe pas ou n’est pas accessible en lecture,
    $    => dans le cas présent, il est non lisible
    $
    $ echo $?
    0    => code de retour de la commande interne if
    $
    

    Mais,

    $ [[ -r toto ]]
    $
    $ echo $?
    1    => code de retour de la commande interne [[
    $
    

  • Types d'une entrée :

    • -f entrée vraie si entrée existe et est un fichier ordinaire.

    • -d entrée vraie si entrée existe et est un répertoire.

    Exemple : programme shell affic

    #!/bin/bash
    #	@(#)	affic
    
    if [[ -f "$1" ]]
      then 
        echo "$1" : fichier ordinaire
        cat "$1"
      elif [[ -d "$1" ]]
        then 
          echo "$1" : repertoire
          ls "$1"
        else 
          echo "$1" : type non traite
    fi
    
    $ affic .		=> traitement du répertoire courant
    . : repertoire
    affic  err  Exercices  for_liste  for_set  rm1	testval  
    $
    
  • Renseignements divers sur une entrée :

    • -a entrée vraie si entrée existe.

    • -s entrée vraie si entrée existe et sa taille est différente de 0 (remarque : la taille d’un répertoire vide est toujours différente de 0).

    • entrée1 -nt entrée2 vraie si la date de modification de entrée1 est plus récente que celle de entrée2 ou bien entrée1 existe et entrée2 n’existe pas.

    • entrée1 -ot entrée2 vraie si la date de modification de entrée1 est plus ancienne que celle de entrée2 ou bien entrée2 existe et entrée1 n’existe pas.

    $ > err		=> création d’un fichier err vide
    $
    $ ls -l err
    -rw-r--r-- 1 sanchis sanchis 0 21 janv. 09:14 err
    $	 
    $ if [[ -a err ]]
    > then echo err existe
    > fi
    err existe
    $ 
    $ if [[ -s err ]]
    > then echo le contenu du fichier err est non vide
    > else echo son contenu est vide
    > fi
    son contenu est vide
    $
    

  • Longueur d’une chaîne de caractères :

    • -z ch vraie si la longueur de la chaîne ch est égale à zéro.

    • ch

      -n ch

      Ces deux expressions sont vraies si la longueur de la chaîne ch est différente de zéro.

    $ a=		=> la variable a est définie et est vide
    $ 
    $ if [[ -z $a ]]
    > then echo la longueur de a est egale a 0
    > fi
    la longueur de a est egale a 0
    $ 
    $ echo $#
    0		=> aucun paramètre de position n’est initialisé
    $ 
    $
    $ if [[ -z $1 ]]
    > then echo la longueur est egale a 0
    > fi
    la longueur est egale a 0
    $
    

  • Comparaisons de chaînes de caractères :

    • ch1 < ch2 vraie si ch1 précède ch2.

    • ch1 > ch2 vraie si ch1 suit ch2.

    L’ordre des chaînes ch1 et ch2 est commandé par la valeur des paramètres régionaux.

    $ a=bonjour A=Bonjour
    $ 
    $ if [[ $a < $A ]]
    > then echo $a precede $A
    > elif [[ $a > $A ]]
    > then echo $a suit $A
    > else echo elles sont egales
    > fi
    bonjour precede Bonjour
    $
    
    • ch == mod vraie si la chaîne ch correspond au modèle mod.

    • ch != mod vraie si la chaîne ch ne correspond pas au modèle mod.

    mod est un modèle de chaînes pouvant contenir caractères et expressions génériques. Ces derniers ne doivent pas être protégés de l’interprétation de bash lorsqu’ils sont placés entre les caractères [[ ]].

    $ a="au revoir"
    $
    $ [[ $a == 123 ]]		=> faux
    $
    $ echo $?
    1
    $
    $ [[ $a == a* ]]		=> vrai : la valeur de a commence par le caractère a ; on
    $				=>      peut remarquer la non protection du caractère *
    $ echo $?
    0
    $
    $ b=123
    $ 
    $ if [[ $b == +([[:digit:]]) ]] 
    > then
    >   echo "c'est une suite de chiffres"
    > fi
    c'est une suite de chiffres
    $
    $ c=BonJOuR
    $ 
    $ shopt -s nocasematch		=> minuscules et majuscules ne sont pas distinguées
    $ 				 
    $ [[ $c == bonjour ]]
    $ 
    $ echo $?
    0		=> la valeur de la variable c correspond au modèle bonjour
    $
    

    Si ch ou mod ne sont pas définies, la commande interne [[ ne provoque pas d’erreur de syntaxe.

    Exemple : programme shell testval

    #!/bin/bash
    #	@(#)	testval
    
    if [[ "$1" == "$a" ]]
      then echo OUI
      else echo >&2 NON
    fi
    
    $ testval coucou
    NON			=> dans testval, $1 est remplacé par coucou, la variable a n’est 
    $			=> 	pas définie
    $ testval
    OUI			=> aucun des deux membres de l’égalité n’est défini
    $
    $ a=bonjour testval bonjour	=> la variable a est locale au fichier shell testval,
    OUI			=> elle est initialisée lors de l’appel du fichier shell
    $
    

    Remarque : il existe un opérateur =~ qui permet de mettre en correspondance une chaîne de caractère ch avec une expression régulière expr_reg : ch =~ expr_reg

    Contrairement aux caractères et expressions génériques, les expressions régulières ne sont pas spécifiques à bash.

  • État d’une variable :

    • -v var vraie si la variable var possède une valeur.

    • -R var vraie si la variable var est une variable nameref [cf. Chapitre 2, Substitution de paramètres, §6.2] et possède une valeur.

    $ declare -n var0  var1=v2 
    $ v2=bonjour 
    $  
    $ [[ -R v2 ]] 
    $ echo $? 
    1 	=> la variable v2 n’est pas une variable nameref
    $
    $ [[ -v v2 ]] 
    $ echo $? 
    0  	=> la variable (ordinaire) v2 est initialisée
    $
    $ [[ -R var0 ]] 
    $ echo $? 
    1 	=> la variable var0 est une variable nameref mais n’est pas initialisée 
    $  
    $ [[ -R var1 ]] 
    $ echo $? 
    0 	=> la variable var1 est une variable nameref initialisée  
    $ 
    

  • État d’une option :

    • -o opt vraie si l’état de l’option opt de la commande interne set est à on.

    $ set -o | grep noglob
    noglob off
    $
    $ if [[ -o noglob ]]; then echo ON
    > else echo OFF
    > fi
    OFF
    $
    

  • Composition d'expressions conditionnelles :

    • ( expr_cond ) vraie si expr_cond est vraie. Permet le regroupement d’expressions conditionnelles.

    • ! expr_cond vraie si expr_cond est fausse.

    • expr_cond1 && expr_cond2 vraie si les deux expr_cond sont vraies. L’expression expr_cond2 n’est pas évaluée si expr_cond1 est fausse.

    • expr_cond1 || expr_cond2 vraie si une des deux expr_cond est vraie. L’expression expr_cond2 n’est pas évaluée si expr_cond1 est vraie.

    Les quatre opérateurs ci-dessus ont été listés par ordre de priorité décroissante.

    $ ls -l /etc/shadow
    -rw-r----- 1 root shadow 1225 27 déc.  15:56 /etc/shadow 
    $ 
    $ if [[ ! ( -w /etc/shadow || -r /etc/shadow ) ]]
    > then
    >   echo OUI
    > else
    >   echo NON
    > fi
    OUI
    $
    

    Le fichier /etc/shadow n’est accessible ni en lecture ni en écriture pour l’utilisateur sanchis : la valeur de l'expression ( -w /etc/shadow || -r /etc/shadow ) est fausse. Par conséquent, la valeur de sa négation est vraie. Le code de retour de la commande interne [[ sera égal à 0 car l’expression conditionnelle globale est vraie.

    Attention : il ne faut pas confondre les opérateurs ! && || utilisés par la commande interne [[ pour construire les expressions conditionnelles et ces mêmes opérateurs ! && || portant sur les codes de retour [cf. Chapitre 7, Code de retour].

    En combinant commande interne [[, opérateurs sur les codes de retour et regroupements de commandes, l’utilisation d’une structure if devient inutile.

    $ ls -l err 
    -rw-r--r-- 1 sanchis sanchis 0 21 janv. 09:14 err 
    $ chmod u-w err   	=> suppression de la permission w pour l’utilisateur sanchis  
    $ ls -l err 
    -r--r--r-- 1 sanchis sanchis 0 21 janv. 09:14 err 
    $  
    $ [[ -w err ]] || { 
    >    echo >&2 "Probleme d'ecriture sur err" 
    > } 
    Probleme d'ecriture sur err 
    $