Programmation et scripts Bash
Introduction
Ce chapitre explique comment automatiser l’exécution de travaux à partir des éléments étudiés précédemment. Les fonctionnalités abordées permettront à l’utilisateur de créer ses propres scripts shell, programmes développés en langage shell.
En outre, le lecteur pourra comprendre et personnaliser les nombreux scripts déjà fournis avec le système Linux.
Scripts shell
Un script Bash est un fichier texte contenant une suite de commandes telles que celles exécutées sur la ligne de commande.
Toute ligne de commande valide peut faire l’objet d’un script shell pour un traitement par lot (batch). Inversement, tout code écrit dans un script Bash peut être saisi directement sur la ligne de commande.
Dans cet exemple, le fichier script porte le nom prog.sh :
[nicolas]$ cat prog.sh
date
whoami
var=12
echo $var
1. Appel et exécution
Les modifications de l’environnement du shell appelant dépendent de la manière dont le script shell est appelé.
bash prog.sh
La première façon d’appeler un script shell consiste à lancer un nouveau bash avec le nom du script en argument. Ce nouveau shell traite alors les instructions contenues dans le fichier comme si elles avaient été saisies au clavier.
Il en résulte la création d’un sous-processus bash qui se termine automatiquement à la fin de l’exécution du script.
[nicolas]$ cat prog.sh
date
whoami
var=12
echo $var
[nicolas]$ echo $var
[nicolas]$ bash prog.sh
dim. avril 1 16:01:06 CEST 2018
nicolas
12
[nicolas]$ echo $var
Toute modification de l’environnement - comme le changement de répertoire ou la définition de variables - n’intervient que sur le sous-shell créé temporairement. Il n’y a aucune modification du shell initial.
On peut représenter ce type d’appel de script comme suit :
Le premier shell lancé sur un terminal (appelé shell de connexion) est préfixé par un tiret dans la liste des processus. C’est la raison pour laquelle son nom est -bash dans le schéma précédent, alors que le sous-shell est nommé bash.
Le shell bash est un sous-processus du shell de connexion -bash. De même, les commandes externes date et whoami sont des sous-processus du shell bash tandis que l’affectation de la variable var et son affichage...
Codes retour
Toute commande sous Linux retourne un code à la fin de son exécution. Ce code témoigne du bon déroulement de son exécution : zéro lorsque le programme s’est terminé correctement, différent de zéro dans le cas contraire.
Le code retour d’une commande est une valeur comprise entre 0 et 255.
Une même commande peut renvoyer différents codes si son exécution s’est mal déroulée, ceci pour plusieurs raisons. Par exemple, la commande cp peut échouer parce que le fichier source est inexistant, les droits d’écriture sur le répertoire cible insuffisants, le système de fichiers saturé, etc.
La variable spéciale $? contient le code retour de la dernière commande exécutée :
[nicolas]$ pwd
/home/nicolas
[nicolas]$ echo $?
0
[nicolas]$ ls zorglub
ls: zorglub: No such file or directory
[nicolas]$ echo $?
1
[nicolas]$ echo $?
0
Le dernier code retour est bien zéro car il indique que la commande précédente (echo $?) s’est bien déroulée. Pour réutiliser ultérieurement le code retour d’un programme, il faut prendre soin de l’enregistrer dans une variable :
[nicolas]$ ls zorglub
ls: zorglub: No such file or directory
[nicolas]$ cr=$?
[nicolas]$ pwd ...
Enchaînement de commandes
1. Exécution séquentielle
Au lieu de saisir les commandes les unes après les autres et d’attendre la fin de leur exécution avant de lancer la suivante, il est possible d’enchaîner plusieurs commandes sur la même ligne, en les séparant par un point-virgule :
[nicolas]$ date
dim. avril 1 16:06:40 CEST 2018
[nicolas]$ ps
PID TTY TIME CMD
4511 pts/1 00:00:00 bash
4613 pts/1 00:00:00 ps
[nicolas]$ date ; ps
dim. avril 1 16:07:12 CEST 2018
PID TTY TIME CMD
4511 pts/1 00:00:00 bash
4624 pts/1 00:00:00 ps
Ces enchaînements sont pratiques lorsque plusieurs commandes longues doivent être lancées successivement, sans que l’utilisateur ait à intervenir.
Les espaces autour du ; sur la ligne de commande ne sont pas obligatoires mais apportent une meilleure lisibilité.
Dans l’enchaînement cmd1 ; cmd2 ; cmd3, la commande cmd2 n’est exécutée qu’à la fin de la commande cmd1 ; de même, cmd3 est lancée lorsque cmd2 est terminée. Par contre, il n’y a aucun lien entre...
Variables spéciales
Certaines variables sont définies par le shell et peuvent être référencées par l’utilisateur dans les scripts shell.
1. $$, $PPID
La variable spéciale $$ contient le PID du shell en cours d’exécution tandis que $PPID (Parent Process ID) donne le PID de son processus père :
[nicolas]$ cat prog.sh
#!/bin/bash
echo "mon PID : $$"
echo "mon PPID : $PPID"
[nicolas]$ echo $$
1223
[nicolas]$ echo $PPID
1222
[nicolas]$ ./prog.sh
mon PID : 1380
mon PPID : 1223
[nicolas]$ . ./prog.sh
mon PID : 1223
mon PPID : 1222
2. $0
La variable $0 contient le nom du script en cours d’exécution tel qu’il a été appelé sur la ligne de commande :
[nicolas]$ cat prog.sh
#!/bin/bash
echo "mon nom : $0"
[nicolas]$./prog.sh
mon nom : ./prog.sh
[nicolas]$ /home/nicolas/prog.sh
mon nom : /home/nicolas/prog.sh
3. $1, $2, $3, ...
Les variables $1, $2, $3, etc. représentent les paramètres positionnels du shell, c’est-à-dire les arguments passés au script sur la ligne de commande :
[nicolas]$ cat prog.sh
#!/bin/bash
echo "premier paramètre : $1"
echo "deuxième paramètre : $2"
echo "troisième paramètre...
Commande test
La commande test permet d’effectuer un ensemble de tests sur les fichiers, les chaînes de caractères, les valeurs arithmétiques et l’environnement utilisateur.
Cette commande a un code retour égal à zéro lorsque le test est positif, et différent de zéro dans le cas contraire ; ceci permet de l’utiliser dans des enchaînements de commandes avec exécution conditionnelle (&& et ||) ou dans les structures de contrôle qui seront étudiées plus loin.
La commande test possède deux syntaxes : test expression et [ expression ] où expression représente le test à effectuer.
La seconde syntaxe offre une lecture plus aisée des conditions dans les structures de contrôle.
Les espaces après le crochet ouvrant et avant le crochet fermant sont obligatoires dans la syntaxe [ expression ]. D’une manière générale, tous les éléments de syntaxe de la commande test doivent être séparés par au moins un espace.
La suite de la section présente les principaux opérateurs composant les expressions de test de la commande.
1. Test de fichiers
-f fichier
Retourne vrai (code retour égal à zéro) si le fichier est de type standard (file) :
[nicolas]$ test -f /etc/passwd
[nicolas]$ echo $?
0
[nicolas]$ [ -f /etc ] || echo "/etc n'est pas un fichier standard"
/etc n'est pas un fichier standard
-d fichier
Retourne vrai si le fichier est de type répertoire (directory) :
[nicolas]$ fichier='/etc'
[nicolas]$ [ -d "$fichier" ] && echo "$fichier est un répertoire"
/etc est un répertoire
Lorsqu’on teste le contenu d’une variable, il est préférable d’encadrer celle-ci de guillemets pour éviter une erreur dans la syntaxe de la commande test si la variable n’est pas définie.
-r fichier
Retourne vrai si l’utilisateur possède le droit de lire le fichier (read).
-w fichier
Retourne vrai si l’utilisateur possède le droit de modifier le fichier (write).
-x fichier
Retourne vrai si l’utilisateur...
Opérations arithmétiques
Comme tout langage de programmation, le Bash offre les outils nécessaires au calcul arithmétique.
Pour cela, il existe principalement les commandes expr, let et bc.
1. expr
expr est une ancienne commande externe du Bash et n’est présentée ici que succintement car on lui préfère la commande let qui offre une syntaxe moins contraignante.
Cette commande renvoie sur sa sortie standard le résultat des expressions arithmétiques passées en argument. Sa syntaxe est expr expression.
Tous les éléments de l’expression doivent être séparés par au moins un espace et certains opérateurs arithmétiques sont préfixés par un antislash pour éviter toute confusion avec les caractères spéciaux du shell.
Opérateurs arithmétiques
Les opérateurs arithmétiques sont :
-
+ : addition
-
- : soustraction
-
\* : multiplication
-
/ : division entière
-
% : reste de la division entière ou modulo
-
\( et \) : parenthèses
Une substitution de commandes est généralement utilisée pour affecter le résultat de la commande expr à une variable. On obtient par exemple :
[nicolas]$ expr 2 + 3
5
[nicolas]$ expr 2 - 3
-1
[nicolas]$ expr 2 + 3 \* 4
14
[nicolas]$ expr \( 2 + 3 \) \* 4
20
[nicolas]$ resultat=$(expr 9 / 2)
[nicolas]$ echo $resultat
4
[nicolas]$ expr $resultat % 3
1
Opérateurs logiques
Les opérateurs logiques sont :
-
\| : ou logique
-
\& : et logique
-
\< : strictement inférieur
-
\<= : inférieur ou égal...
Commande read
La commande read interrompt l’exécution du shell jusqu’à ce que l’utilisateur ait introduit une chaîne de caractères (même vide) sur son entrée standard.
Les mots composant la chaîne de caractères saisie par l’utilisateur sont affectés aux variables dont les noms sont passés en argument à la commande read.
[nicolas]$ read a b c
1 2 3
[nicolas]$ echo $a ; echo $b ; echo $c
1
2
3
S’il y a plus de mots que de variables, la dernière variable contiendra la fin de la chaîne de caractères. Dans le cas inverse, les variables supplémentaires seront nulles :
[nicolas]$ read a b c
1 2 3 4
[nicolas]$ echo $a ; echo $b ; echo $c
1
2
3 4
[nicolas]$ read a b c
1 2
[nicolas]$ echo $a ; echo $b ; echo $c
1
2
Si la commande read est appelée sans argument, la réponse de l’utilisateur est affectée à la variable d’environnement $REPLY :
[nicolas]$ read
réponse de l'utilisateur
[nicolas]$ echo $REPLY
réponse de l'utilisateur
Il est possible de faire précéder la saisie d’une phrase avec l’option -p (prompt) de la commande read :
[nicolas]$ read -p "âge du capitaine ? " age
âge du capitaine ? 12
[nicolas]$ echo $age ...
Structures de contrôle
Les structures de contrôle permettent d’exécuter une ou plusieurs commandes suivant le résultat d’une expression.
L’expression fournie comme condition de la structure peut être n’importe quelle commande ; c’est le code retour de cette commande qui est déterminant. On utilise principalement les commandes test ou let comme conditions.
Seules les instructions if, for et while sont présentées ici.
1. L’instruction if
L’instruction if exécute une série de commandes si la condition indiquée est vraie.
La syntaxe générale est :
if condition
then
série de commandes si condition vraie
else
série de commandes si condition fausse
fi
Chaque mot-clé de la structure (if, then, else et fi) doit se trouver sur une ligne distincte mais la clause else n’est pas obligatoire.
Voici un exemple d’utilisation :
[nicolas]$ cat prog.sh
#!/bin/bash
if [ "$1" = "glop" ]
then
echo "c'est bien"
else
echo "c'est pas bien"
fi
[nicolas]$ ./prog.sh glop
c'est bien
[nicolas]$ ./prog.sh pasglop
c'est...
Exercice
Exercice
Pour chaque extension .conf, .cfg et .d, indiquez s’il y a plus de 10 fichiers ou non, dont le nom se termine avec cette extension dans le répertoire /etc.
Solution
La solution utilise une boucle for pour parcourir avec la variable $ext les différentes extensions proposées dans l’énoncé.
[nicolas]$ for ext in .conf .cfg .d
> do
> [[ $(ls -d /etc/*$ext | wc -l) -gt 10 ]] \
> && echo "il a plus de 10 fichiers se terminant par $ext" \
> || echo "il a moins de 10 fichiers se terminant par $ext"
> done
il a plus de 10 fichiers se terminant par .conf
il a moins de 10 fichiers se terminant par .cfg
il a plus de 10 fichiers se terminant par .d
La commande ls -d /etc/*$ext | wc -l, qui retourne le nombre de fichiers correspondants, est substituée ($(...)) par son résultat dans le test ([[... -gt 10 ]]) qui, lui, détermine s’il y en a plus de 10.
Si ce test est vrai (&&), on exécute la commande echo "...plus..." ; sinon (||), on exécute la suivante : echo "...moins...".