3. Structures de Contrôle & autres éléments algorithmiques

Ces structures algorithmiques sont utilisées dans les scripts shells. Pour rédiger un script shell (ici, en GNU/bash), il faut :

  1. Créer le fichier avec un éditeur de texte (sample.sh). La première ligne doit commencer avec le prologue [shebang] : #!/bin/bash.

  2. Attribuer la permission d'exécution sur le fichier créé (chmod +x sample.sh).

  3. Enfin, pour l'exécuter il suffit de taper le nom du fichier, puisque c'est désormais une nouvelle commande (si on est dans le répertoire courant de cette commande, il suffit de taper ./sample.sh).

Nous donnons dans ce qui suit, quelques exemples de scripts shells (GNU/bash).

3.1. Alternatives

3.1.1.  La structure alternative if

Cette structure permet l'exécution conditionnelle d'une instruction. Voici sa syntaxe (les deux formes sont équivalentes) :


if [ <condition> ]; then                   if test <condition>; then            
  <liste instructions>                       <liste instructions>
elif [ <condition> ]; then                 elif test <condition>; then
  <liste instructions>                       <liste instructions>
elif [ <condition> ]; then                 elif test <condition>; then
  <liste instructions>                       <liste instructions>
else                                       else
  <liste instructions>                       <liste instructions>
fi                                         fi

Exemple 5. Utilisation du if

#!/bin/bash
# /home/pascal/bin/fcomp.sh
#
# but : comparer deux fichiers
#
#                                      [Sept. 2002]

if [ $# != 2 ]; then
    echo "usage : $0 file1 file2"
    echo "compare deux fichiers file1 et file2 et dit s'ils sont identiques ou \
differents."
    exit -1
fi

`/usr/bin/cmp -s $1 $2`   # exécution d'un sous-shell, pour la comparaison
stat=$?                   # statut de retour de la commande
                          #   (0 si les fichier sont identiques)

if [ $stat -eq 0 ]; then
    echo "Les fichiers $1 et $2 sont identiques."
else
    echo "Les fichiers $1 et $2 sont differents."
fi
exit $stat

Exemple 6.  Résultat du code de Exemple 5, « Utilisation du if »


$ ~/bin/fcomp.sh
usage : /home/pascal/bin/fcomp.sh file1 file2
compare deux fichiers file1 et file2 et dit s'il sont identiques ou differents. 

$ ~/bin/fcomp.sh ~/bin/fcomp.sh ~/bin/fdate.sh 
Les fichiers /home/pascal/bin/fcomp.sh et /home/pascal/bin/fdate.sh sont differe
nts.

3.1.2. La structure alternative case

Cette structure permet l'exécution conditionnelle d'une instruction.


case <variable> in                                                              
  motif11|motif12|... )
         <liste instructions>
         ;;

  motif21|motif22|... )
         <liste instructions>
         ;;

  motif3)
         <liste instructions>
         ;;

  motif4)
         <liste instructions>
         ;;

  *)
         <liste instructions>
         ;;
esac

Exemple 7. Utilisation du switch

#!/bin/bash
# /home/pascal/bin/fdate.sh
#
# but :  conversion date en francais 
#
#                                      [Sept. 2002]

set `date`      # transmission dans les var. positionnelles $1, $2 ...          

# Traiter le jour 
case "$1" in
    Mon)
        day=Lun
        ;;
    Tue)
        day=Mar
        ;;
    Wed)
        day=Mer
        ;;
    Thu)
        day=Jeu
        ;;
    Fri)
        day=Ven
        ;;
    Sat)
        day=Sam
        ;;
    Sun)
        day=Dim
        ;;
esac

# Traiter le mois
case "$2" in
    Feb)
        mon=Fev
        ;;
    Apr)
        mon=Avr
        ;;
    May)
        mon=Mai
        ;;
    Jun)
        mon=Juin
        ;;
    Jul)
        mon=Juil
        ;;
    Aug)
        mon=Aou
        ;;
    Jan|Sep|Oct|Nov|Dec )
        mon=$2
        ;;
esac

# Afficher le resultat
echo "$day $3 $mon $6, $4 [$5]"

Exemple 8.  Résultat du code de Exemple 7, « Utilisation du switch »

$ fdate.sh                                                                      
Sam 12 Avr 2003, 21:20:45 [RET]

3.2. Boucles

3.2.1.  Boucle while

Il s'agit de l'instruction standard qui permet d'exécuter le corps de la boucle tant que la condition d'entrée est vérifiée (valeur évaluée à vrai).


while [ <condition> ]; do                                                       
  <corps_de_boucle>
done

Exemple 9. Utilisation du while


#!/bin/bash                                                                     
# ~/bin/test.sh                                                                 

ind=1

while [ $ind -lt  11 ]; do
  indcar=`expr $ind \* $ind` 
  echo $ind "  " $indcar
  ind=` expr $ind + 1` 
done

Exemple 10.  Résultat du code de Exemple 9, « Utilisation du while »


$ ~/bin/test.sh                                                                 
1     1                                                                         
2     4
3     9
4     16
5     25
6     36
7     49
8     64
9     81
10     100

3.2.2. Boucle until

Il s'agit de l'instruction standard qui permet d'exécuter le corps de la boucle jusquà ce que la condition d'entrée soit vérifiée (valeur évaluée à vrai).


until [ <condition> ]; do                                                       
  <corps_de_boucle>
done

Exemple 11.  Utilisation du until


#!/bin/bash                                                                     
# ~/bin/test.sh                                                                 

ind=1

until [ $ind -ge  11 ]; do
  indcar=`expr $ind \* $ind` 
  echo $ind "  " $indcar
  ind=` expr $ind + 1` 
done

Exemple 12.  Résultat du code de Exemple 11, «  Utilisation du until  »


$ ~/bin/test.sh                                                                 
1     1                                                                         
2     4
3     9
4     16
5     25
6     36
7     49
8     64
9     81
10     100

3.2.3.  Boucle for

Cette instruction permet de traiter en séquence tous les éléments d'une liste.


for <variable> in <liste_de_valeur>; do                                         
  <corps_de_boucle>
done

Exemple 13.  Utilisation du for


#!/bin/bash 
# ~/bin/test.sh                                                                 

for i in 1 2 3 4 5 6 7 8 9; do                                                  
  m=`expr $i \* 2`
  echo -n " $m"
done
echo

Exemple 14.  Résultat du code de Exemple 13, «  Utilisation du for  »


$ ~/bin/test.sh                                                                 
2 4 6 8 10 12 14 16 18                                                          
$

3.3. Variables utilisateurs

Ce sont les variables que les utilisateurs peuvent définir. Le shell étant un langage interprété non typé, aucune déclarartion n'est nécessaire et la même variable peut contenir tour à tour une chaîne de caractères, un entier ou un vecteur (tableau d'éléments de type chaîne de caractères ou entier, indexé par des entiers, commençant à 0).

Affectation. Cette opération permet d'associer un contenu à une variable (le contenant). En GNU/bash l'affectation se fait en utilisant de la manière suivante (noter bien l'absence d'espace autour du symbole =) :

EXEMPLE :

$ myvar1=10                                                                     
$ myvar2="chaine de caractère"

Accès au contenu. L'accès au contenu de la variable se fait en préfixant le nom de la variable par le symbole $.

EXEMPLE (SUITE) :

$ echo $myvar1                                                                  
10
$ echo myvar2
chaine de caractère

$ echo myvar1     # omission du méta-caractère $
myvar1            # echo affiche le mot myvar1

On peut aussi encadrer la variable par les symboles { et } avant de la préfixer par le symbole $ :

EXEMPLE (SUITE) :

$ si="pas "                                                                     
$ echo "c'est vraiment $sidrole"  # la variable $sidrole n'est pas définie      
c'est vraiment
$ echo "c'est vraiment ${si}drôle"
c'est vraiment pas drôle

[Note]Note

L'accès à une variable non définie ne produit pas d'erreur en GNU/bash.

Effacer une variable. Cette opération permet de libérer la case mémoire occupée par la variable. Cela se fait en GNU/bash en utilisant la commande interne unset de la manière suivante :

EXEMPLE (SUITE) :

$ echo $myvar1                                                                  
10
$ unset myvar1
$ echo $myvar1

$

Variable de type vecteur. Voici un exemple :

CODE :


#!/bin/bash
# ~/bin/testvect.sh

vect=( 2 -2 4 -4 6 -6 8 -8 10 -10 )

# afficher les  elements du vecteur vect, element par element
i=0
sum=0
echo " -  Affichage element par element"
while [ $i -lt ${#vect[*]} ]; do
    echo '${vect[' $i ']} = ' ${vect[$i]}
    bidon=$[ sum += ${vect[i]} ]    # id. sum=`expr $sum + ${vect[$i]}`
    bidon=$[ i++ ]                  # id. i=`expr $i + 1`
done

echo " - Somme des elements = " $sum

echo "Voici le vecteur vect (${#vect[*]} elements)         : " ${vect[*]}

# fin du script



EXEMPLE :

$ ./testvect.sh                                                                 
 -  Affichage element par       element
${vect[ 0 ]} =  2
${vect[ 1 ]} =  -2
${vect[ 2 ]} =  4
${vect[ 3 ]} =  -4
${vect[ 4 ]} =  6
${vect[ 5 ]} =  -6
${vect[ 6 ]} =  8
${vect[ 7 ]} =  -8
${vect[ 8 ]} =  10
${vect[ 9 ]} =  -10
 - Somme des elements =  0
Voici le vecteur vect (10 elements)         :  2 -2 4 -4 6 -6 8 -8 10 -10
$

Mécanismes de substitution. 

Tableau 7. Clé de substitution

CléSémantique
${#<name>}Donne le nombre de caractères du contenu de la variable
${<name>:-<value>}Renvoit la valeur de la variable <name> si elle existe et sinon <value> (il n'y a pas affectation).
${<name>:=<value>}Renvoit la valeur de la variable <name> si elle existe et sinon affecte <value> à <name> et renvoit la valeur.
${<name>:?<value>}Affiche <value> sur la sortie d'erreur standard si <name> n'est pas définie et réalise un exit s'il ne s'agit pas d'un shell interactif
${<name>:+<value>}Affiche <value> si <name> est définie et rien sinon
${<name>#<motif>}Supprime de <name> le motif <motif> le plus petit correspondant à gauche.
${<name>##<motif>}Supprime de <name> le motif <motif> le plus grand correspondant à gauche.
${<name>%<motif>}Supprime de <name> le motif <motif> le plus petit correspondant à droite.
${<name>%%<motif>}Supprime de <name> le motif <motif> le plus grand correspondant à droite.

EXEMPLE :


$ var=/usr/local/bin/example.tar.gz

$ echo ${var#*.}   # supprime ce qui précède le 1er . [+ petit motif à gauche]  
tar.gz                
            
$ echo ${var##*.}  # supprime ce qui précède le 2e . [+ long motif à gauche]
.gz

$ echo ${var%.*}   # supprime ce qui suit le dernier . [+ petit motif à droite]
/usr/local/bin/example.tar

$ echo ${var%%.*}  # supprime ce qui suit l'av-dernier . [+ gand motif à droite]
/usr/local/bin/example

Q : D'après l'exemple qui précède, comment n'obtenir que le nom du fichier, sans le chemin complet et sans le suffixe (en deux temps) ?
Q :

D'après l'exemple qui précède, comment n'obtenir que le nom du fichier, sans le chemin complet et sans le suffixe (en deux temps) ?

R :

3.4. Paramètres du shell

Le shell définit pour chaque commande un certain nombre de paramètres « automatiques ».

Tableau 8. Opérateurs de comparaison

Paramètre(s)Utilisation
$0nom de la commande ou du script invoqué.
$1, $2 .. $9valeurs des paramètres positionnels passés à la commande ou au script.
$*ensemble des paramètres, sauf $0. "$#" vaut "$1 $2 ..."
$@ensemble des paramètres, sauf $0. "$@" vaut "$1" "$2" ...
$#nombre de paramètres effectifs, $0 non compris.
$-les options courantes du shell
$?le code de retour de la dernière commande
$$le pid du shell parent
$!le pid du dernier processus asynchrone lancé par le shell parent
$_le dernier argument de la commande précédente

CODE :

#!/bin/bash
# ~/bin/testparm.sh

echo 'je suis le script, $0 = ' $0

if [ $# -gt 0 ]; then
    echo ' evoque avec ' $# ' arguments sur la ligne de commande'
    echo 'Voici les parametres : $* ' $*
    i=0
    for param in $*; do
        echo "parametre ($i) :  $param"
        i=`expr $i + 1`
    done
else
    echo ' pas d arguments, nb arg $# = ' $#
fi

# fin du script


EXEMPLE :

$ ./testparam.sh toto titi tutu 147                                             
je suis le script, $0 =  ./ftestparm.sh
 evoque avec  4  arguments sur la ligne de commande
Voici les parametres : $*  toto titi tutu 147
parametre (0) :  toto
parametre (1) :  titi
parametre (2) :  tutu
parametre (3) :  147

$ ./testparam.sh
je suis le script, $0 =  ./testparam.sh
 pas d arguments, nb arg $# =  0
$

3.5. Expressions conditionnelles

Voici une liste non exhaustive des principaux opérateurs de comparaison :

Tableau 9. Opérateurs de comparaison

OpérateurSémantique
O P E R A T E U R S    D E    F I C H I E R S
-b <filename>est vraie si le fichier désigné par <filename> est un fichier spécial en mode bloc.
-c <filename>est vraie si le fichier désigné par <filename> est un fichier spécial en mode caractères.
-d <filename>est vraie si le fichier désigné par <filename> est un répertoire.
-e <filename>est vraie si le fichier désigné par <filename> existe.
-f <filename>est vraie si le fichier désigné par <filename> est un fichier régulier.
-g <filename>est vraie si le fichier désigné par <filename> a le setgid bit activé.
-k <filename>est vraie si le fichier désigné par <filename> a le sticky bit activé.
-u <filename>est vraie si le fichier désigné par <filename> a le setuid bit activé.
-G <filename>est vraie si le fichier désigné par <filename> appartient au groupe (egid).
-O <filename>est vraie si le fichier désigné par <filename> appartient à l'utilisateur effectif du processus (euid).
-L <filename>est vraie si le fichier désigné par <filename> est un lien symbolique.
-p <filename>est vraie si le fichier désigné par <filename> est un fichier spécial FIFO (tube nommé).
-r <filename>est vraie si le fichier désigné par <filename> est un fichier accessible en lecture.
-w <filename>est vraie si le fichier désigné par <filename> est un fichier accessible en écriture.
-x <filename>est vraie si le fichier désigné par <filename> est un fichier exécutable.
<filename1> -nt <filename2>est vraie si <filename1> est plus récent que <filename2>.
<filename1> -ot <filename2>est vraie si <filename1> est plus vieux que <filename2>.
C O M P A R A I S O N    D E    C H A I N E S
-n <str>est vraie si <str> a une longueur non nulle.
-z <str>est vraie si <str> a une longueur nulle.
<str1> = <str2>est vraie si <str1> est égale à <str2>.
<str1> != <str2>est vraie si <str1> est différente de <str2>.
C O M P A R A I S O N    D ' E N T I E R S
<int1> -eq <int2>est vraie si <int1> est égal à <int2>.
<int1> -ne <int2>est vraie si <int1> est différent <int2>.
<int1> -ge <int2>est vraie si <int1> est supérieur ou égal à <int2>.
<int1> -gt <int2>est vraie si <int1> est strictement supérieur à <int2>.
<int1> -le <int2>est vraie si <int1> est inférieur ou égal à <int2>.
<int1> -lt <int2>est vraie si <int1> est strictement inférieur à <int2>.
C O M B I N A I S O N    D ' E X P R E S S I O N S
( <expr> ) est vraie si <expr> est vraie.
! <expr> est vraie si <expr> est fausse.
<expr1> -a <expr2> est vraie si <expr1> et <expr2> sont simultanément vraies.
<expr1> -o <expr2> est vraie si <expr1> ou <expr2> est/sont vraie(s).

Règles :

  • Les instructions arithmétiques apparaissent dans l'instruction let et dans l'expansion arithmétique.

  • Les constantes commençant par 0 (resp. 0x) sont interprétées comme des valeurs en base octale (resp. héxadécimale).

  • Une constante entière peut être exprimée dans une base de l'intervalle [2, 64], en la préfixant par l'expression base#. La base par défaut est 10.

  • Les variables du shell sont autorisées comme opérandes dans les expressions arithmétiques, l'expansion (substitution de la variable par sa valeur) ayant lieu, logiquement, avant l'évaluation de l'expression arithmétique.