Introduction aux scripts shell --~les bases <author>Ben Okopnik <!-- Compléter: Linux Gazette n°52 - Traducteur:Xavier Serpaggi --> <!-- L'article sera coupé ici... --> <sect>Introduction aux scripts shell --~les bases <p>Par Ben Okopnik <tt><url url="mailto:ben-fuzzybear@yahoo.com" name="ben-fuzzybear@yahoo.com"></tt> <!-- La traduction --> <bf> «~Voici un truc. Si vous pensez que votre code servant à lancer une fonction shell ne fonctionne pas, ne lui passez jamais, je dit bien <em/jamais/ l'argument "/etc/reboot/" histoire de voir ce que ça fait.~»<newline> ~--~Elliot Evans </bf> <sect1>Introduction <p>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 <em/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 (<em/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. <sect1>La philosophie des scripts <p>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 <it/TMTOWTDI/ --~There's More Than One Way To Do It (<em/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 <em/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. <sect1>Prérequis <p>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 <em/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 <em/path/ (Truc~: tapez <tt/which bkup/ pour vérifier si il y a un exécutable du nom de <tt/bckup/ dans votre <em/path/). C'est pour cette raison que vous ne devrez <it/jamais/ appeler vos scripts <tt/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 <tt>/bin</tt> 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, <em/vi/, <em/emacs/, <em/mcedit/ (l'éditeur par défaut de <url url="http://www.gnome.org/mc/" name="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. <sect1>Écrire un script <p>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 <em/Guru/ en 834657~étapes faciles. <tt/:)/ 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~: <itemize> <item>une annotation au début du script qui précise quel programme est utilisé pour l'exécuter et <item>un changement de permission du fichier le contenant, dans le but de le rendre exécutable. </itemize> Pour vous donner une exemple qui en plus sera utile, créons un script qui fera une sauvegarde (<em/back up/) d'un fichier dans un répertoire choisi. Nous allons détailler les différentes étapes nécessaires à une telle tâche. Créons tout d'abord le fichier et positionnons ses permissions. Tapez <tscreen> touch bkup<newline> chmod +x bkup </tscreen> <!-- j'ai un peu modifié le contenu de ce qui suit, mais c'est juste pour éviter d'avoir des alternances <tt// et normal tous les mots ! --> La première ligne crée un fichier nommé <tt/bkup/ dans le répertoire courant. La seconde le rend exécutable. Remarquez que la commande <tt/chmod +x/ rend le fichier exécutable par tout le monde. Si vous voulez limiter les droits d'exécution vous devez utiliser <tt/chmod u+x/ ou <tt/chmod ug+x/ (référez vous à la page de manuel de chmod). Toutefois, dans la plupart des cas, <tt/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~: <tscreen> mcedit bkup </tscreen> La toute première ligne des scripts que nous écrirons sera la suivante (encore une fois, ne tenez pas compte du chiffre et du <tt/:/ au début de la ligne)~: <tscreen> 1: #!/bin/bash </tscreen> J'ai entendu appeler ça le <em/hash-bang hack/~: la <it/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 <tt/#!/ 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~: <tscreen> # "bkup" - copie les fichiers spécifiés dans le répertoire ˜/Sauvegarde de<newline> # l'utilisateur après avoir vérifié qu'il n'y ait pas de conflit de nom. </tscreen> Comme je l'ai déjà dit, le caractère <tt/#/ 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... <tscreen> 4: cp -i $1 ˜/Sauvegarde </tscreen> L'option <tt/-i/ de la commande <tt/cp/ la rend interactive~; c'est à dire que si nous lançons la commande <tt/bkup fichier.txt/ et qu'un fichier <tt/fichier.txt/ existe déjà dans le répertoire <tt>˜/Sauvegarde</tt>, cp vous demandera s'il faut l'écraser (il mettra fin à l'opération si vous tapez aute chose que la touche <tt/y/ --~NdT~: <tt/yes/, <tt/o/ et <tt/oui/ fonctionnent aussi. Chez moi en tout cas). Le <tt/$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~: <itemize> <item><tt/$0/ - le nom du script qui est exécuté~; dans ce cas <tt/bkup/, <item><tt/$1/ - le premier paramètre~; dans ce cas <tt/fichier.txt/. De la même manière il est possible de faire référence à n'importe quel paramètre en utilisant la notation <tt/$<numéro>/. <item><tt/$@/ - la liste complète de tous les paramètres~: <tt/$1 $2 $3.../, <item><tt/$#/ - le nombre de paramètres. </itemize> Il existe de nombreuses autres façons de faire référence aux paramètres de position et de les manipuler (voyez la page de manuel de bash), mais celle-là nous suffira pour l'instant. <sect1>Le rendre plus intelligent <p>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 <tt>˜/Sauvegarde</tt> <it/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~: <tscreen> 4: a=$(date +%T-%d_%m_%Y)<newline> 5: cp -i $1 ˜/Sauvegarde/$1.$a </tscreen> Là, nous commençons à entrevoir la vrai puissance des scripts shell~:<!-- mais le chemin vers la Force est encore long Luc ;) --> la possibilité de se servir du résultat d'autres commandes Linux. C'est ce qui est appelé <em/la substitution de commandes/. La forme <tt/$(commande)/, permet d'exécuter la commande entre parenthèses, puis de remplacer la chaîne <tt/"$(commande)"/ par le résultat. Notre exemple demande à la commande <tt/date/ d'écrire la date, l'heure et les secondes du moment, puis d'affecter le résultat à la variable <tt/a/. Ensuite, nous ajoutons la valeur de cette variable à la fin du nom de fichier qu'il faudra sauvegarder dans <tt>˜/Sauvegarde</tt>. Remarquez que quand nous affectons une valeur à une variable nous utilisons son nom (<tt/a=xxx/), mais que quand nous voulons utiliser cette valeur, nous devons faire précéder le nom de la variable du caractère <tt/$/ (<tt/$1.$a/). Le nom d'une variable peut être n'importe quoi à l'exception~: <itemize> <item>de mots réservés~: <tscreen> case do done elif else esac fi for function if in select then until while time </tscreen> <item>de caractères ou métacaractères sans guillemets ou de caractères réservés~: <tscreen> ! { } | & * ; ( ) < > space tab </tscreen> <item>de noms standards de variables shell comme~: <tscreen> PATH PS1 PWD RANDOM SECONDS </tscreen> (lisez <tt/man bash/ pour connaître les autres). </itemize> D'après l'expérience que j'en ai, si vous vous contentez de noms de variables ne contenant que des lettres minuscules, des <tt/-/ et des <tt/_/, il n'y aura aucun problème. Les deux dernières lignes de ce script ont pour effet de créer un fichier unique --~quelque chose comme <tt/fichier.txt.01:00:00-01_01_2000/~-- qui ne devrait pas entrer en conflit avec les fichiers déjà présents dans <tt>˜/Sauvegarde</tt>. Vous remarquerez que j'ai conservé l'option <tt/-i/ comme une sécurité~: si pour une raison des plus étrange, deux fichiers ont le même nom, <tt/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 <tt/$(commande)/~: <tt/`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~: <tscreen> $(cat $($2$(basename fichier1 txt))) </tscreen> 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é)~: <code> #!/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.$a </code> Bien 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 <tscreen> bkup </tscreen> vous aurez surement cette réponse bien connue~: <tscreen> bash: bkup: command not found </tscreen> «~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é <tt/ls/ et qui contiendrait <tt/rm -rf */ (tout effacer) et que vous tapiez <tt/ls/~! Si, dans votre variable PATH, le répertoire courant <tt/./ était placé avant le répertoire <tt/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. <tt>/usr/local/bin</tt> semble un bon candidat (Truc~: tapez <tt/echo $PATH/ pour connaître les répertoires qui en font partie). En attendant, pour l'exécuter tapez simplement~: <tscreen> ./bkup fichier.txt </tscreen> où le <tt>./</tt> signifie que le fichier à exécuter est dans le répertoire courant. Si vous l'invoquez de n'importe où ailleurs, utilisez plutôt <tt>˜/</tt> (<em/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 <em/path/. Cela laisse bien entendu supposer qu'un fichier appelé <tt/fichier.txt/ est présent dans le répertoire courant et que vous avez crée un sous-répertoire appelé <tt/Sauvegarde/ dans votre répertoire personnel. Si tel n'est pas le cas, vous aurez un message d'erreur. <sect1>Récapitulatif <p>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~: <itemize> <item>création de fichier, <item>permissions, <item>sous-shells, <item>exécution dans un répertoire n'appartenant pas au <tt/PATH/, <item>la <it/dièse-bidouille/, <item>les commentaires, <item>les paramètres de position, <!-- ??? --> <item>la substitution de commande, <item>les variables. </itemize> <sect1>Conclusion <p>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 <em/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<newline> Joyeux Linux-age. <sect1>Références <p>Pages de manuel de <em/bash/, <em/cp/ et <em/chmod/. <bf> «~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 <it/est/ la Bible.<newline> Excuse moi...~»<newline> ~--~En savoir plus sur les alias déroutants, tiré de comp.os.linux.misc. </bf> <p>Copyright 2000, Ben Okopnik. Paru dans le numéro 52 de la Linux Gazette d'Avril 2000. <p>Traduction française de <url url="mailto:xavier.serpaggi@libertysurf.fr" name="Xavier Serpaggi">. </sect> <!-- ...et ici --> </article> <!-- MERCI :-) -->