Unix Shell Scripting

Introduction

Le shell est un outil fondamental dans les systèmes Unix. Il sert principalement d'interface entre l'utilisateur et le système d'exploitation. C'est lui en particulier qui s'occupe d'exécuter les commandes que l'utilisateur saisit. Mais sa fonction ne s'arrête pas là, il offre à l'utilisateur toute une palette de service, tel que la possibilité de manipuler les processus comme par exemple les communications par le biais des tubes, de manipuler des fichiers en utilisant les redirections. Il offre aussi un environnement d'exécution qui affecte aussi bien son comportement que le comportement des processus dont il a la charge.

De part la diversité des systèmes Unix, et l'ancienneté de ces systèmes, il est normal que cela se ressente dans la variété des shells qui sont disponibles. Au fil des ans, certains shells se sont distingués, le korn shell (ksh), les shells tcsh et csh, le bourne shell (sh) et son poussin le bash. Il existe d'autres shells mais leur diffusion est limitée. Une caractéristique des shells est de posséder un langage de script. Un langage de script se distingue des langages de programmation habituels du fait qu'un script n'est pas transformé en exécutable binaire autonome qui contient du code machine, mais a besoin d'un programme qui interprète le script à chaque exécution de celui-ci.

Comme le shell a pour vocation d'aider l'utilisateur dans la gestion des fichiers et des processus, les langages de script associés sont particulièrement adaptés à cet usage. L'écriture de script pour faciliter l'administration système est très courant dans l'industrie, qui bien souvent tend à minimiser les coûts humains de maintenance et gestion de ces systèmes.

Dans la suite de ce document, nous nous attacherons particulièrement au langage de script 'bourne shell' (sh) qui est installé par défaut sur tout système Unix. Ce shell d'ailleurs ne présente pas beaucoup d'intérêt pour une utilisation interactive, mais le fait qu'il soit installé en standard sur tout type de système Unix, garantie la portabilité des scripts.

Pour écrire un shell script il n'est pas besoin d'outil spécifique, seul votre éditeur de texte préféré fera l'affaire (emacs, vi , .).

Il existe deux manières d'invoquer un script shell. La première est d'exécuter le shell dans lequel il est écrit, avec comme argument le nom du script (par exemple : sh monscript.sh). La deuxième manière consiste d'une part à rajouter au fichier comme première ligne un appel au shell dans lequel le script est programmé en commençant la ligne par ' # !' et en fournissant ensuite le chemin vers l'exécutable du shell en question ( dans notre cas cela donne : # !/bin/sh). Ensuite il suffit de rendre exécutable le script au yeux du système à l'aide de la commande chmod. Il suffit d'invoquer le script par son nom dans la ligne commande pour l'exécuter (par exemple ./monscript.sh).

Le langage sh

Le langage du shell sh possède des structures de control tel que IF..THEN..ELSE, FOR, WHILE. Il fournit plusieurs moyens d'accéder aux variables en lecture et en écriture. Il propose aussi une extension des possibilités de redirection, et un certain nombre de commande interne.

Création de variable

La syntaxe pour assigner une variable est la suivante : nom=valeur. Si cette variable n'existait pas auparavant, elle est de facto crée, sinon l'ancienne valeur est écrasée. Une variable fraîchement crée est toujours une variable locale. C'est à dire qu'elle appartient au contexte du shell en cours d'exécution et à ses processus fils. Pour assigner une valeur qui contient des espaces, il suffit d'englober la valeur autour de double guillemet (par exemple toto="toto ta"). Il est important qu'il n'y ai pas d'espace entre '=' et la valeur.

Pour accéder aux variables, il existe plusieurs moyens :

     . $nom est remplacé par la valeur de la variable
     . ${nom} est remplacé par la valeur de  la  variable,  cela  est  utile
       lorsque la chaîne de caractère est immédiatement suivie par une autre
       chaîne (${poids}kg, affichera 17kg si poid=17)
     . ${nom-mot} est remplacé par la valeur  de  nom  si  la  variable  est
       initialisée et par mot sinon
     . ${nom=mot} assigne la valeur mot à nom si cette  variable  n'est  pas
       initialisée et est remplacé ensuite par la valeur de nom.
     . ${nom ?mot} est remplacé par la valeur de nom si cette  variable  est
       initialisée et sinon renvoie mot sur la sortie d'erreur et le  script
       se termine. Si mot est vide alors un message  d'erreur  standard  est
       affiché.

Pour initialiser une variable à partir de l'entrée standard (généralement le clavier ou une redirection) il faut utiliser la commande read. Elle assigne successivement les mots entrés aux noms de variables donnés en argument. Si le nombre de mot est supérieur aux nombre de variable, alors la dernière variable écope du surplus de mot.

Pour exporter une variable locale à l'environnement, il faut utiliser la commande export ( par exemple export nom=toto). La commande env permet d'afficher le contenu de l'environnement.Pour rendre une variable nom modifiable, il suffit d'utiliser la commande readonly, avec le nom de la variable en argument.

Le shell sh fournit un certain de variable locales prédefinies.

    . $@ est la liste des paramètres de la dernière commande exécutée
    . $# est le nombre de paramètre de la dernière commande exécutée
    . $? est la valeur de sortie de la dernière commande exécutée
    . $$ est le PID du processus en cours d'exécution  (généralement  le
          shell qui interprète le script.

Le shell sh possède un outil d'évaluation d'expression arithmétique nommé expr. Il supporte une variété d'opérateur : */%, +-, => >= < <= !=, &, | . Il supporte aussi des opérations sur les chaînes de caractères comme : match, substr, index, lenght.

Le shell sh possède un outil de test d'expression judicieusement appelé test. Une liste non exhaustive des options est donnée :

    . -f nomdefichier, teste si le fichier nomdefichier existe
    . -l chaîne, teste sur la chaîne est non vide
    . -n chaîne, teste si la chaîne a au moins un caractère.
    . -z chaîne, teste si la chaîne est vide
    . str1 = str2 est vrai si str1 est égal à str2
    . str1 != str2 est vrai si str1 est différent de str2
    . int1 -eq int2 est vrai si int1 est égal à int2
    . int1 -ne int2 est vrai si int1 est différent de int2
    . int1 -gt int2 est vrai si int1 est plus grand que int2
    . int1 -ge int2 est vrai si int1 est plus grand ou égal à int2
    . int1 -lt int2 est vrai si int1 est plus petit que int2
    . int1 -le int2 est vrai si int1 est plus petit ou égal à int2
    . !expr est vrai si expr est faux
    . expr1 -a expr2 est vrai si exrp1 et expr2 sont vrais
    . expr1 -o expr2 est vrai si expr1 ou expr2 sont vrais.

Les structures de contrôle

La première structure est case .. in .. esac, qui permet plusieurs branchements suivant la valeur d'une variable.

Par exemple :

           case $toto in
            "1")
                 date
            ;;
            "2")
                 pwd
            ;;
              *)
           echo choix illégale
            ;;

La structure for.. do ..done permet de faire des traitement sur une variable qui va prendre des valeurs successives, ces Valeurs étant fournit par une liste si cette variable n'est pas déjà une liste.

Par exemple :

           for i in *.zip
           do
              gunzip $i
           done

La structure if ..then .. fi permet de faire des branchements conditionnels suite à un ou plusieurs tests.

Par exemple :

           if test $nombre -eq 0
           then echo 0
           else
             echo non zero
           fi

Il est possible de faire un test de manière différente, nous aurions pu écrire '[ $nombre -eq 0]' à la place de 'test $nombre -eq 0'.

La structure while ..do ..done évalue une série de commande et exécute une deuxième série de commande si la première a réussi.

Par exemple :

           while [ $x -le $1]
           do
              echo $x
              x=`expr $x + 1`
           done