Initiation aux scripts shell pour automatiser des tâches - Structures de contrôle
Après avoir survécu à la canicule, nous repartons en quête de l'automatisation de tâches avec notre fidèle ami le Shell. Dans les articles précédents, nous avons vu comment créer un script Shell, puis la création et consultation de variables. Maintenant, afin de pouvoir analyser et réagir avec les données contenues dans ces variables, nous allons avoir besoin de structures de contrôle.
Les structures de contrôle sont le pilier fondamental de la programmation. Ce sont ces structures qui vont nous permettre de prendre une décision si une valeur spécifique a été atteinte. A nous de bien préciser laquelle (lesquelles). Avec les structures de contrôle dont nous allons parler ci-dessous et qui ont déjà été vues dans d'autres langages (voir article), il est possible de traiter la plupart des cas de figures connus en Shell.
Bonnes pratiques
Voici un petit rappel sur les bonnes pratiques à respecter lorsque vous faîtes de la programmation (peu importe le langage).
Bien commenter son code
En Shell, tout ce qui suit un # jusqu'à la fin de la ligne est considéré comme un commentaire. Un commentaire c'est du texte qui n'est pas interprété mais dont la présence doit être utile au lecteur pour bien comprendre le code. Même si vous ne le faîte pas pour les autres, faîtes le au moins pour vous, car relire son propre code 6 mois après peut s'avérer parfois compliqué.
La condition if
La condition if (SI) est une structure de contrôle indispensable à tout langage de programmation. C'est une base pour d'autres structures plus complexe et permet de prendre une décision face à un cas de figure défini.
SI [ $ELEMENT = VALEUR ] ALORS # Instruction à exécuter SINON # Instruction à exécuter FIN
Pour la suite de cet article, nous aurons besoin de vérifier si un dossier est vide ou pas. Une commande et un if vont nous permettre d'y parvenir simplement.
#!/bin/sh dossier="/tmp/mon_dossier" # Cette méthode permet d'obtenir le nombre de fichiers présent dans $dossier # La variable $# contient normalement le nom d'argument passé au script, ici il s'agit du nombre d'éléments retournés set - ls $dossier # Si il y a 0 fichier, le dossier est vide if [ $# = 0 ] then echo "$dossier est vide" else echo "$dossier non vide" fi
La structure case (switch)
Cette structure particulière, permet d'écrire simplement des instructions pour chaque cas de figure à traiter. Attention toutefois, ce n'est qu'une facilité d'écriture, car dans les faits, cette structure se comporte comme un if, else if, else.
Une petite représentation logique pour case
CAS $variable POUR Valeur1) # Instructions spécifiques si $variable = Valeur1 # Ne pas oublier de terminer chaque condition par un ;; (break dans les autres langages) sinon les autres conditions seront exécutées aussi. ;; ValeurX) # Instructions spécifiques si $variable = ValeurX uniquement ValeurY) # Instructions spécifiques si $variable = ValeurX et ValeurY ;; *) # Instructions à exécuter pour tous les autres cas de figure FIN
Voici un exemple d'utilisation de la structure case en shell. On retrouve très souvent cette structure dans les scripts de lancement et d'arrêt des services dans /etc/init.d sur Linux.
#!/bin/sh # On prend une décision en fonction du premier argument passé au script case $1 in start) echo "Démarrage du service" ;; stop) echo "Arrêt du service" ;; reload) reload=1 restart) if [ reload = 1 ] then echo "Rechargement du fichier de configuration" else echo "Redémarrage du service" fi ;; *) echo "Erreur! Les argument autorisé en paramètres sont : start, stop, reload, restart" esac
La fonction
En shell, la fonction est une structure de contrôle qui se comporte comme l'appel à un script externe. Il faut voir ça comme un script dans un script. Un fonction est une série d'instructions regroupées dans un espace logique que nous pouvons exécuter avec des arguments et qui peut retourner une valeur.
Voici la représentation logique d'une fonction
function nom_fonction { # Instruction à exécuter return code_retour } # Pour appeler la fonction nom_fonction arg1 arg2 # Afficher le code retour de la fonction echo $?
Voici une petite fonction en shell qui traite des arguments et retourne une valeur.
#!/bin/sh function multiplie { ret=`expr $1 "*" $2` return $ret } echo -en "2 multiplié par 3 = " multiplie 2 3 # Affiche le code retour de la fonction appelée echo $? # Affichera 6
La boucle while
Si vous devez traiter un fichier texte par ligne, alors cette boucle sera sans doute votre meilleure amie. En effet, la boucle while (TANT QUE) répète les mêmes instructions tant qu'une condition est vraie. Plutôt que de réécrire les mêmes instructions pour traiter (par exemple) chaque ligne d'un fichier, while vous propose de les réutiliser tant qu'il y a des lignes à traiter.
Voici ce que donne une boucle while logiquement.
TANT QUE condition vraie ALORS # Instructions à exécuter FIN
Afin d'illustrer l'utilisation de cette boucle, nous allons traiter un fichier texte ligne par ligne en shell.
#!/bin/sh # Le fichier à traiter est passé en argument $fichier = $1 # On définit le symbole séparateur de champs IFS=';' # Numéro de ligne courant nl=1 # On teste si le fichier existe if [ -f $fichier ] then # Pour chaque ligne du fichier lue, on applique le séparateur de champs ; qui sera remplacé par un espace afin de séparer les champs cat $fichier | while read ligne do # ajout.pl ajoute la ligne avec les champs à une base de données existante ajout.pl $ligne # Cas d'erreur if [ $? = 1 ] then echo "Erreur de traitement à la ligne $nl du fichier $fichier" fi # Incrémentation de la ligne en cours nl=expr $nl "+" 1 done else echo "Le fichier $fichier n'existe pas" return 1 fi
La boucle for
La boucle for va nous permettre comme son nom l'indique, d’exécuter en boucle la même série d'instructions pour tous les éléments d'une liste. Autrement dit, plutôt que de réaliser le même travail pour les 10 éléments d'une liste, nous allons l'écrire une fois et la structure l'appliquera à chacun des éléments.
Voici à quoi ressemble une boucle for d'un point de vue logique
POUR $ELEMENT DANS $LISTE FAIRE # Instructions à exécuter FIN
Un petit exemple pour illustrer son utilité. Nous avons des agences situées dans différentes villes en France : Auxerre, Beauvais, Bordeaux, Grenoble, Lyon, Nantes, Paris, Valence qui déposent des fichiers sur notre serveur. Tous les soirs à 19h, un script planifié (par Cron) s’exécute pour traiter les fichiers envoyés par les agences puis les supprime après traitement. On part du principe que le traitement archive les fichiers.
Voici en Shell ce que cela pourrait donner... Bien entendu, il est possible de faire mieux (autrement), mais c'est pour illustrer un exemple d'utilisation.
#!/bin/sh # La fonction liste le dossier passé en argument # Retourne 0 si le dossier est vide, sinon, retourne la liste des fichiers function liste_dossier { # Le premier argument de la fonction est le dossier à contrôler set - ls $1 # Si il y a 0 fichier, le dossier est vide if [ $# = 0 ] then return 0 # Si il y a des fichiers, on retourne la liste else return $* fi } # On définit la liste des agences (attention au respect des majuscules/minuscules dans le nom des dossiers) agences='Auxerre Beauvais Bordeaux Grenoble Lyon Nantes Paris Valence' # on définit le dossier racine des agences racine='/var/www/data/agences' # Pour chaque agence for $a in $agences do # Constitution du chemin vers le dossier de l'agence dossier=${racine}"/"{$a} # Analyse du dossier de l'agence $fichiers=liste_dossier $dossier if [ $fichiers = 0 ] then # Un peu d'affichage à titre informatif echo "Pas de fichier à traiter pour l'agence $a" else # Pour chaque fichier de l'agence, on exécute le traitement: traitement.pl (script en perl) for $f in $fichiers do echo "Traitement du fichier $f de l'agence $a" # On concatène (assemble) les variables afin de générer le chemin vers le fichier à traiter /home/spec/bin/traitement.sh $dossier"/"$f # Contrôle d'erreur du traitement if [ $? = 0 ] then echo "Traitement sans erreur pour le fichier $f de l'agence $a" else echo "Le traitement a rencontré des erreurs dans le fichier $f de l'agence $a" fi done fi done
Dans les prochains articles consacrés au shell, nous ferons le tour de quelques commandes utiles pour vos scripts.