Introduction aux scripts de l'interpréteur BASH

De POLR
Sauter à la navigation Sauter à la recherche
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.


Un petit script simple

Voici un petit script exemple.sh qui sera exécuté par l’interpréteur de commande bash. Le script a été créé à l’aide d’un éditeur de texte tel gedit ou vi.

#!/bin/bash
#fichier exemple.sh
# ceci est un commentaire
echo "Le nombre de parametres est: "  $#
echo "Les parametres sont :" $*
echo ' Les paramtetres sont aussi disponible comme ceci : ' $@ 
echo "Le parametre numero 1 est : " $1
echo "Le parametre numero 12 est : " ${12}
echo "Mon numero de processus (PID) est: " $$
echo "Entrez un nombre au clavier: "
read nombre
echo "Votre nombre est : " ${nombre:-nul}

Explication du script

Le script débute avec la ligne #!/bin/bash. Normalement, ‘ # ’ indique le début d’un commentaire. Cependant, la séquence ‘#!’ au début d’un script indique à Linux d’utiliser le programme spécifié, ici, l’interpréteur de commandes /bin/bash pour interpréter et exécuter les commandes qui suivent.

Les paramètres passés au script sont disponibles via les variables $1, $2, ... $9. $* et $@ représentent la liste de tous les paramètres passés au script alors que $# contient le nombre de paramètres. La variable $$ contient le numéro du processus qui exécute le script (résultat d’un fork et exec).

L’énoncé read nombre attend une entrée de l’entrée standard (habituellement le clavier) et attribue la valeur fournie à la variable nombre. Pour utiliser le contenu de la variable nombre, on peut utiliser la syntaxe $nombre ou bien ${nombre}. On peut aussi attribuer une valeur par défaut à la variable nombre si aucune valeur n’a été fournie au clavier: dans cet exemple, si aucune valeur n’est fournie, la variable nombre prend pour valeur nul.

Pour exécuter ce script, il faut tout d’abord le rendre exécutable:

-bash-3.2$ ls -l exemple.sh
-rw-r--r--. 1 caveenj users 289 nov  2 20:20 exemple.sh
-bash-3.2$ chmod +x exemple.sh
-bash-3.2$ ls -l exemple.sh
-rwxr-xr-x. 1 caveenj users 289 nov  2 20:20 exemple.sh
-bash-3.2$ ./exemple.sh bonjour tout le monde 
Le nombre de parametres est:  4 
Les parametres sont : bonjour tout le monde 
Les paramtetres sont aussi disponible comme ceci : bonjour tout le monde 
Le parametre numero 1 est :  bonjour 
Le parametre numero 12 est :
Mon numero de processus (PID) est:  3576 
Entrez un nombre au clavier: 
6 
Votre nombre est :  6 
-bash-3.2$

Nous pouvons utiliser la redirection des entrées-sorties avec les scripts:

-bash-3.2$ echo 6 | ./exemple.sh bonjour tout le monde

donnera le même résultat sans attendre qu'on fournisse un nombre à partir du clavier.

Récapitulatif des variables spéciales d'un script

#!/bin/bash
Syntaxe donnant le nom de l'interpréteur de commande à utiliser pour exécuter le script. Cet énoncé doit se trouver sur la première ligne du script.
$#
Contient le nombre de paramètres (ou arguments) que l'utilisateur a fourni lors de l'appel du script. Si on appelle le script exemple.sh de la façon suivante: exemple.sh a b c d, $# aura pour valeur 4 et l'appel exemple.sh a b c d e f donnera à $# la valeur 6.
$1
Contient la valeur du premier argument. L'appel exemle.sh a b c d attribue la valeur a à $1.
$2
Contient la valeur du deuxième argument. L'appel exemle.sh a b c d attribue la valeur b à $2.
$3, $4,...$n
Comme précédemment pour les paramètres 3,4,...n.
$* ou $@
Ces variables contiennent la liste complète des arguments passés au script. Par exemple, si on lance la commande exemple.sh a b c d e f, la variable $* aura pour valeur a b c d e f.
$0
Cette variable contient le nom du script, tel qu'il a été invoqué pour son exécution. Si on exécute notre script avec l'appel suivant: ./exemple.sh la variable $0 aura pour valeur ./exemple.sh.
$$
Cette variable contient le numéro du processus qui exécute le script. En effet, sous UNIX,à chaque fois qu'on lance un programme (ou un script), un nouveau processus est créé auquel le système d'exploitation attrbue un identifiant unique, le numéro de processus (ou PID). Ce numéro étant unique, on peut l'utiliser dans un script pour créer un fichier ou répertoire temporaire en ajoutant $$ au nom du fichier/répertoire à créer: mkdir travail_$$.

Les énoncés de contrôle

Énoncé if-then-else

Les scripts bash peuvent exécuter des branchements selon une condition :

if [ test ]
then
     commandes à exécuter si test est vrai
fi

ou:

if [ test ]
then
     commandes à exécuter si test est vrai
else
     commandes à exécuter si test est faux
fi

ou encore:

if [ test ]
then
     commandes à exécuter si test est vrai
elif [ test]
then
     commandes à exécuter si test est vrai
else
     commandes à exécuter si test est faux
fi


La condition (test) à évaluer peut impliquer les propriétés de fichiers ou répertoires, ou bien simplement être une comparaison de nombres ou de chaînes de caractères.

Attention : Le caractère ‘[‘ utilisé ici est en fait une commande (/usr/bin/[) servant à évaluer l’expression test. Il faut donc mettre des espaces (blancs) avant et après les symboles ‘[‘ et ‘]’ .

Voici quelques conditions très utilisées:

-s fichier
retourne vrai si fichier existe et est non vide
-f fichier
retourne vrai si fichier est un fichier ordinaire
-d fichier
retourne vrai si fichier est un répertoire
-r fichier
retourne vrai si on peut lire fichier
-w fichier
retourne vrai si on peut écrire dans fichier
-x fichier
retourne vrai si fichier est exécutable
$X -eq $Y
vrai si X est égal à Y (comparaison d'entiers)
$X -ne $Y
vrai si X n’est pas égal à Y (comparaison d'entiers)
$X -lt $Y
vrai si X est plus petit que Y (comparaison d'entiers)
$X -gt $Y
vrai si X est plus grand que Y (comparaison d'entiers)
$X -le $Y
vrai si X est plus petit ou égal Y (comparaison d'entiers)
$X -ge $Y
vrai si X est plus grand ou égal à Y (comparaison d'entiers)
$X ! -gt $Y
vrai si X n’est pas plus grand que Y (! inverse le résultat de la relation) (comparaison d'entiers)
"$A" = "$B"
vrai si la chaîne A est égale à la chaîne B (comparaison de chaînes)
"$A" != "$B"
vrai si la chaîne A n’est pas égale à la chaîne B (comparaison de chaînes)
$E -a $F
vrai si E et F sont tous les deux vrai (ET)
$E -o $F
vrai si E ou F est vrai (OU)

Exemples

nombre=100
if [ $nombre -eq 100 ]
then
    echo 'nombre egal 100'
fi
nombre=100
if [ $nombre -eq 100 ]
then
   echo 'nombre egal 100'
else
   echo 'nombre different de 100'
fi


nombre=100
if [ $nombre -lt 100 ]
then
   echo 'nombre plus petit que 100'
elif [ $nombre -gt 100 ]
then
    echo 'nombre plus grand que 100'
else
    echo 'Nombre est egal a 100'
fi

Les boucles for

Lorsque nous voulons, par exemple, faire un ensemble d’opérations sur plusieurs fichiers, nous pouvons utiliser une boucle for:


for var in liste
do
    commande ( utilisant $var)
done

Ici, liste représente une suite de valeurs qui seront attribuées à la variable var une après l'autre à chacune des itérations de la boucle. Par exemple l'énoncé for val in 1 2 3 4 5 provoquera cinq itérations au cours desquelles la variable val prendra succesivement pour valeur 1,2,3,4 et 5.

Exemples

Le script suivant trie (commande sort) le contenu de tous les fichiers du répertoire courant ayant dans leur nom l’extension .txt et écrit le résultat dans un fichier ayant pour nom le même nom auquel on ajoute le suffixe trie:


#!/bin/bash
#fichier triertout.sh
for f in *.txt
do
   echo On trie le fichier $f
   cat $f | sort > $f.trie
   echo le fichier trie est conserve dans le fichier $f.trie
done
          

Le script suivant reçoit une année en paramètre et affiche un calendrier de chaque mois de l’année sur la sortie standard (l’écran) tout en s’assurant que l’année est entre 1 et 9999. Notons l'utilisation de l'opérateur logique -o dans le second énoncé if.

#!/bin/bash 
#fichier calendrier.sh
#
#On s'assure d'avoir recu une annee en parametre
#
if [ $# -ne 1 ] 
then 
    echo usage $0 annee 
    exit 1 
fi 
#
#On initialise la variable annee en on valide sa valeur
#
annee=$1 
if [ $annee -lt 1 -o $annee -gt 9999 ] 
then 
    echo Annee doit etre entre 1 et 9999 
    exit 1 
fi 
#
#On boucle sur les 12 mois avec la commande seq()
#et on affiche un calendrier pour chacun des mois de l'annee 
#
for mois in $(seq 01 12) 
do 
    cal ${mois} ${annee} 
done

Boucle while

Une autre forme de boucle est la boucle while:

while [ test ]
do
     commandes     (à être exécutées tant que test est vrai)
done


Exemples

Le script suivant attend que le fichier donnees_entree.txt soit créé et soit non vide puis il se termine. Mentionnons l'emploi de l'opérateur ! pour inverser la condition de l'énoncé while (c.a.d., on boucle tant que le fichier donnees_entree.txt n'existe pas).

#!/bin/bash
#fichier attend1.sh
while [ ! -s  donnees_entree.txt ]
do
   echo on attend ...
   sleep 5
done
echo le fichier donnees_entree.txt est pret
exit 0

Vous pouvez terminer l’exécution d’un script n’importe quand en utilisant l’énoncé exit. Le script suivant est donc équivalent:

#!/bin/sh
#fichier attend2.sh
while true
do
   if [ -s donnees_entree.txt ]
   then
       echo donnees_entree.txt est pret
       exit 0
   fi
   echo on attend...
   sleep 5
done

Le script suivant initialise un compteur à 1 et boucle tant que le compteur n'a pas pour valeur 100:

#/bin/bash
#
#Initialisation du compteur
#
compteur=1
while [ $compteur -lt 100 ]
do
      #incrementer le compteur
      compteur=$(( $compteur + 1 ))
done


Enoncé case

L’énoncé case permet d’effectuer un branchement d’exécution en comparant la valeur d'une variable à plusieurs patrons possibles. Lorsqu'il y a concordance entre la valeur de la variable et un patron, les commandes associées à ce patron sont alors exécutées.

case variable in
  patron1)
    commande  (exécuté si  variable concorde à patron1)
    ;;
  patron2)
    commande
    ;;
   etc.
esac

Exemples

L'exemple suivant détermine si la valeur d'un nombre positif est inférieure à 10, entre 10 et 99 ou supérieure à 100:

#!/bin/bash
nombre=89
case $nombre in 
  [0-9][0-9][0-9]*)    
     echo nombre plus grand ou egal 100
     ;; 
  [0-9][0-9])    
     echo nombre entre 10 et 99
     ;; 
  [0-9])    
     echo nombre entre 0 et 9
     ;; 
esac

L’exemple suivant tente de déterminer la nature de chacun des fichiers passés en paramètre, soit à partir de son nom ou de son extension. On remarquera dans l’exemple:

  • l’emploi de l’opérateur « | » (OU) pour indiquer des patrons multiples ;
  • l'emploi de « * » utilisé pour indiquer n’importe-quoi ;
  • la syntaxe $(commande) qui permet d’exécuter une commande et dans récupérer le résultat. Ici, le résultat de $(file -b ...) est récupéré et affiché par la commande echo. Voir Capture du résultat d'une commande.
#!/bin/bash
#fichier natfic.sh
#
#On boucle sur tous les fichiers qui ne sont 
#pas des répertoires et qui ne sont pas executables
#
for fichier in $*
do
   if [ -f $fichier -a ! -x $fichier ]
   then
      case $fichier in
          core)
              echo "$fichier : un fichier core"
              ;;
          *.c)
              echo "$fichier : un programme en C"
              ;;
          *.cpp|*.cc|*.cxx)
              echo "$fichier: un programme en C++"
              ;;
          *.txt)
              echo "$fichier: un fichier texte"
              ;;
          *.pl)
              echo "$fichier: un script en langage PERL"
              ;;
          *.html|*.htm)
              echo "$fichier: un fichier WEB"
              ;;
          *)
              echo "$fichier: semble etre un fichier " $(file -b $f)
              ;;
     esac
  fi
done

Capture du résultat d'une commande

Tous les programmes et commandes UNIX peuvent être exécutés aussi bien à partir d’un script qu’à l’aide de l’invite de commande. Nous pouvons capturer le résultat d’une commande et remiser ce résultat dans une variable ou le passer en paramètre à une autre commande en utilisant la syntaxe $(commande) ou encore `commande` (accents graves):

Exemple

Ce script retourne le nombre de lignes du fichier qu’on lui passe en paramètre:

#!/bin/bash
ficin=$1
nblignes=$(wc -l ${ficin})    
echo "le fichier ${ficin} contient ${nblignes} lignes"

Expressions arithmétiques

Pour évaluer des expressions mathématiques simples en bash, on utilise la syntaxe $((expression)). Par exemple, on peut incrémenter une variable dans un script de la façon suivante:

#!/bin/bash
ligne=20
#Ajouter 1 a la variable ligne
ligne=$(( $ligne + 1 ))

On peut utiliser:

les opérateurs de calcul
+, -, *, /, % (modulo), ** (exposant).
les opérateurs logiques
<, <=, =, !=, >=, >. Ces opérateurs retournent 1 pour vrai et 0 pour faux.
les opérateurs de bits
| (OU) , & (ET).

Exemple

Script servant à déterminer si une année est bissextile ou non. Le script se termine à l'aide de la commande exit qui retourne un code:

  • 0 : Année bissextile ;
  • 1 : Année régilière ;
  • 2 : Erreur lors de l'appel.

On peut récupérer la valeur du code de retour du script grâce à la variable $?.

#!/bin/bash
#
#Script bisextile
#Code de retour :
#         0 si annee bisextile
#         1 si annnee ordinaire
#         2 erreur d'appel
#
#Sequence d'appel : bisextile.sh annee
#
IS_LEAP=0
NOT_LEAP=1
ERR_APPEL=2
if [ $# -eq 0 ]
then
   echo usage:
   echo $0 annee
   exit ${ERR_APPEL}
fi
annee=$1 
modulo=$(( $annee % 400))
if [ $modulo -eq 0 ]
then
  exit ${IS_LEAP}
fi
modulo=$(( $annee % 100))
if [ $modulo -eq 0 ]
then
  exit ${NOT_LEAP}
fi
modulo=$(( $annee % 4))
if [ $modulo -eq 0 ]
then
  exit ${IS_LEAP}
fi
#
exit ${NOT_LEAP}

Exemple d'appel pour récupérer le code retour:

-bash-3.2$ ./bissextile.sh 1998
-bash-3.2$ echo $?
 1
-bash-3.2$
-bash-3.2$ ./bissextile.sh 2000
-bash-3.2$ echo $?
 0
-bash-3.2$


Les fonctions

Il est possible d’écrire et utiliser des fonctions dans un script bash. Pour définir une fonction, on utilise la syntaxe suivante:

function nom()
{
   commandes
}

Remarques concernant les fonctions:

Voici quelques particularités des fonctions et de la portée des variables dans un script bash:

  • Les fonctions doivent être définies avant d'être appelées dans le script; on les place avant le code formant le corps du script;
  • Les variables définies dans le corps du script (c.a.d., à l'extérieur du code des fonctions) sont aussi définies dans le corps des fonctions ;
  • Les variables définies dans le corps d'une fonction sont connues du script et de toutes les autres fonctions ;
  • Pour limiter l'existence (la portée) d'une variable à une fonction, celle-ci doit utiliser l'attribut local lors de la définition de la variable.

Le script fonctions.sh suivant démontre la portée des variables:

#!/bin/bash
#fichier fonctions.sh
#
# fc1 fait un echo de la variable globale val
# telle que définie ailleur dans le script
#
function fc1()
{
   echo fc1 val = $val
}
#
# fc2 modifie la valeur de la variable globale val
#
function fc2()
{
  val=2
  echo fc2 val = $val
}
#
#
# fc3 definie et initialise une variable locale val
# qui est differente de la variable globale val
#
function fc3()
{
  local val=3
  echo fc3 val = $val
}
#
val=0
echo 'corps du script, val' = $val
fc1
fc2
echo 'corps du script, val' = $val
fc1
fc3
echo 'corps du script, val' = $val

Voici le résultat de l'appel à ce script:

-bash-3.2$ ./fonctions.sh
corps du script, val = 0
fc1 val = 0
fc2 val = 2
corps du script, val = 2
fc1 val = 2
fc3 val = 3
corps du script, val = 2
-bash-3.2

On y remarque que la variable val définie et utilisée dans le corps du script et dans les fonctions fc1 et fc2 est une seule et même variable. La variable locale val définie et initialisée par fc3 est interne à la fonction.

Exemples

Dans cet exemple, on défini deux fonctions soit usage() et addition(). La fonction usage affiche à l’écran la séquence d’appel du script alors que addition() additionne les deux nombres fournis en paramètre. Les variables $1 et $2 utilisées dans addition() sont internes à la fonction et représentent les paramètres qui lui sont passés et non ceux qui sont passés au script lui-même.

#!/bin/bash 
#fichier add1.sh 
# 
#Demonstration de definition d'une fonction
#Version echo 
# 
function usage() 
{ 
   echo usage $0 nombre1 nombre2 
} 
#
function addition() 
{ 
   echo $(( $1 + $2 )) 
} 
#
if [ $# -ne 2 ] 
then 
   usage 
   exit 1 
else 
  nb1=$1
  nb2=$2
  total=$(addition $nb1 $nb2) 
  echo Total = $total 
fi 
exit 0

Voici une autre version du même script qui retourne le résultat de l'addition dans une variable globale qui est ensuite utilisée par le corps du script:


#!/bin/bash 
#fichier add2.sh 
# 
#Demonstration de definition d'une fonction
#Version variable globale 
# 
function usage() 
{ 
   echo usage $0 nombre1 nombre2 
} 
#
function addition() 
{ 
   total=$(( $1 + $2 )) 
} 
#
if [ $# -ne 2 ] 
then 
   usage 
   exit 1 
else 
  nb1=$1
  nb2=$2
  addition $nb1 $nb2 
  echo Total = $total 
fi 
exit 0

Récupération des paramètres d'appel avec getopts

La fonction interne getopts permet de récupérer les paramètres d'appel d'un script et de les valider. Lorsqu'on fait un appel à getopts, le shell lui passe automatiquement les paramètres d'appel du script (variable $@) pour traitement.

getopts permet d'automatiser le passage de paramètres avec la syntaxe script -clef1 valeur -clef2 ...

C'est l'emploi de cette fonction qui permet de rédiger des scripts auxquels on peut passer des arguments sans ordre précis. Comparez, par exemple, l'emploi des scripts cal1.sh et cal2.sh. Les deux scripts affichent un calendrier pour un mois et une année donnée. Cal1.sh n'utilise pas getopts et s'attend à recevoir deux paramètres, soit mois et année, dans cet ordre. cal2.sh utilise la fonction getopts et les clefs -m et -a:

Appel de cal1.sh

-bash-3.2$ ./cal1 10 2012
    octobre 2012    
lu ma me je ve sa di
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
-bash-3.2$
-bash-3.2$ ./cal1 2012 10
cal: valeur de mois incorrecte : utilisez 1-12
-bash-3.2$

Appel de cal2.sh

-bash-3.2$ ./cal2 -m 10 -a 2012
    octobre 2012    
lu ma me je ve sa di
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
-bash-3.2$
-bash-3.2$ ./cal2 -a 2012 -m10
    octobre 2012    
lu ma me je ve sa di
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
-bash-3.2$

Séquence d'appel

getopts OPTSTRING VARNAME [ARGS...]
  • OPTSTRING contient la liste des clefs à reconnaitre (e.g., -a -b -c etc.);
    • On y inscrit le nom de chaque clef à traiter et si celle-ci requiert un argument, on ajouteà son non le symbole ' :';
    • Par exemple, si OPTSTRING a pour valeur « ai:o:d » ceci indique à getopts que le script s'attend à recevoir les clefs -a, -i, -o et -d.. De plus, les clefs -i et -o doivent être suivies d'une valeur. On pourra donc appeler notre script en lui passant des paramètres de la façon suivante :
      • monscript -a -i fic_entree -o fic_sortie;
      • monscript -ad -o fic_sortie;
      • monscript -o fic_sortie -da -i fic_entree;
  • VARNAME est une variable locale au script dans laquelle getopts copie le nom de la clef présentement en traitement. Si la clef n'est pas valide, VARNAME prend pour valeur «? ». Si la clef requiert un paramètre et que celui-ci est manquant, VARARG prend pour valeur « : »;
  • ARGS est une liste optionnelle de paramètres à traiter. Par défaut, le shell utilise $@.

On doit faire un appel à optargs pour chacune des clefs à traiter. Lorsqu'il traite une clef, getops fait les opérations suivantes :

  • Remise le nom de la clef dans la variable VARNAME;
  • Remise, le cas échéant, dans la variable globale OPTARG le paramètre associé à la clef traitée;
  • Remise dans la variable globale OPTIND l'index de la prochaine clef à traiter.

Exemples

Dans cet exemple, le script s'attend à traiter les clefs -h, -i, -a, -m et -o. Les clefs -i, -a, -m et -o doivent être suivies d'un argument. La fonction getopts copie la valeur de la clef présentement en traitement dans la variable local c. C'est donc avec c que l'énoncé case se fait.

############################################################ 
# Recuperation des parametres d'appel d'un script
# 
############################################################ 
while getopts hi:a:m:o:  c 
    do 
          case $c in 
         h)       usage 
                   exit 0;; 
         i)       REP_FSTD=$OPTARG;; 
         a)       ANNEE=$OPTARG;; 
         m)      MOIS=$OPTARG;; 
         o)       REP_SORTIE=$OPTARG;; 
         \?)     usage
                  exit 2;;
         esac 
    done

Le script cal2.sh qui retourne un calendrier pour un mois et une année à l'aide des clefs -m et -a:

#!/bin/bash
#
function usage()
{
    echo usage:
    echo $0 -m mois -a annee
}
# 
#S'assurer qu'on a quatre parametres
#
if [ $# -ne 4 ]
then
   usage
   exit 1
fi
while getopts m:a: c
do
  case $c in
    m) MOIS=$OPTARG;;
    a) ANNEE=$OPTARG;;
   \?)
       usage
       exit 1
       ;;
  esac
done
#
#Appel au programme cal
# 
cal $MOIS $ANNEE
#
exit 0