Par Ben Okopnik ben-fuzzybear@yahoo.com
« Voici un truc. Si vous pensez que votre code servant à lancer une
fonction shell ne fonctionne pas, ne lui passez jamais, je dit bien jamais
l'argument "/etc/reboot/" histoire de voir ce que ça fait. »
-- Elliot Evans
Les scripts shell sont une combinaison fascinante d'art et de science qui, sous la forme d'outils très simple, vous donnent accés à l'incroyable flexibilité et à l'incroyable puissance de Linux. À l'époque où les PC n'en étaient qu'à leur début, j'étais considéré comme un expert des fichiers batch de DOS. Maintenant je réalise qu'ils n'étaient qu'une pâle imitation sans âme des scripts shell d'Unix. Je n'ai pas l'habitude de donner dans l'anti-Microsoft -- je crois réellement qu'en leur temps ils ont fait des choses vraiment bien -- mais leur langage BFL (Batch File Language) était une plaisanterie en comparaison. Et elle n'était même pas drôle.
Vu que les scripts sont partie intégrante du shell lui-même, une partie de cet article parlera des bizzareries du shell, de ses méthodes et de ses spécificités. Soyez patients. Cela fait partie intégrante du savoir nécessaire à l'écriture de bons scripts.
Linux, en général, n'est pas un système acceuillant, chaleureux et destiné à des utilisateurs non avertis. Plutôt que de vous dire pas à pas ce que vous devez faire, il met à votre disposition une myriade de petits outils, qui peuvent être combinés d'une infinité de façons, pour aboutir à un résultat donné (Je trouve la devise de Perl TMTOWTDI -- There's More Than One Way To Do It (Il existe plus d'une façon de le faire) -- tout à fait à propos pour tous les Unix). Cette forme de puissance et de flexibilité, bien sûr, a un prix : une plus grande complexité et des compétences plus poussées pour l'utilisateur. De la même manière qu'il existe une énorme différence dans la manière de conduire disons, une bicyclette et un avion de combat supersonique, il existe une énorme différence entre suivre à l'aveuglette les dictats rigides d'une interface utilisateur standardisée et créer vos propres programmes (ou scripts shell) qui font exactement ce que vous voulez qu'ils fassent et de la manière que voulez qu'ils le fasse.
Écrire des scripts shell est une manière de programmer -- mais c'est de la programmation facile à appréhender, avec peut (quand il y en a) de structure formelle. C'est un langage interprété proposant sa propre syntaxe -- mais ce n'est rien d'autre que la syntaxe que vous utilisez quand vous lancez des programmes depuis la ligne de commande ; parfois je nomme ça du savoir recyclable. C'est en fait ce qui rend les scripts shell si utiles : quand vous les écrivez vous en apprenez toujours plus sur les spécificités de votre shell et sur les opérations de votre système -- et c'est vraiment ce savoir qui, à court comme à long terme, paie.
Vu que ma préférence va largement à bash et qu'en plus il apparaît être le shell le plus utilisé, vous comprendrez pourquoi ces scripts sont écrits pour lui. Mais il n'y a aucun problème a utiliser un autre shell ; il suffit que vous ayez bash d'installé et tout ira très bien. Comme vous le verrez, les script font appel au shell dont ils ont besoin. Cela fait partie des attributions d'un script bien écrit.
Je vais faire l'hypothèse que vous êtes dans votre répertoire personnel. Cela évitera d'avoir des fichier dispersés partout de telle manière qu'on ne puisse plus les retrouver par la suite. Je vais également supposer que vous en savez assez pour appuyer sur la touche Enter après chaque ligne que vous avez tapé, et qu'une fois que vous aurez choisi un nom pour votre script, vous verifirez qu'il n'existe pas d'exécutable portant le même dans votre path (Truc : tapez which bkup pour vérifier si il y a un exécutable du nom de bckup dans votre path). C'est pour cette raison que vous ne devrez jamais appeler vos scripts test. C'est une des FAQ d'Unix : « Pourquoi est-ce que mon script shell/programme ne fait rien ? » Il existe un exécutable dans /bin appelé test qui ne fait rien (rien d'apparent en tout cas) quand on le lance...
Vous devez également connaître les principes de base des opérations sur les fichiers (copie, déplacement, etc.) ainsi qu'être familier avec les abréviations usuelles (« . » est le répertoire courant, « .. » le répertoire parent, « ~ » votre répertoire personnel, etc.), cela va sans dire. Vous ne le saviez pas ? Et bien maintenant vous le savez ! Hé hé...
Quelque soit l'éditeur que vous utilisez, vi, emacs, mcedit (l'éditeur par défaut de Midnight Commander et un de mes outils préférés) ou autre, il conviendra ; prenez simplement garde de ne pas sauvegarder vos scripts sous un format propre à un traitement de texte.
Pour éviter les répétition je vais numéroter les lignes au fur et à mesure que nous avancerons dans la description du script. De toutes façon, je remetrai tout dans l'ordre à la fin.
Examinons ensemble les bases de la création d'un script. Ceux d'entre vous qui trouveraient cela évident sont tout de même invités à lire ce qui suit ; alors que nous progresserons, tout se compliquera et un petit raffraîchissement de mémoire ne peut pas faire de mal. Donc, le public visé par cet article est l'utilisateur débutant de Linux, quelqu'un qui n'a jamais écrit de script shell mais qui désire devenir un Guru en 834657 étapes faciles. :)
Vu simplement, un script shell n'est rien de plus qu'un raccourci -- une liste de commandes que vous auriez normalement tapées, les unes après les autres, pour les faire exécuter sur votre ligne de commande -- avec une rien de magie pour faire savoir au shell que c'est en fait un script.
Cette magie se résume tout simplement à deux choses :
Créons tout d'abord le fichier et positionnons ses permissions. Tapez
touch bkup
chmod +x bkup
La première ligne crée un fichier nommé bkup dans le répertoire courant. La
seconde le rend exécutable. Remarquez que la commande chmod +x rend le
fichier exécutable par tout le monde. Si vous voulez limiter les droits
d'exécution vous devez utiliser chmod u+x ou chmod ug+x (référez vous
à la page de manuel de chmod). Toutefois, dans la plupart des cas, chmod +x
convient parfaitement.
A présent, nous devons effectivement créer le script. Ouvrez avec votre éditeur le fichier que vous venez de créer :
mcedit bkup
La toute première ligne des scripts que nous écrirons sera la suivante (encore
une fois, ne tenez pas compte du chiffre et du : au début de la
ligne) :
1: #!/bin/bash
J'ai entendu appeler ça le hash-bang hack : la dièse-bidouille. Ce qui
est intéressant c'est que le signe dièse correspond en réalité à un début de
commentaire. Mais le doublet #! est dans ce sens unique puisqu'il est
interprété comme un préfixe au nom de l'exécutable qui servira à interpréter les
lignes qui suivent.
C'est un point discret mais délicat puisque : quand un script s'exécute il lance en fait un nouveau processus bash qui est le fils du bash courant. Ce nouveau processus exécute le script et se termine, pour vous laisser dans les mains du processus bash qui lui avait donné naissance. Voilà pourquoi un script qui, par exemple, change de répertore alors qu'il s'exécute, ne vous y laissera pas une fois terminé : il n'a pas été demandé au shell original de changer de répertoire, et vous vous retrouvez exactement où vous étiez quand vous avez commencé. Bien sûr, il y a changement de répertoire pendant l'exécution du script.
Pour continuer avec notre script :
# "bkup" - copie les fichiers spécifiés dans le répertoire ~/Sauvegarde de
# l'utilisateur après avoir vérifié qu'il n'y ait pas de conflit de nom.
Comme je l'ai déjà dit, le caractère # sert à délimiter un commentaire. Il
est judicieux de mettre des commentaires au début de chaque script (ou ailleurs)
pour vous souvenir exactement de ce qu'il fait et le différencier des nombreux autres que
vous aurez écrit. Dans de futurs articles nous verrons comment faire pour rendre
cet aide mémoire un peu plus automatique, mais poursuivons...
4: cp -i $1 ~/Sauvegarde
L'option -i de la commande cp la rend interactive ; c'est à dire que
si nous lançons la commande bkup fichier.txt et qu'un fichier fichier.txt
existe déjà dans le répertoire ~/Sauvegarde, cp vous demandera s'il
faut l'écraser (il mettra fin à l'opération si vous tapez aute chose que la
touche y -- NdT : yes, o et oui fonctionnent aussi. Chez moi en
tout cas).
Le $1 est un paramètre de position . Il fait référence à la première chose qui est tapée après le nom du script. En fait, il existe une liste complète de ces variables :
Jusqu'à présent notre script ne fait pas grand chose. Ça vaut tout juste la peine, non ? Bien, rendons-le un petit peu plus utile. Que faudrait-il faire si vous vouliez à la fois conserver le fichier dans le répertoire ~/Sauvegarde et sauvegarder le nouveau ? Peut-être ajouter une extension pour faire apparaître la « version » ? Essayons ça. Il suffit d'ajouter une ligne et de modifier la dernière comme suit :
4: a=$(date +%T-%d_%m_%Y)
5: cp -i $1 ~/Sauvegarde/$1.$a
Là, nous commençons à entrevoir la vrai puissance des scripts shell : la possibilité de se servir du
résultat d'autres commandes Linux. C'est ce qui est appelé la substitution
de commandes. La forme $(commande), permet d'exécuter la commande entre
parenthèses, puis de remplacer la chaîne "$(commande)" par le résultat.
Notre exemple demande à la commande date d'écrire la date, l'heure et les
secondes du moment, puis d'affecter le résultat à la variable a. Ensuite,
nous ajoutons la valeur de cette variable à la fin du nom de fichier qu'il
faudra sauvegarder dans ~/Sauvegarde. Remarquez que quand nous
affectons une valeur à une variable nous utilisons son nom (a=xxx), mais
que quand nous voulons utiliser cette valeur, nous devons faire précéder le nom
de la variable du caractère $ ($1.$a). Le nom d'une variable peut être
n'importe quoi à l'exception :
case do done elif else esac fi for function if in select then until while time
! { } | & * ; ( ) < > space tab
PATH PS1 PWD RANDOM SECONDS
(lisez man bash pour connaître les autres).Les deux dernières lignes de ce script ont pour effet de créer un fichier unique -- quelque chose comme fichier.txt.01:00:00-01_01_2000 -- qui ne devrait pas entrer en conflit avec les fichiers déjà présents dans ~/Sauvegarde. Vous remarquerez que j'ai conservé l'option -i comme une sécurité : si pour une raison des plus étrange, deux fichiers ont le même nom, cp vous donnera une dernière chance de tout arréter. Dans le cas contraire cela ne fera aucune différence et, comme la levure morte dans la bière, ça ne fera pas de mal même si ça n'apporte rien.
À propos, l'ancienne version de la notation $(commande) : `commande` (remarquez les apostrophes inversées utilisées à la place des guillements) est de plus en plus laissée de côté et ce pour une bonne raison. En effet, il est plus facile de faire plusieurs niveaux de parenthèses. Par exemple :
$(cat $($2$(basename fichier1 txt)))
ne peut pas être écrit à l'aide d'apostrophes inversées, puisque la seconde
serait considérée comme la fin de la première et la commande échouerai ou
donnerai un résultat inattendu. Cependant vous pouvez tout de même les utiliser
dans le cas de substitutions non imbriquées (ce qui est le cas le plus répandu),
ou uniquement aux extrémités de la substitution. Vous éviterez ainsi tout
problème.
Bien, voyons à présent ce que nous avons obtenu (avec l'ajout de quelques espaces et la suppression des numéros de ligne pour plus de lisibilité) :
#!/bin/bash # "bkup" - copie les fichiers spécifiés dans le répertoire ~/Sauvegarde de # l'utilisateur après avoir vérifié qu'il n'y ait pas de conflit de nom. a=$(date +%T-%d_%m_%Y) cp -i $1 ~/Sauvegarde/$1.$aBien sûr ce n'est qu'un script de deux ligne, mais de ceux qui savent se rendre utile. Nous continuerons à le modifier dans le prochain numéro.
Ah, oui, une dernière chose ; une autre FAQ d'Unix. Si vous essayez d'éxécuter votre script fraichement écrit en tapant simplement
bkup
vous aurez surement cette réponse bien connue :
bash: bkup: command not found
« Quoi ! Ne nous sommes nous pas donnés assez de mal ? Que se
passe-t-il ? »
Contrairement à DOS, l'exécution des commandes et des scripts dans le répertoire courant est désactivée par défaut. C'est une sécurité. Imaginez ce qui se passerait si quelqu'un copiait, dans votre répertoire personnel, un script appelé ls et qui contiendrait rm -rf * (tout effacer) et que vous tapiez ls ! Si, dans votre variable PATH, le répertoire courant . était placé avant le répertoire bin, vous auriez été vraiment triste...
Voilà pourquoi, entre autre, vous devez préciser le chemin de tous les exécutables que vous désirez lancer dans le répertoire courant : une restriction pleine de sagesse. Une fois que vous avez terminé de le modifier, vous pouvez déplacer votre exécutable dans un répertoire qui est listé dans votre variable PATH. /usr/local/bin semble un bon candidat (Truc : tapez echo $PATH pour connaître les répertoires qui en font partie).
En attendant, pour l'exécuter tapez simplement :
./bkup fichier.txt
où le ./ signifie que le fichier à exécuter est dans le répertoire
courant. Si vous l'invoquez de n'importe où ailleurs, utilisez plutôt
~/ (NdT : souvenez vous que vos scripts sont à la racine de votre
répertoire personnel). Ce que je tente de mettre en avant ici est que vous
devez donner le chemin complet d'accès à votre exécutable, vu qu'il n'est dans
aucun des répertoires contenus dans le path.
Cela laisse bien entendu supposer qu'un fichier appelé fichier.txt est présent dans le répertoire courant et que vous avez crée un sous-répertoire appelé Sauvegarde dans votre répertoire personnel. Si tel n'est pas le cas, vous aurez un message d'erreur.
Dans cet article nous avons pris connaissance des opérations de base nécessaires à la création d'un script shell, mais également de principes précis :
Voilà qui n'est pas mal pour un début. Jouez un peu avec, expérimentez. Les scripts shell représentent une grande part de la puissance et du côté attrayant de Linux. Le mois prochain nous parlerons de la gestion des erreurs -- ce que votre script doit faire si la personne qui l'utilise fait des erreurs de syntaxe par exemple -- mais également des boucles et des exécutions conditionnelles et aussi peut-être un peu des super outils qui sont utilisés en général dans les scripts shell.
N'hésitez pas à m'envoyer vos suggestions à propos de corrections ou d'améliorations, mais aussi à propos de vos trucs préférés dans vos scripts shell et des découvertes géniales que vous avez pû faire. Comme toute personne non submergée par son ego, je me considére un éternel étudiant, toujours prêt à apprendre quelque chose de nouveau. Si j'utilisais quelque chose dont vous m'auriez fait part, je vous citerai.
À la prochaine fois et
Joyeux Linux-age.
Pages de manuel de bash, cp et chmod.
« Pas à moi mon gars. Je lis le man de Bash chaque jour comme un Témoin de
Jéhova lit la Bible. Non attend, le man de Bash est la
Bible.
Excuse moi... »
-- En savoir plus sur les alias déroutants, tiré de comp.os.linux.misc.
Copyright 2000, Ben Okopnik. Paru dans le numéro 52 de la Linux Gazette d'Avril 2000.
Traduction française de Xavier Serpaggi.