Introduction à la programmation en shell
Par Rick Dearman
Adaptation française : Éric Jacoboni
Précédent Suivant Table des matières
1. Bourne/Bash : Introduction à la programmation en shell
Tôt ou tard, tout utilisateur UNIX aura besoin d'un script de shell (« shell script », en anglais). On peut simplement vouloir rendre une tâche répétitive plus facile, ou donner un coup de fouet à un programme existant. L'utilisation d'un shell script rend ce genre de choses plus faciles. L'un des premiers shell scripts que j'ai écrit consistait à traiter un répertoire plein de fichiers dont les noms étaient en majuscules afin de les transformer en minuscules :
1 #!/bin/sh 2 3 DIR=$1 4 5 for a in `ls $DIR` 6 do 7 fname=`echo $a | tr A-Z a-z` 8 mv $DIR/$a $DIR/$fname 9 done; 10 exit 0 11 # Ce script affichera une erreur si le nom du fichier est deja 12 # en minuscules et suppose que son paramètre est un répertoireLa première ligne indique au système le shell à utiliser, ici il s'agit de « sh », le Bourne shell (cela peut être un lien vers le shell bash). La combinaison des deux symboles #! est spéciale et indique à Linux quel shell exécutera les commandes contenues dans le fichier abritant le script. Elle n'est donc pas ignorée comme les autres lignes de commentaires. La ligne 3 initialise une variable nommée
DIR
avec la valeur du premier paramètre d'entrée (les paramètres débutent par$0
, qui est le nom du script shell, ici :LCem.sh
.À la ligne 5, on entre dans une boucle. Ici, il s'agit d'un for. Traduit en français, cela signifie que je veux faire quelque chose pour toute entrée « a » obtenue par la commande «
ls $DIR
». Le shell remplacera le nom de la variable$DIR
par ce qui a été entré sur la ligne de commande. La ligne 6 commence la boucle.À la ligne 7, on utilise deux commandes UNIX,
echo
ettr
. Nous produisons ainsi la valeur courante de$a
et nous l'envoyons àtr
(qui signifie translate). Dans notre cas, on traduit les minuscules en majuscules et on affecte le résultat à une variable nomméefname
.La ligne 8 renomme le fichier
$DIR/$a
, quel qu'il soit, en$DIR/$fname
. La ligne 9 indique au shell qu'il doit recommencer pour traiter toutes les autres variables$a
. Enfin, la ligne 10 termine le script avec un code erreur de 0. Les lignes 11 et 12 sont des commentaires.Ce script n'aurait pas été nécessaire pour changer les noms d'un ou de deux fichiers, mais puisque je voulais en modifier une centaine, il m'a évité beaucoup de saisie. Afin de pouvoir l'exécuter sur votre machine, vous devez le rendre exécutable avec
chmod +x LCem.sh
ou vous pouvez invoquer directement le shell en lui donnant le nom de votre script avecsh LCem.sh
. L'utilisation de la combinaison#!
indiquera au noyau quel shell doit être invoqué, c'est la façon de faire la plus commune. Rappelez vous que si vous utilisez#!
, le fichier doit avoir les droits d'exécution.Il y a seulement douze lignes mais elles nous en montrent beaucoup sur les scripts. On a appris comment dire au système de lancer le script en utilisant la combinaison
#!
. Cette combinaison d'une marque de commentaire et d'un point d'exclamation est utilisée pour lancer un script sans avoir à invoquer d'abord le shell. On a appris qu'un#
permet d'écrire un commentaire qui sera ignoré lorsque le script sera traité. On a appris comment passer des paramètres au script afin de récupérer la saisie de l'utilisateur et on sait comment initialiser une variable. On a eu un aperçu de l'une des nombreuses structures de contrôle utilisables pour contrôler les fonctionnalités d'un script.Ne vous affolez pas si vous ne maîtrisez pas encore tout cela. Nous allons maintenant expliquer certaines des structures de contrôle permettant de prendre une décision. La première est l'instruction «
if
». Dans tout langage de programmation, on veut pouvoir changer le déroulement du programme en fonction de diverses conditions. Par exemple, si un fichier est dans ce répertoire, on fait une chose, s'il n'y est pas, on fait autre chose. La syntaxe de la commande «if
» est la suivante :
if expression then commandes fiSi
expression
est vraie, les instructions du blocif
sont exécutées. Voici un exemple d'utilisation de l'instructionif
.
WhoMe.sh 1 #!/bin/sh 2 3 # initialise la variable ME avec le premier paramètre suivant la commande 4 ME=$1 5 6 # recherche de $ME dans le fichier passwd (sans affichage) 7 if grep $ME /etc/passwd > /dev/null 8 then 9 # si $ME est dans le fichier, afficher un message 10 echo "Vous êtes un utilisateur" 11 fiNotez l'utilisation des commentaires aux lignes 3, 6 et 9. Commentez bien tous vos scripts car quelqu'un pourrait avoir besoin de les étudier plus tard. Dans six mois, vous pouvez ne pas vous rappeler ce que avez fait et avoir vous-même besoin de ces commentaires.
En utilisant l'instruction
if
, on peut maintenant corriger certaines des erreurs du script de mise en minuscules. DansLCem.sh
, le script se plantait si l'utilisateur n'entrait pas un répertoire comme paramètre. Pour vérifier la présence d'une chaîne vide, on peut utiliser la forme suivante :
if [ ! $1 ]qui signifie « si
$1
n'existe pas ».La nouveauté est l'utilisation du point d'exclamation en tant qu'opérateur logique
NOT
. Utilison cela dans notre programme.
#!/bin/sh 1 if [ ! $1 ] 2 then 3 echo "Usage: `basename $0` nom_repertoire" 4 exit 1 5 fi 6 7 DIR=$1 8 9 for a in `ls $DIR` 10 do 11 fname=`echo $a | tr A-Z a-z` 12 mv $DIR/$a $DIR/$fname 13 done;Désormais, si l'utilisateur tape la commande sans le répertoire, le script se terminera en produisant un message sur la façon adéquate de l'invoquer, suivi d'un code erreur de 1.
Que se passe-t-il si l'on ne veut changer que le nom d'un seul fichier ? On a déjà cette commande et ce serait bien si elle pouvait s'en charger. Si l'on veut faire cela, il faut pouvoir tester si le paramètre est un fichier ou un répertoire. Voici une liste des opérateurs de test portant sur les fichiers :
- -b fichier
Vrai si fichier est un fichier spécial de type bloc
- -c fichier
Vrai si fichier est un fichier spécial de type caractère
- -d fichier
Vrai si fichier est un répertoire
- -f fichier
Vrai si fichier est un fichier ordinaire
- -r fichier
Vrai si fichier est un fichier accessible en lecture
- -w fichier
Vrai si fichier est un fichier accessible en écriture
- -x fichier
Vrai si fichier est un fichier exécutable
Il y a d'autres opérateurs mais ceux-ci sont les plus utilisés. Maintenant, on peut tester si notre script a en entrée un répertoire ou un fichier : modifions donc encore un peu le programme.
1 #!/bin/sh 2 3 if [ ! $1 ] 4 then 5 echo "Usage: `basename $0` nom_repertoire" 6 exit 1 7 fi 8 9 if [ -d $1 ] 10 then 11 DIR="/$1" 12 fi 13 14 if [ -f $1 ] 15 then 16 DIR="" 17 fi 18 19 for a in `ls $DIR` 20 do 21 fname=`echo $a | tr A-Z a-z` 22 mv $DIR$a $DIR$fname 23 done;Nous avons inséré les lignes 9 à 17 pour effectuer les vérifications fichier/répertoire. S'il s'agit d'un répertoire, on initialise
DIR
à «/$1
», sinon on l'initialise à vide. Remarquez que l'on met maintenant le/
du répertoire dans la variableDIR
et que nous avons modifié la ligne 22 pour qu'il n'y ait pas de/
entre$DIR
et$a
. Avec cette méthode, les chemins sont corrects.On a encore quelques problèmes. L'un d'eux est que, si un fichier porte déjà le nom que nous voulons attribuer, le script produit une erreur. On doit donc vérifier le nouveau nom de fichier avant de renommer. Un autre problème est de savoir ce qui se passe si quelqu'un met plus d'un paramètre. Nous allons modifier notre script pour accepter plus d'un répertoire ou fichier.
Le premier problème se corrige facilement en utilisant un simple test de chaîne et une instruction
if
, comme nous l'avons fait plus haut. Le deuxième problème est légèrement plus difficile car nous devons connaître le nombre de paramètres que l'utilisateur a fourni en entrée. Pour obtenir cette information, on utilise une variable spéciale du shell, toute préparée pour nous. Il s'agit de la variable$#
qui contient le nombre de paramètres de la ligne de commande. Ce que nous voulons faire est une boucle sur tous les paramètres. Cette fois-ci, nous utiliserons la bouclewhile
pour faire ce travail. Enfin, nous aurons besoin de comparer des valeurs entières car on veut pouvoir comparer le nombre de fois où l'on est passé dans la boucle au nombre de paramètres. Il existe des options de test spéciales pour évaluer les entiers, les voici :
- int1 -eq int2
(equal) Vrai si l'entier 1 est égal à l'entier 2.
- int1 -ge int2
(greater or equal) Vrai si l'entier 1 est supérieur ou égal à l'entier 2.
- int1 -gt int2
((greater than) Vrai si l'entier 1 est supérieur à l'entier 2.
- int1 -le int2
(lesser or equal) Vrai si l'entier 1 est inférieur ou égal à l'entier 2.
- int1 -lt int2
(lesser than) Vrai si l'entier 1 est inférieur à l'entier 2.
- int1 -ne int2
(nor equal) Vrai si l'entier 1 est différent de l'entier 2.
Avec ces nouvelles informations, nous pouvons modifier notre programme.
1 #!/bin/sh 2 3 if [ ! $1 ] 4 then 5 echo "Usage: `basename $0` nom_repertoire" 6 exit 1 7 fi 8 9 while [ $# -ne 0 ] 10 do 11 if [ -d $1 ] 12 then 13 DIR="/$1" 14 fi 15 16 if [ -f $1 ] 17 then 18 DIR="" 19 fi 20 21 for a in `ls $DIR` 22 do 23 fname=`echo $a | tr A-Z a-z` 24 if [ $fname != $a ] 25 then 26 mv $DIR$a $DIR$fname 27 fi 28 done; 29 30 shift 31 doneNous avons inséré une boucle
while
en ligne 9 qui vérifie si le nombre de paramètres est égal à zéro. Cela peut ressembler à une boucle sans fin mais l'instructionshift
en ligne 30 nous sauve. Elle annule le paramètre le plus proche du nom de la commande et le remplace par le suivant. Cette boucle annulera tous les paramètres éventuels : leur nombre sera alors de zéro, ce qui terminera la boucle.Enfin, remarquez l'instruction
if
en ligne 24 : elle vérifie si le nom de fichier est déjà en minuscules et, si c'est le cas, l'ignore.J'espère que vous avez apprécié cette brève introduction à la programmation Bourne/Bash. Je vous encourage à essayer vous-même ces exemples. En fait, si vous pourriez améliorer ce script en lui ajoutant une option comme
-m
pour les minuscules et-M
pour les majuscules.J'assume la pleine responsabilité des erreurs ou confusions dans cette documentation. Merci d'envoyer vos commentaires ou questions à rick@ricken.demon.co.uk.
1.1 Références
- « The UNIX programming environment »
Brian W. Kernighan & Rob Pike, Prentice Hall.
- « Inside UNIX »
New Riders.
Précédent Suivant Table des matières
Copyright © 1998, Rick Dearman. Publié dans le n°25 de la « Linux Gazette », Février 1998
Adaptation française : Éric Jacoboni.