Programmation Shell

Rappel : Qu'est-ce qu'un shell ?

Le shell, ou interface système est une couche logicielle qui fournit l'interface utilisateur d'un système d'exploitation. Il correspond à la couche la plus externe de ce dernier.

Il existe 2 types d'interfaces système/shell :

  • En ligne de commandes (CLI) : Le programme fonctionne alors à partir d'instructions saisies de comandes au clavier en mode texte.
  • Graphique fournissant une interface graphique pour l'utilisateur (GUI)

Deux méthodes d'accès au shell CLI sont possibles :

  • Le mode console qui affiche un shell CLI unique en plein écran, c'est l'interface homme-machine de base d'un système d'exploitation.
  • Le mode terminal qui émule une console et qui affiche en général le shell CLI dans une fenètre à l'écran.

Il existe différents shell CLI pour les ssystèmes Unix. Le plus utilisé est sans aucun doute "bash" (Bourne Again Shell).

L'interpréteur de commandes/shell est un programme faisant partie des composants de base d'un système d'exploitation. Le shell est un fichier exécutable chargé d'interpréter les commandes, de les transmettre au système et de retourner le résultat.

Nous allons voir ci-dessous, comment un shell bash est en mesure d'interpréter un fichier textuel, que l'on appelera un script, contenant une suite de commandes à exécuter.


1 - Ecrire un script

Fichier script

Pour écrire un script, il suffir de créer un fichier textuel avec la commande touch. Notre fichier aura une extension .sh

touch exemple.sh

Puis de le rendre exécutable :

chmod +x exemple.sh

On peut ensuite éditer notre fichier avec son éditeur préféré :

vim exemple.sh

La première chose à faire dans un script shell est d'indiquer l'interpréteur shell que nous allons utilisé. En effet, comme dit précédemment, la syntaxe du langage change un peu selon qu'on utilise sh, bash, ksh, etc.

En ce qui nous concerne, nous allons utiliser la syntaxe de bash, plus répandu sous Linux et plus complet que sh. Nous indiquons où se trouve le programme bash :

#!/bin/bash
echo "Hello World"

INFO : Le #! est appelé le sha-bang, (on écrit aussi shebang)

Executons maintenant le script :

$ ./exemple.sh
Hello World !

Exécution d'une commande

Apres le sha-bang, nous pouvons commencer à coder. Un script peut utiliser n'importe quelle commande accessible dans un terminal.

Commencons par quelque chose de simple, nous allons afficher le contenu du dossier courant en mode liste : ls -l

#!/bin/bash
echo "Hello World"
# Liste des fichiers du dossier courant
ls -l

Vous pouvez aussi ajouter des commentaires dans vos scripts. Tous les commentaires commencent par un #. Vous aurez surement remarqué que la ligne du sha-bang commence aussi par un #, c'est aussi un commentaire, considérez le comme une exception.

Executons le script :

$ ./exemple.sh
Hello World
total 4
-rwxr-xr-x 1 root root 77 May 11 15:54 exemple.sh

Mode débogage

Pour passer en mode débogage, on appelle directement le programme bash et on lui ajoute le paramètre -x

# bash -x exemple.sh
+ echo 'Hello World'
Hello World
+ ls -l
total 4
-rwxr-xr-x 1 root root 77 May 11 15:54 exemple.sh

Le shell affiche alors le détail de l'exécution du script. Toutes les lignes commençant par + sont les informations de débogage. Elles permettent de visualiser les commandes exécutées.

Gérer le retour d'une commande

Toutes les commandes renvoient un résultat sous la forme d'un entier positif ou nul. Lorsqu'il est nul, c'est que la commande s'est terminée proprement. Dans le cas contraire, le resultat indique un numéro d'erreur dont la ligique est propre à la commande.

Si dans une console on voit immédiatement le résultat, il faut faire en sorte que notre script puisse savoir si la commande a echoué ou non.

if [ $? -eq 0 ] ; then
    echo "Succès"
else
    echo "Erreur"
​fi

La variable $? permet de récupérer la valeur de retour de la dernière commande précédemment exécutée.

Cet exemple montre aussi la manière dont on contruit un bloc conditionnel. La syntaxe utilise les mots clés if then elif else if, l'utilisation des crochets et l'opérateur d'égalité pour les nombres qui est -eq.

Gérer les arguments

L'utilisateur de votre commande devra pouvoir passer des argumentes et vous devrez vous assurer que vous avez le bon nombre. Voici un exemple permettant de controler que nous avons bien deux arguments :

if [ $# -lt 2 ] ; then
    echo "too few arguments"
elif [ $# -gt 2 ] ; then
    echo "too many arguments"
fi

Concernant l'écriture des conditions, nous avons vu jusqu'ici les opérateurs d'agalité, d'infériorité stricte et de supériorité stricte pour les nombres Noter qu'il existe aussi :

  • -ge pour plus grand ou égal
  • -le pour plus petit ou égal
  • -ne pour différent

On peut également utiliser des opérateurs logiques tels que && pour ET et || pour OU :

if [ $# -eq 2 ] && [ $1 = $2 ] ; then
    echo "the two files must be distinct"
fi

Pour comparer des chaînes de caractères , on utilise simplement les opérateurs = ou !=

Dans l'exemple ci-dessus, on vérifie que les deux noms de fichiers passés en paramètres sont bien distincts.

Gérer les fichiers

Des vérifications peuvent être réalisées sur les fichiers. On peut par exemple vérifier l'existenced'un chemin :

if [ ! -e $MY_FILE ] ; then
    echo "$MY_FILE does not exists"
    exit 0
fi

Dans ce cas, si le chemin n'existe pas, on affiche un message et on quitte. Noter que la condition a été renversée par l'utilisation d'un point d'exclamation.

Voici quelques exemples de vérification de chemin possible :

  • Vérifier que le chemin est un fichier
if [ -f $MY_FILE ]
  • Vérifier que le chemin est un répertoire
if [ -d $MY_FILE ]
  • Vérifier que le chemin est un lien symbolique
if [ -L $MY_FILE ]

On peut également vérifier les conditions d'accès :

if [ -r $MY_FILE ]
if [ -w $MY_FILE ]
if [ -x $MY_FILE ]

Il est également possible de savoir si un fichier est plus récent (nt) ou plus ancien (ot) :

if [ $MY_FILE -nt $MY_FILE2 ]
if [ $MY_FILE -ot $MY_FILE2 ]

Saisies utilisateur

Il est possible de demander à l'utilisateur de saisir des informations :

read -p 'Votre nom: ' username

La commande read permet de demander une saisie à l'utilisateur. L'option -p permet d'afficher un message préalable, expliquant à l'utilisateur la nature attendue de la saisie. Sa réponse sera une chaîne qui sera stockée dans la variable username.

Nous pouvons ensuite vérifier que la saisie a bien eu lieu, voire même boucler tant que ce n'est pas le cas :

while [ -z $username ] ; do
    read -p 'Votre nom: ' username
done

Le -z permet de vérifier si la chaîne est vide (l'inverse est -n, permettant de vérifier qu'elle n'est pas vide)

La boucle while permet d'exécuter du code tant qu'une condition est vraie. Il existe également until qui permet de boucler jusqu'à ce que la condition soit vraie (soit le contraire de while) :

until [[ -n $password ]] ; do
    read -sp 'Mot de passe: ' password
done

Dans ce dernier exemple, on notera la préesnce de -s qui permet de ne pas afficher à l'écran la saisie de l'utilisateur.


2 - Script Avancée

Choix multiple

Précédemment, nous avons vu la structure pour traiter une condition à l'aide de if, then, elif, else et fi. Lorsque l'on souhaite répéter un test sur de multiples valeurs, il est plus simple de passer par l'utilisation de case :

read -n 1 p 'Que décide-t-on [O/n/_] ' choix
case $choix in
    _ )
        echo 'Choix reporté'
        ;;
    o|O )
        echo 'Oui'
        ;;
    n|N )
        echo 'Non'
        ;;
    * )
        echo 'Choix non compris'
        ;;
​esac

Analyse de l'exemple ci-dessus :

L'utilisation du -1 permet de limiter le nombre de caractères lors de la saisie de l'utilisateur. La syntaxe case utilise le mot clé in, nous permettant de déclarer différentes options. Ces options sont, le caractère souligné, les lettres o ou O, n ou N et enfin toutes les autres possibilités *. Le caractère | permet a plusieurs choix de suivre le même chemin logique dans l'éxécution du code. Le caractère parenthèse fermante permet de séparer les groupes de choix. Chacun de ces blocs se termine par deux points-virgules ;;.

Les Fonctions

Il est possible de déclarer des fonctions dans un script shell à l'aide de la syntaxe suivante :

function bonjour {
    echo 'Hello World'
}

Pour appeler une fonction :

bonjour

Si vous avez déjà pratiqué un tant soit peu un autre langage de programmation, vous devez vous demander où se trouvent les variables. En fait, il n'y a pas besoin de les déclarer dans la fonction, car par défaut, les variables d'une fonction sont disponibles dans $1, $2, etc. comme c'est le cas pour un programme. On dispose aussi du nombre d'arugments $#.

function bonjour {
    echo "Hello $1"
}

Il n'y a pas de parenthèses pour gérer les arguments lors de l'appel d'une fonction, on les sépare simplement par un espace :

bonjour World

Il faut également savoir qu'une variable déclarée à l'intérieur d'une fonctione est globale, sauf mention contraire et qu'une fonction peut accéder à l'ensemble des variables déclarées dans le contexte global :

#!/bin/bash
ma_variable_globale="_"
function a {
    ma_variable_globale="${ma_variable_globale}A"
}
function b {
    ma_variable_globale="${ma_variable_globale}B"
}
echo $ma_variable_globale
a
echo $ma_variable_globale
b
echo $ma_variable_globale

L'exécution du code ci-dessus va provoquer le résultat suivant :

$ ./exemple.sh
_
_A
_AB

Conclusions

  • Vous pouvez utiliser dans un fichier script l'ensemble des commandes que vous utiliserez dans une terminal interactif ; $? permet de récupérer les codes des retours.

  • Le nombre de paramètres est donnée par $#, les paramètres sont appelés individuellement par leur numéro $1, $2, ... sachant que $0 est le nom de la commande et $* permet d'itérer dessus.

  • Les conditions sont gérées à l'aide des mots clés if then elif else fi et les conditions s'appliquent de manière différente selon les variables (chaînes de caractères, entiers) ou que l'on souhaite faire des vérifications sur des chemins vers des fichiers.

  • Les itérations peuvent être réalisées avece while, until, ou encore for et les conditions multiples avec case.

  • La commande read permet de gérer la sortie utilisateur, son option -s permet de masquer la saisie de l'ulilisateur.

  • La déclaration d'une fonction est minimaliste ; toutes les variables sont globales, elles s'utilisent comme des commandes et leurs arguments sont gérés de la même façon.