Introduction aux scripts shell — les bases

Gazette Linux n°52 — Avril 2000

Xavier Serpaggi

Adaptation française 

Frédéric Marchal

Correction du DocBook 

Article paru dans le n°52 de la Gazette Linux d'avril 2000.

Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

Introduction
La philosophie des scripts
Prérequis
Écrire un script
Le rendre plus intelligent
Récapitulatif
Conclusion
Références
 

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

Introduction

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.

La philosophie des scripts

Linux, en général, n'est pas un système accueillant, 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.

Prérequis

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 remettrai tout dans l'ordre à la fin.

Écrire un script

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 :

  • une annotation au début du script qui précise quel programme est utilisé pour l'exécuter et

  • un changement de permission du fichier le contenant, dans le but de le rendre exécutable.

Pour vous donner une exemple qui en plus sera utile, créons un script qui fera une sauvegarde (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

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 autre 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 :

$0

le nom du script qui est exécuté ; dans ce cas bkup,

$1

le premier paramètre ; dans ce cas 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 $<numéro>.

$@

la liste complète de tous les paramètres : $1 $2 $3...,

$#

le nombre de paramètres.

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.

Le rendre plus intelligent

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 :

  • de mots réservés :

    case do done elif else esac fi for function if in select then until while time

  • de caractères ou métacaractères sans guillemets ou de caractères réservés :

    ! { } | & * ; ( ) < > space tab

  • de noms standards de variables shell comme :

    PATH PS1 PWD RANDOM SECONDS

    (lisez man bash pour connaître les autres).

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 - et des _, 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 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.$a

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

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.

Récapitulatif

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 :

  1. création de fichier,

  2. permissions,

  3. sous-shells,

  4. exécution dans un répertoire n'appartenant pas au PATH,

  5. la dièse-bidouille,

  6. les commentaires,

  7. les paramètres de position,

  8. la substitution de commande,

  9. les variables.

Conclusion

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.

Références

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.

Adaptation française de la Gazette Linux

L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.

Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://www.traduc.org/Gazette_Linux.

Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.