Les bases de la programmation shell
Procédures et paramètres
Comme nous l’avons déjà mentionné, le shell est à la fois un interpréteur de commandes et un langage de programmation destiné à l’écriture de procédures utilitaires. En complément des informations présentées dans le chapitre Utilisation du shell, nous allons présenter maintenant les bases de la programmation shell. Sans prétendre traiter le sujet dans son intégralité, nous souhaitons disposer de compétences de premier niveau qui nous permettront de lire et de maintenir des scripts existants ainsi que d’écrire des procédures simples d’exploitation.
Nous choisissons de présenter des syntaxes communes aux deux interpréteurs ksh et bash. Par expérience, ce tronc commun de syntaxe est souvent suffisant pour l’écriture de la majorité des procédures d’exploitation. Tous les exemples présentés fonctionnent donc à l’identique dans le contexte des deux interpréteurs.
1. Procédures
Des séquences de commandes et d’instructions shell peuvent être regroupées dans un fichier qui devient ainsi, à son tour, une nouvelle commande du système. On parle de procédure ou de script. Nous utiliserons indifféremment l’un ou l’autre des deux termes dans la suite de ce chapitre.
La procédure est interprétée par un shell fils du shell de connexion (mécanisme du fork, chapitre Processus et mécanismes) et elle hérite donc des variables...
Instructions de contrôle
Nous présentons maintenant les principales instructions de contrôle disponibles. Dans les résumés de syntaxe, nous faisons apparaître en gras les mots-clés du langage.
1. Tests
a. Tests simples
Pour effectuer des tests simples, plusieurs syntaxes sont possibles :
if commande
then
liste_de_commandes
fi
ou bien :
if commande ; then
liste_de_commandes
fi
ou bien :
if commande
then
liste_de_commandes
else
liste_de_commandes
fi
Dans les tests en shell, contrairement aux langages de programmation classiques, il est à noter que le if n’est pas suivi d’une expression booléenne mais bien d’une commande.
On considère qu’une commande est vraie lorsque son code retour est égal à zéro.
Il est possible de placer plusieurs commandes (notamment un pipeline) après le if. Dans ce cas, le test prend en compte le code retour de la dernière de ces commandes.
Exemple
if who | grep toto > /dev/null
then
echo "toto est connecte"
else
echo "toto n'est pas connecte"
fi
La redirection vers /dev/null permet d’éliminer la sortie de la commande grep en cas de succès. Nous ne sommes intéressés que par son code retour.
Il conviendrait aussi de fournir une expression régulière à la commande grep pour que le test soit vraiment correct.
b. Tests séquentiels
Il est possible d’effectuer des tests séquentiels. On exécutera la liste de commandes associée au premier test vrai pour quitter ensuite l’instruction. Si aucun test n’est vrai, les commandes associées à une éventuelle clause else seront exécutées.
Deux syntaxes sont disponibles :
if commande
then
liste_de_commandes
else if commande
then
liste_de_commandes
else if commande
then
liste_de_commandes
..........
..........
else
liste_de_commandes
fi
fi
fi
ou bien :
if commande
then
liste_de_commandes
elif commande
then
liste_de_commandes
elif commande
then
liste_de_commandes
..........
..........
else
liste_de_commandes
fi
L’utilisation de la syntaxe...
Fonctionnalités complémentaires
1. Tableaux
Le shell permet la manipulation de tableaux monodimensionnels. Les syntaxes disponibles sont les suivantes :
Affectation globale
set -A tab valeur1 valeur2 valeur3 # Syntaxe ksh
tab=(valeur1 valeur2 valeur3) # Syntaxe bash
Désignation des éléments (les indices démarrent à zéro)
echo $tab # le premier élément
valeur1
echo ${tab[2]} # le troisième élément
valeur3
echo ${tab[*]} # tous les éléments
valeur1 valeur2 valeur3
Affectation d’un élément
tab[0] =toto
echo ${tab[*]}
toto valeur2 valeur3
Nombre d’éléments
echo ${#tab[*]}
3
2. La variable IFS
La variable système IFS précise le séparateur de champs lors des lectures (read) et des résultats de substitutions de commande ou de paramètres. Sa valeur par défaut est « espace, tabulation et interligne ».
Elle peut être modifiée ponctuellement pour traiter, par exemple, des lignes avec un séparateur différent.
Exemples
$ echo $PATH
/home/michel/bin:/usr/local/bin:/usr/bin:/bin:/usr/games
$ IFS=:
$ for i in $PATH
> do
> echo $i
> done
/home/michel/bin
/usr/local/bin
/usr/bin
/bin
/usr/games
$ read var1 var2
bonjour:salut
$ echo $var1
bonjour
$ echo $var2
salut
$
Dans ces exemples, après modification de la variable IFS, une boucle for nous permet d’abord de traiter le contenu de la variable PATH. Ensuite, une lecture au clavier de deux variables nous impose d’utiliser le nouveau séparateur en lecture.
3. La commande interne shift
La commande interne shift permet de modifier la liste des paramètres en décalant ceux-ci vers la gauche.
$1 disparaît, $2 devient $1, $3 devient $2 etc. Bien entendu, le nombre de paramètres $# est décrémenté et la variable $* est mise à jour.
Concrètement, cette commande est utile dans la programmation des procédures à options (voir la commande getopts dans ce même chapitre). Elle permet en effet de se positionner au début de la liste des véritables arguments, après...
Quelques exercices
Les scripts que l’on se propose d’écrire ici peuvent indifféremment être écrits en ksh ou en bash.
Énoncés
1. Tests séquentiels
# ex1 : Exemple de tests séquentiels
# On attend un argument exactement
# Si c'est un répertoire accessible, on fait la liste des fichiers.
# Sinon, s'il s'agit d'un "fichier texte", on affiche sa taille en octets
# Sinon, on affiche un message d'erreur
2. Boucle for
# ex2 : Exemple de boucle for
# Liste des commandes du répertoire donné en argument.
# Si aucun argument, on traite le répertoire courant.
3. Boucle while
# ex3 : Exemple de boucle while et de lecture clavier
# Récupérer l'heure système sous la forme hh:mm.
# Lire au clavier une heure postérieure (au même format).
# Si la saisie est vide, quitter le programme.
# Attendre cette heure via une boucle qui recalcule l'heure système
# toutes les 5 secondes.
4. Aiguillage
# ex4 : Exemple d'aiguillage
# Proposer un menu
cat <<FIN
1. QUELLE HEURE EST-IL ?
2. CHANGEMENT DE REPERTOIRE COURANT
== Les choix suivants concernent le répertoire courant ==
3. LISTE DES COMMANDES
4. SAUVEGARDE DES FICHIERS ORDINAIRES (ARCHIVE TAR ZIPPEE)
5. LISTE D'UNE ARCHIVE
6. RESTAURATION INTERACTIVE D'UN FICHIER
0. SORTIE
FIN
# Il faudra indiquer dans cette variable l'emplacement des fichiers
# à inclure.
rep_include=$HOME/bin
# Il faudra inclure des définitions de variables et de fonctions.
. ${rep_include}/mes_variables 2> /dev/null
. ${rep_include}/mes_fonctions 2> /dev/null
5. Gestion des signaux
# ex5 : Exemple de traitement des interruptions
# Faire grossir un fichier temporaire toutes les 5 secondes ...