« Script shell » : différence entre les versions
mAucun résumé des modifications |
m (nettoyage) |
||
Ligne 7 : | Ligne 7 : | ||
== Introduction == | == Introduction == | ||
Vous aurez envie d'écrire un script (petit programme écrit avec un langage simple : shell, perl ou autre) dès que vous aurez tapé dans un terminal quatre fois la même série de commandes et que vous vous apercevrez que vous êtes | Vous aurez envie d'écrire un script (petit programme écrit avec un langage simple : shell, perl ou autre) dès que vous aurez tapé dans un terminal quatre fois la même série de commandes et que vous vous apercevrez que vous êtes amené à le refaire de nombreuses fois. | ||
Un script est une suite d'instructions élémentaires qui sont éxécutées de façon séquencielle (les unes après les autres) par le langage de script. Dans cet article nous nous limiterons à l'utilisation du shell comme langage, et en particulier à du shell <code>bash</code>. En guise de première introduction, vous pouvez lire ce qui concerne les commandes du shell dans l'article [[Admin-admin_env-shell|Le Shell et les Commandes]]. Attention, n'espérez pas que le présent document constitue un manuel complet de programmation ! C'est une courte introduction qui nous l'espérons, vous permettra d'écrire de petits scripts qui vous rendront de précieux services. | |||
== Notions de base == | == Notions de base == | ||
Ligne 13 : | Ligne 15 : | ||
=== Mon premier script. === | === Mon premier script. === | ||
Pour commencer, il faut savoir qu'un script est un fichier texte standard pouvant être créé par n'importe quel éditeur : [ | Pour commencer, il faut savoir qu'un script est un fichier texte standard pouvant être créé par n'importe quel éditeur : [Software-soft_edit-vi|vi], [Software-soft_edit-emacs|emacs], kedit, gnotepad, ou autre. D'autre part, conventionnellement, un script commence par une ligne de commentaire contenant le nom du langage à utiliser pour interpréter ce script, soit dans notre cas : <code>/bin/sh</code> (on parle alors de "script shell"). Donc un script shell élémentaire pourrait être : | ||
< | <code multi>#!/bin/sh</code> | ||
< | <cadre type=note> | ||
'''Note :''' <code>"#!"</code> se prononce "''she bang''", soit "chi-bang". | |||
</cadre> | |||
< | Évidemment un tel script ne fait rien ! Changeons cela. La commande qui affiche quelque chose à l'écran est <code>echo</code>. Donc pour créer le script <code>bonjour_monde</code> nous pouvons écrire : | ||
<code>#!/bin/sh | |||
echo "Bonjour, Monde !" | |||
echo "un premier script est né."</code> | |||
Comment on l'éxécute ? C'est simple il suffit de faire : | Comment on l'éxécute ? C'est simple il suffit de faire : | ||
< | <code>[user@becane user]$ '''sh bonjour_monde''' | ||
Bonjour, Monde ! | |||
un premier script est né. | |||
[user@becane user]$ <font color="red">_</font></code> | |||
C'est pas cool, vous préféreriez taper quelque chose comme : | C'est pas cool, vous préféreriez taper quelque chose comme : | ||
< | <code>[user@becane user]$ '''./bonjour_monde''' | ||
Bonjour, Monde ! | |||
un premier script est né. | |||
[user@becane user]$ _</code> | |||
C'est possible si vous avez au préalable rendu votre script exécutable par la commande : | C'est possible si vous avez au préalable rendu votre script exécutable par la commande : | ||
< | <code>[user@becane user]$ '''chmod +x bonjour_monde''' | ||
[user@becane user]$ '''./bonjour_monde''' | |||
Bonjour, Monde ! | |||
un premier script est né. | |||
[user@becane user]$ _</code> | |||
<u>Résumons</u> : un script shell commence par : < | <u>Résumons</u> : un script shell commence par : <code>#!/bin/sh</code>, il contient des commandes du shell et est rendu exécutable par <code>chmod +x</code>. | ||
=== Quelques conseils concernant les commentaires === | === Quelques conseils concernant les commentaires === | ||
Dans un shell-script, est considéré comme un commentaire tout ce qui suit le caractère # et ce, jusqu'à la fin de la ligne. | Dans un shell-script, est considéré comme un commentaire tout ce qui suit le caractère # et ce, jusqu'à la fin de la ligne. Usez et abusez des commentaires : lorsque vous relirez un script 6 mois après l'avoir écrit, vous serez bien content de l'avoir documenté. Un programme n'est jamais trop documenté. Par contre, il peut être mal documenté ! Un commentaire est bon lorsqu'il décrit pourquoi on fait quelque chose, pas quand il décrit ce que l'on fait. Exemple : | ||
< | <code>#!/bin/sh | ||
# pour i parcourant tous les fichiers, | |||
for i in * ; do | |||
# copier le fichier vers .bak | |||
cp $i $i.bak | |||
# fin pour | |||
done</code> | |||
Que fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre : | Que fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre : | ||
< | <code>#!/bin/sh | ||
# on veut faire un copie de tous les fichiers | |||
for i in * ; do | |||
# sous le nom *.bak | |||
cp $i $i.bak | |||
done</code> | |||
Là, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.) | Là, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.) | ||
Ligne 49 : | Ligne 78 : | ||
=== Le passage de paramètres === | === Le passage de paramètres === | ||
Un script ne sera, en général, que d'une utilisation marginale si vous ne pouvez pas modifier son comportement d'une manière ou d'une autre. On obtient cet effet en "passant" un (ou plusieurs) paramètre(s) au script via la ligne de commande. Voyons comment faire cela. Soit le script < | Un script ne sera, en général, que d'une utilisation marginale si vous ne pouvez pas modifier son comportement d'une manière ou d'une autre. On obtient cet effet en "passant" un (ou plusieurs) paramètre(s) au script via la ligne de commande. Voyons comment faire cela. Soit le script <code>essai01</code>: | ||
< | <code>#!/bin/sh | ||
echo le paramètre \$1 est \"$1\" | |||
echo le paramètre \$2 est \"$2\" | |||
echo le paramètre \$3 est \"$3\"</code> | |||
Que fait-il ? Il affiche, les uns après les autres les trois premiers paramètres du script, donc si l'on tappe : | Que fait-il ? Il affiche, les uns après les autres les trois premiers paramètres du script, donc si l'on tappe : | ||
< | <code>$ '''./essai01 paramètre un''' | ||
le paramètre $1 est "paramètre" | |||
le paramètre $2 est "un" | |||
le paramètre $3 est "" | |||
$ _</code> | |||
Donc, les variables < | Donc, les variables <code>$1</code>, <code>$2</code> ... <code>$9</code> contiennent les "mots" numéro 1, 2 ... 9 de la ligne de commande. Attention : par "mot" on entend ensemble de caractères ne contenant pas de caractères de séparations. Les caractères de séparation sont l'espace, la tabulation, le point virgule. | ||
Vous avez sans doute remarqué que j'ai utilisé les caractères : < | Vous avez sans doute remarqué que j'ai utilisé les caractères : <code>\$</code> à la place de <code>$</code> ainsi que <code>\"</code> à la place de <code>"</code> dans le script. Pour quelle raison ? La raison est simple, si l'on tape : <code>echo "essai"</code> on obtient : <code>essai</code>, si l'on veut obtenir <code>"essai"</code> il faut dire à <code>echo</code> que le caractère <code>"</code> n'indique pas le début d'une chaîne de caractère (comme c'est le comportement par défaut) mais que ce caractère fait partie de la chaîne : on dit que l'on "échappe" ou "protège" le caractère <code>"</code> en tapant <code>\"</code>. En "échappant" le caractère <code>\</code> (par <code>\\</code>) on obtient le caractère <code>\</code> sans signification particulière. On peut dire que le caractère <code>\</code> devant un autre lui fait perdre sa signification particulière s'il en a une, ne fait rien si le caractère qui suit <code>\</code> n'en a pas. | ||
Maintenant, essayons de taper : | Maintenant, essayons de taper : | ||
< | <code>$ '''./essai01 *''' | ||
le paramètre $1 est "Mail" | |||
le paramètre $2 est "essai01" | |||
le paramètre $3 est "nsmail"$ _</code> | |||
Le résultat doit être sensiblement différent sur votre machine : il dépend du contenu de votre répertoire courant. Que s'est-il passé ? Le shell a remplacé le caractère * par la liste de tous les fichiers non cachés présents dans le répertoire actif. En fait, toutes les substitutions du shell sont possibles ! C'est le shell qui "substitue" aux paramètres des valeurs étendues par les caractères spéciaux : * (toute suite de caractères) ? (un caractère grave quelconque), [dze] (l'un des caractères d, z ou e), [d-z] (les caractères de 'd' à 'z')... Autre exemple : | |||
< | <code>$ '''./essai01 \*''' | ||
le paramètre $1 est "*" | |||
le paramètre $2 est "" | |||
le paramètre $3 est ""$ _</code> | |||
Hé oui, on a "échappé" le caractère < | Hé oui, on a "échappé" le caractère <code>*</code> donc il a perdu sa signification particulière : il est redevenu un simple <code>*</code>. | ||
C'est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser la commande shift ; à titre d'exemple voici le script < | C'est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser la commande shift ; à titre d'exemple voici le script <code>essai02</code> : | ||
< | <code>#!/bin/sh | ||
echo le paramètre 1 est \"$1\" | |||
shift | |||
echo le paramètre 2 est \"$1\" | |||
shift | |||
echo le paramètre 2 est \"$1\" | |||
shift | |||
echo le paramètre 4 est \"$1\" | |||
shift | |||
echo le paramètre 5 est \"$1\" | |||
shift | |||
echo le paramètre 6 est \"$1\" | |||
shift | |||
echo le paramètre 7 est \"$1\" | |||
shift | |||
echo le paramètre 8 est \"$1\" | |||
shift | |||
echo le paramètre 9 est \"$1\" | |||
shift | |||
echo le paramètre 10 est \"$1\" | |||
shift | |||
echo le paramètre 11 est \"$1\"</code> | |||
Si vous tapez : | Si vous tapez : | ||
< | <code>$ '''./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13''' | ||
le paramètre 1 est "1" | |||
le paramètre 2 est "2" | |||
le paramètre 2 est "3" | |||
le paramètre 4 est "4" | |||
le paramètre 5 est "5" | |||
le paramètre 6 est "6" | |||
le paramètre 7 est "7" | |||
le paramètre 8 est "8" | |||
le paramètre 9 est "9" | |||
le paramètre 10 est "10" | |||
le paramètre 11 est "11"$ _</code> | |||
A chaque appel de < | A chaque appel de <code>shift</code> les paramètres sont décalés d'un numéro : le paramètre 2 devient le paramètre 1, 3 devient 2, etc... Évidemment le paramètre 1 est perdu par l'appel de shift : vous devez donc vous en servir avant d'appeler <code>shift</code> (ou le sauvegarder dans une variable). | ||
=== Les variables === | === Les variables === | ||
Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : < | Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : <code>$1</code>, <code>$2</code> etc. Ce sont les substitutions des variables <code>1</code>, <code>2</code>, etc. par leur valeurs. Mais vous pouvez définir et utiliser n'importe quel nom. Attention toutefois à ne pas confondre le nom d'une variable (notée par exemple <code>machin</code>) et son contenu (noté dans cas <code>$machin</code>). Vous connaissez peut-être la variable <code>PATH</code> (attention, le shell différencie les majuscules des minuscules) qui contient la liste des répertoires (séparés par des <code>":"</code>) dans lesquels il doit rechercher les programmes. Si dans un script vous tapez : | ||
< | <code>1:#!/bin/sh | ||
2:PATH=/bin # PATH contient /bin | |||
3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin | |||
4:PATH=/bin # PATH contient /bin | |||
5:PATH=$PATH:/usr/bin # PATH contient /bin:/usr/bin</code> | |||
Les numéros ne sont là que pour repérer les lignes, il ne faut pas les taper.La ligne 3 est très certainement une erreur, à gauche du signe <code>"="</code> il faut une variable (donc un nom sans <code>$</code>) mais à droite de ce même signe il faut une valeur, et la valeur que l'on a mis est <code>"PATH:/usr/bin"</code> : il n'y a aucune substitution à faire. Par contre la ligne 5 est certainement correcte : à droite du <code>"="</code> on a mis <code>"$PATH:/usr/bin"</code>, la valeur de <code>$PATH</code> étant <code>"/bin"</code>, la valeur après substitution par le shell de <code>"$PATH:/usr/bin"</code> est <code>"/bin:/usr/bin"</code>. Donc, à la fin de la ligne 5, la valeur de la variable <code>PATH</code> est <code>"/bin:/usr/bin"</code>. | |||
Attention : il ne doit y avoir aucun espace de part et d'autre du signe < | Attention : il ne doit y avoir aucun espace de part et d'autre du signe <code>"="</code>. | ||
<u>Résumons</u> : < | <u>Résumons</u> : <code>MACHIN</code> est un nom de variable que l'on utilise lorsque l'on a besoin d'un nom de variable (mais pas de son contenu), et <code>$MACHIN</code> est le contenu de la variable <code>MACHIN</code> que l'on utilise lorsque l'on a besoin du contenu de cette variable. | ||
=== Variables particulières === | === Variables particulières === | ||
Ligne 97 : | Ligne 175 : | ||
Il y a un certain nombre de variables particulières, en voici quelques unes : | Il y a un certain nombre de variables particulières, en voici quelques unes : | ||
* la variable < | * la variable <code>*</code> (dont le contenu est <code>$*</code>) contient l'ensemble de tous les "mots" qui on été passé au script (c'est à dire toute la ligne de commande, sans le nom du script). | ||
* la variable < | * la variable <code>#</code> contient le nombre de paramètres (<code>$#</code>) qui ont été passés au programme. | ||
* la variable < | * la variable <code>0</code> (zéro) contient le nom du script (ou du lien si le script a été appelé depuis un lien). | ||
Il y en a d'autres, moins utilisées : allez voir la man page de < | Il y en a d'autres, moins utilisées : allez voir la man page de <code>bash</code>. | ||
=== Saisir la valeur d'une variable === | === Saisir la valeur d'une variable === | ||
Les paramètres permettent à l'utilisateur d'agir sur le déroulement du script avant son exécution. Mais il est aussi souvent intéressant de pouvoir agir sur le déroulement du script lors de son exécution, c'est ce que permet la commande : < | Les paramètres permettent à l'utilisateur d'agir sur le déroulement du script avant son exécution. Mais il est aussi souvent intéressant de pouvoir agir sur le déroulement du script lors de son exécution, c'est ce que permet la commande : <code>read nom_variable</code>. Dans cette commande vous pouvez bien sûr remplacer nom_variable par le nom de variable qui vous convient le mieux. Voici un exemple simple. | ||
< | <code>#!/bin/sh | ||
echo -n "Entrez votre prénom : " | |||
read prenom | |||
echo -n "Entrez votre nom de login : " | |||
read nomlogin | |||
echo "Le nom de login de $prenom est $nomlogin."</code> | |||
Ce script se déroule ainsi : | Ce script se déroule ainsi : | ||
< | <code>'''./essai02bis | ||
'''Entrez votre prénom : '''Marc | |||
'''Entrez votre nom de login : '''spoutnik | |||
'''Le nom de login de Marc est spoutnik.</code> | |||
Lors du déroulement du script vous devez valider vos entrées en appuyant sur la touche "Entrée". | Lors du déroulement du script vous devez valider vos entrées en appuyant sur la touche "Entrée". | ||
Ligne 117 : | Ligne 203 : | ||
=== Arithmétique === | === Arithmétique === | ||
Vous vous doutez bien qu'il est possible de faire des calculs avec le shell. En fait, le shell ne "sait" faire que des calculs sur les nombres entiers (ceux qui n'ont pas de virgules ;-). Pour faire un calcul il faut encadrer celui-ci de : < | Vous vous doutez bien qu'il est possible de faire des calculs avec le shell. En fait, le shell ne "sait" faire que des calculs sur les nombres entiers (ceux qui n'ont pas de virgules ;-). Pour faire un calcul il faut encadrer celui-ci de : <code>$(( un calcul ))</code> ou <code>$[ un calcul ]</code>. Exemple, le script essai03 : | ||
< | <code>#!/bin/sh | ||
echo 2+3*5 = $((2+3*5)) | |||
MACHIN=12 | |||
echo MACHIN*4 = $[$MACHIN*4]</code> | |||
Affichera : | Affichera : | ||
< | <code>$ '''sh essai03''' | ||
2+3*5 = 17 | |||
MACHIN*4 = 48</code> | |||
Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est < | Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est <code>"**"</code> (ie : 2 puissance 5 s'écrit : <code>2**5</code>). On peut utiliser des parenthèses pour modifier l'ordre des calculs. | ||
== Les instructions de contrôle de scripts == | == Les instructions de contrôle de scripts == | ||
Ligne 133 : | Ligne 224 : | ||
=== L'exécution conditionnelle === | === L'exécution conditionnelle === | ||
Lorsque vous programmerez des scripts, vous voudrez que vos scripts fassent une chose si une certaine condition est remplie et autre chose si elle ne l'est pas. La construction de bash qui permet cela est le fameux test : < | Lorsque vous programmerez des scripts, vous voudrez que vos scripts fassent une chose si une certaine condition est remplie et autre chose si elle ne l'est pas. La construction de bash qui permet cela est le fameux test : <code>if then else fi</code>. Sa syntaxe est la suivante (la partie <code>else...</code> est optionnelle) : | ||
<code>if <test> ; | |||
then | |||
<instruction 1> | |||
<instruction 2> | |||
... | |||
<instruction n> | |||
else | |||
<instruction n+1> | |||
... | |||
<instruction n+p> | |||
fi</code> | |||
Il | Il faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable <code>?</code> dont la valeur est, rappelons le : "<code>$?</code>". Pour le shell une valeur nulle est synonyme de VRAI et une valeur non nulle est synonyme de FAUX. Ceci parce que, en général les programmes renvoie zéro quand tout c'est bien passé et un code d'erreur (nombre non nul) quand il s'en est produit une. | ||
< | Il existe deux programmes particuliers : <code>false</code> et <code>true</code>. <code>true</code> renvoie toujours 0 et <code>false</code> renvoie toujours 1. Sachant cela, voyons ce que fait le programme suivant : | ||
< | <code>#!/bin/sh | ||
if true ; | |||
then | |||
echo Le premier test est VRAI($?) | |||
else | |||
echo Le premier test est FAUX($?) | |||
fi | |||
</ | if false ; | ||
then | |||
echo Le second test est VRAI($?) | |||
else | |||
echo Le second test est FAUX($?) | |||
fi</code> | |||
Affichera : | Affichera : | ||
< | <code>$ ./test | ||
Le premier test est VRAI(0) | |||
Le second test est FAUX(1) | |||
$ _</code> | |||
On peut donc conclure que l'instruction < | On peut donc conclure que l'instruction <code>if ... then ... else ... fi</code>, fonctionne de la manière suivante : si ('''if''' en anglais) le test est VRAI(0) alors ('''then''' en anglais) le bloc d'instructions compris entre le <code>then</code> et le <code>else</code> (ou le <code>fi</code> en l'absence de <code>else</code>) est exécuté, sinon ('''else''' en anglais) le test est FAUX(différent de 0)) et on exécute le bloc d'instructions compris entre le <code>else</code> et le <code>fi</code> si ce bloc existe. | ||
Bon, évidemment, des tests de cet ordre ne paraissent pas très utiles. Voyons maintenant de vrais tests. | Bon, évidemment, des tests de cet ordre ne paraissent pas très utiles. Voyons maintenant de vrais tests. | ||
Ligne 157 : | Ligne 270 : | ||
=== Les tests === | === Les tests === | ||
Un test, nous l'avons vu, n'est rien de plus qu'une commande standard. Une des commandes standard est | Un test, nous l'avons vu, n'est rien de plus qu'une commande standard. Une des commandes standard est <code>test</code>, sa syntaxe est un peu complexe, je vais la décrire avec des exemples. | ||
* si l'on veut tester l'existence d'un répertoire < | * si l'on veut tester l'existence d'un répertoire <code><machin></code>, on tapera : <code>test -d <machin></code> ('d' comme '''''d'''irectory'') | ||
* si l'on veut tester l'existence d'un fichier < | * si l'on veut tester l'existence d'un fichier <code><machin></code>, on tapera : <code>test -f <machin></code> ('f' comme '''''f'''ile'') | ||
* si l'on veut tester l'existence d'un fichier ou répertoire < | * si l'on veut tester l'existence d'un fichier ou répertoire <code><machin></code>, on tapera : <code>test -e <machin></code> ('e' comme '''''e'''xist'') | ||
Pour plus d'information faites : < | Pour plus d'information faites : <code>man test</code>. | ||
On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à < | On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à <code>-o</code> ('o' comme '''''<font size="+1">o</font>'''r''), 'et' correspond à <code>-a</code> ('a' comme '''''<font size="+1">a</font>'''nd'') (à nouveau allez voir la man page), exemple : | ||
< | <code multi>test -x /bin/sh -a -d /etc</code> | ||
Cette instruction teste l'existence de l'éxécutable < | Cette instruction teste l'existence de l'éxécutable <code>/bin/sh</code> (<code>-x /bin/sh</code>) et (<code>-a</code>) la présence d'un répertoire <code>/etc</code> (<code>-d /etc</code>). | ||
On peut remplacer la commande < | On peut remplacer la commande <code>test <un test></code> par <code>[ <un test> ]</code> qui est plus lisible, exemple : | ||
< | <code>if [ -x /bin/sh ] ; then | ||
# ('x' comme ''e'''x'''ecutable'') | |||
echo /bin/sh est exécutable. C\'est bien. | |||
else | |||
echo /bin/sh n\'est pas exécutable. | |||
echo Votre système n\'est pas normal. | |||
fi</code> | |||
Toujours avec les crochets de < | Toujours avec les crochets de <code>test</code>, si vous n'avez qu'une seule chose à faire en fonction du résultat d'un test, alors vous pouvez utiliser la syntaxe suivante : | ||
< | <code>[ -x /bin/sh ] && echo /bin/sh est exécutable.</code><br /> ou encore :<br /><code>[ -x /bin/sh ] || echo /bin/sh n\'est pas exécutable.</code> | ||
L'affichage du message est effectué, dans le premier cas que si le test est vrai et dans le second cas, que si le test est faux. Dans l'exemple on teste si /bin/sh est un fichier exécutable.<br /> Cela | L'affichage du message est effectué, dans le premier cas que si le test est vrai et dans le second cas, que si le test est faux. Dans l'exemple on teste si /bin/sh est un fichier exécutable.<br /> Cela allège le script sans pour autant le rendre illisible, si cette syntaxe est utilisée à bon escient. | ||
Mais il n'y a pas que la commande <code>test</code> qui peut être employée. Par exemple, la commande <code>grep</code> renvoie 0 quand la recherche a réussi et 1 quand la recherche a échoué. <br />Par exemple : | |||
< | <code>if grep -E "^frederic:" /etc/passwd > /dev/null ; then | ||
echo L\'utilisateur frederic existe. | |||
else | |||
echo L'utilisateur frederic n\'existe pas. | |||
fi</code> | |||
Cette série d'instruction teste la présence de l'utilisateur < | Cette série d'instruction teste la présence de l'utilisateur <code>frederic</code> dans le fichier <code>/etc/passwd</code>. Vous remarquerez que l'on a fait suivre la commande <code>grep</code> d'une redirection vers <code>/dev/null</code> pour que le résultat de cette commande ne soit pas affiché : c'est une utilisation classique. Ceci explique aussi l'expression : "Ils sont tellement intéressants, tes mails, que je les envoie vers /dev/null" ;-). | ||
=== Faire quelque chose de différent suivant la valeur d'une variable === | === Faire quelque chose de différent suivant la valeur d'une variable === | ||
L'instruction < | L'instruction <code>case ... esac</code> permet de modifier le déroulement du script selon la valeur d'un paramètre ou d'une variable. On l'utilise le plus souvent quand les valeurs possibles sont en nombre restreint et peuvent être prévues. Les imprévus peuvent alors être représentés par le signe *. Demandons par exemple à l'utilisateur s'il souhaite afficher ou non les fichiers cachés du répertoire en cours. | ||
< | <code>#!/bin/sh | ||
# pose la question et récupère la réponse | |||
echo "Le contenu du répertoire courant va être affiché." | |||
echo -n "Souhaitez-vous afficher aussi les fichiers cachés (oui/non) : " | |||
read reponse | |||
# agit selon la réponse | |||
case $reponse in | |||
oui) | |||
clear | |||
ls -a;; | |||
non) | |||
ls;; | |||
*) echo "Erreur, vous deviez répondre par oui ou par non.";; | |||
esac</code> | |||
Seules les réponses "oui" et "non" sont réellement attendues dans ce script, toute autre réponse engendrera le message d'erreur. On notera qu'ici l'écran est effacé avant l'affichage dans le cas d'une réponse positive, mais pas dans celui d'une réponse négative. Lorsque vous utilisez l'instruction < | Seules les réponses "oui" et "non" sont réellement attendues dans ce script, toute autre réponse engendrera le message d'erreur. On notera qu'ici l'écran est effacé avant l'affichage dans le cas d'une réponse positive, mais pas dans celui d'une réponse négative. Lorsque vous utilisez l'instruction <code>case ... esac</code>, faites bien attention de ne pas oublier les doubles points-virgules terminant les instructions de chacun des cas envisagés. | ||
=== Faire la même chose pour tous les éléments d'une liste === | === Faire la même chose pour tous les éléments d'une liste === | ||
Ligne 199 : | Ligne 335 : | ||
Lorsqu'on programme, on est souvent amené à faire la même chose '''pour tous''' les élément d'une liste. Dans un shell script, il est bien évidemment possible de ne pas réécrire dix fois la même chose. On dira que l'on fait une boucle. L'instruction qui réalise une boucle est | Lorsqu'on programme, on est souvent amené à faire la même chose '''pour tous''' les élément d'une liste. Dans un shell script, il est bien évidemment possible de ne pas réécrire dix fois la même chose. On dira que l'on fait une boucle. L'instruction qui réalise une boucle est | ||
< | <code>for <variable> in <liste de valeurs pour la variable> ; do | ||
<instruction 1> | |||
... | |||
<instruction n> | |||
done</code> | |||
Voyons comment ça fonctionne. Supposons que nous souhaitions renommer tous nos fichiers *.tar.gz en *.tar.gz.old, nous taperons le script suivant : | Voyons comment ça fonctionne. Supposons que nous souhaitions renommer tous nos fichiers *.tar.gz en *.tar.gz.old, nous taperons le script suivant : | ||
< | <code>#!/bin/sh | ||
# x prend chacune des valeurs possibles correspondant | |||
# au motif : *.tar.gz | |||
for x in *.tar.gz ; do | |||
# tous les fichiers $x sont renommés $x.old | |||
echo "$x -> $x.old" | |||
mv $x $x.old | |||
# on finit notre boucle | |||
done</code> | |||
Simple, non ? Un exemple plus complexe ? Supposons que nous voulions parcourir tous les sous-répertoires du répertoire courant pour faire cette même manipulation. Nous pourrons taper : | Simple, non ? Un exemple plus complexe ? Supposons que nous voulions parcourir tous les sous-répertoires du répertoire courant pour faire cette même manipulation. Nous pourrons taper : | ||
< | <code> 1:#!/bin/sh | ||
2:for REP in `find -type d` ; do | |||
3: for FICH in $REP/*.tar.gz ; do | |||
4: if [ -f $FICH ] ; then | |||
5: mv $FICH $FICH.old | |||
6: else | |||
7: echo On ne renomme pas $FICH car ce n\'est pas un répertoire | |||
8: fi | |||
9: done | |||
10:done</code> | |||
<u>Explications</u> : dans le premier 'for', on a précisé comme liste : < | <u>Explications</u> : dans le premier 'for', on a précisé comme liste : <code>`find -type d`</code> (attention au sens des apostrophes, sur un clavier azerty français, on obtient ce symbole en appuyant sur <code>ALTGR+é</code>, ce ne sont pas des simples quotes '). <br />Lorsque l'on tape une commande entre apostrophes inverses, le shell exécute d'abord cette commande, et remplace l'expression entre apostrophes inverses par la sortie standard de cette commande (ce qu'elle affiche à l'écran). Donc, dans le cas qui nous intéresse, la liste est le résultat de la commande <code>find -type d</code>, c'est à dire la liste de tous les sous-répertoires du répertoire courant. <br />Ainsi, en ligne 2, on fait prendre à la variable REP le nom de chacun des sous-répertoires du répertoire courant, puis (en ligne 3) on fait prendre à la variable FICH le nom de chacun des fichiers .tar.gz de $REP (un des sous-répertoires), puis si $FICH est un fichier, on le renomme, sinon on affiche un avertissement. | ||
<u>Remarque</u> : ce n'est pas le même fonctionnement que la boucle < | <u>Remarque</u> : ce n'est pas le même fonctionnement que la boucle <code>for</code> d'autres langage (le pascal, le C ou le basic par exemple). | ||
=== Faire une même chose tant qu'un certaine condition est remplie === | === Faire une même chose tant qu'un certaine condition est remplie === | ||
Ligne 217 : | Ligne 374 : | ||
Pour faire une certaine chose '''tant qu''''une condition est remplie, on utilise un autre type de boucle : | Pour faire une certaine chose '''tant qu''''une condition est remplie, on utilise un autre type de boucle : | ||
< | <code>while <un test> ; do | ||
<instruction 1> | |||
... | |||
<instruction n> | |||
done</code> | |||
Supposons, par exemple que vous souhaitiez afficher les 100 premiers nombres (pour une obscure raison), alors vous taperez : | Supposons, par exemple que vous souhaitiez afficher les 100 premiers nombres (pour une obscure raison), alors vous taperez : | ||
< | <code>i=0 | ||
while [ $i -lt 100 ] ; do | |||
echo $i | |||
i=$[$i+1] | |||
done</code> | |||
<u>Remarque</u> : < | <u>Remarque</u> : <code>-lt</code> signifie "'''<code><font color="#ff0000">l</font></code>'''esser '''<code><font color="#ff0000">t</font></code>'''han" ou "plus petit que" (et <code>-gt</code> signifie "plus grand", ou "'''<code><font color="#ff0000">g</font></code>'''reater '''<code><font color="#ff0000">t</font></code>'''han"). | ||
Ici, on va afficher le contenu de < | Ici, on va afficher le contenu de <code>i</code> et lui ajouter 1 tant que <code>i</code> sera (<code>-lt</code>) plus petit que 100. Remarquez que 100 ne s'affiche pas, car <code>-lt</code> est "plus petit", mais pas "plus petit ou égal" (dans ce cas, utilisez <code>-le</code> et <code>-ge</code> pour "plus grand ou égal"). | ||
=== Refaire à un autre endroit la même chose === | === Refaire à un autre endroit la même chose === | ||
Souvent, vous voudrez refaire ce que vous venez de taper autre part dans votre script. Dans ce cas il est inutile de retaper la même chose, préférez utiliser l'instruction < | Souvent, vous voudrez refaire ce que vous venez de taper autre part dans votre script. Dans ce cas il est inutile de retaper la même chose, préférez utiliser l'instruction <code>function</code> qui permet de réutiliser une portion de script (on dit : une "'''fonction'''"). Voyons un exemple : | ||
< | <code>#!/bin/sh | ||
function addpath () | |||
{ | |||
if echo $PATH | grep -v $1 >/dev/null; then | |||
PATH=$PATH:$1; | |||
fi; | |||
PATH=`echo $PATH|sed s/::/:/g` | |||
} | |||
addpath /opt/apps/bin | |||
addpath /opt/office52/program | |||
addpath /opt/gnome/bin | |||
export PATH</code> | |||
Au début, nous avons défini une fonction nommée < | Au début, nous avons défini une fonction nommée <code>addpath</code> dont le but est d'ajouter le premier argument (<code>$1</code>) de la fonction <code>addpath</code> à la varaible <code>PATH</code> si ce premier argument n'est pas déjà présent (<code>grep -v $1</code>) dans la variable <code>PATH</code>, ainsi que supprimer les chemins vides (<code>sed s/::/:/g</code>) de PATH. <br />Ensuite, nous exécutons cette fonction pour trois arguments : /opt/apps/bin, /opt/office52/bin et /opt/gnome/bin. | ||
En fait, une fonction est seulement un script écrit à l'intérieur d'un script. Les fonctions permettent surtout de ne pas multiplier les petits scripts, ainsi que de partager des variables sans se préoccuper de la clause < | En fait, une fonction est seulement un script écrit à l'intérieur d'un script. Les fonctions permettent surtout de ne pas multiplier les petits scripts, ainsi que de partager des variables sans se préoccuper de la clause <code>export</code> mais cela constitue une utilisation avancée du shell, nous n'irons pas plus loin dans cet article. | ||
<u>Remarque</u> : le mot < | <u>Remarque</u> : le mot <code>function</code> peut être omis, mais son utilisation facilite la lecture du script. | ||
=== Autres types de répétitions. === | === Autres types de répétitions. === | ||
Il existe d'autres types de répétitions, mais nous ne nous en occuperons pas dans cet article, je vous conseille la lecture, forcément profitable, de la "man page" de bash (< | Il existe d'autres types de répétitions, mais nous ne nous en occuperons pas dans cet article, je vous conseille la lecture, forcément profitable, de la "man page" de bash (<code>man bash</code>). | ||
À vous de jouer ! | À vous de jouer ! |
Version du 26 octobre 2005 à 09:20
Programmation de Script: Une introduction
Introduction
Vous aurez envie d'écrire un script (petit programme écrit avec un langage simple : shell, perl ou autre) dès que vous aurez tapé dans un terminal quatre fois la même série de commandes et que vous vous apercevrez que vous êtes amené à le refaire de nombreuses fois.
Un script est une suite d'instructions élémentaires qui sont éxécutées de façon séquencielle (les unes après les autres) par le langage de script. Dans cet article nous nous limiterons à l'utilisation du shell comme langage, et en particulier à du shell bash
. En guise de première introduction, vous pouvez lire ce qui concerne les commandes du shell dans l'article Le Shell et les Commandes. Attention, n'espérez pas que le présent document constitue un manuel complet de programmation ! C'est une courte introduction qui nous l'espérons, vous permettra d'écrire de petits scripts qui vous rendront de précieux services.
Notions de base
Mon premier script.
Pour commencer, il faut savoir qu'un script est un fichier texte standard pouvant être créé par n'importe quel éditeur : [Software-soft_edit-vi|vi], [Software-soft_edit-emacs|emacs], kedit, gnotepad, ou autre. D'autre part, conventionnellement, un script commence par une ligne de commentaire contenant le nom du langage à utiliser pour interpréter ce script, soit dans notre cas : /bin/sh
(on parle alors de "script shell"). Donc un script shell élémentaire pourrait être :
#!/bin/sh
<cadre type=note>
Note : "#!"
se prononce "she bang", soit "chi-bang".
</cadre>
Évidemment un tel script ne fait rien ! Changeons cela. La commande qui affiche quelque chose à l'écran est echo
. Donc pour créer le script bonjour_monde
nous pouvons écrire :
#!/bin/sh
echo "Bonjour, Monde !"
echo "un premier script est né."
Comment on l'éxécute ? C'est simple il suffit de faire :
[user@becane user]$ sh bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
C'est pas cool, vous préféreriez taper quelque chose comme :
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
C'est possible si vous avez au préalable rendu votre script exécutable par la commande :
[user@becane user]$ chmod +x bonjour_monde
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
Résumons : un script shell commence par : #!/bin/sh
, il contient des commandes du shell et est rendu exécutable par chmod +x
.
Quelques conseils concernant les commentaires
Dans un shell-script, est considéré comme un commentaire tout ce qui suit le caractère # et ce, jusqu'à la fin de la ligne. Usez et abusez des commentaires : lorsque vous relirez un script 6 mois après l'avoir écrit, vous serez bien content de l'avoir documenté. Un programme n'est jamais trop documenté. Par contre, il peut être mal documenté ! Un commentaire est bon lorsqu'il décrit pourquoi on fait quelque chose, pas quand il décrit ce que l'on fait. Exemple :
#!/bin/sh
- pour i parcourant tous les fichiers,
for i in * ; do
- copier le fichier vers .bak
cp $i $i.bak
- fin pour
done
Que fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre :
#!/bin/sh
- on veut faire un copie de tous les fichiers
for i in * ; do
- sous le nom *.bak
cp $i $i.bak
done
Là, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.)
Le passage de paramètres
Un script ne sera, en général, que d'une utilisation marginale si vous ne pouvez pas modifier son comportement d'une manière ou d'une autre. On obtient cet effet en "passant" un (ou plusieurs) paramètre(s) au script via la ligne de commande. Voyons comment faire cela. Soit le script essai01
:
#!/bin/sh
echo le paramètre \$1 est \"$1\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$3 est \"$3\"
Que fait-il ? Il affiche, les uns après les autres les trois premiers paramètres du script, donc si l'on tappe :
$ ./essai01 paramètre un
le paramètre $1 est "paramètre"
le paramètre $2 est "un"
le paramètre $3 est ""
$ _
Donc, les variables $1
, $2
... $9
contiennent les "mots" numéro 1, 2 ... 9 de la ligne de commande. Attention : par "mot" on entend ensemble de caractères ne contenant pas de caractères de séparations. Les caractères de séparation sont l'espace, la tabulation, le point virgule.
Vous avez sans doute remarqué que j'ai utilisé les caractères : \$
à la place de $
ainsi que \"
à la place de "
dans le script. Pour quelle raison ? La raison est simple, si l'on tape : echo "essai"
on obtient : essai
, si l'on veut obtenir "essai"
il faut dire à echo
que le caractère "
n'indique pas le début d'une chaîne de caractère (comme c'est le comportement par défaut) mais que ce caractère fait partie de la chaîne : on dit que l'on "échappe" ou "protège" le caractère "
en tapant \"
. En "échappant" le caractère \
(par \\
) on obtient le caractère \
sans signification particulière. On peut dire que le caractère \
devant un autre lui fait perdre sa signification particulière s'il en a une, ne fait rien si le caractère qui suit \
n'en a pas.
Maintenant, essayons de taper :
$ ./essai01 *
le paramètre $1 est "Mail"
le paramètre $2 est "essai01"
le paramètre $3 est "nsmail"$ _
Le résultat doit être sensiblement différent sur votre machine : il dépend du contenu de votre répertoire courant. Que s'est-il passé ? Le shell a remplacé le caractère * par la liste de tous les fichiers non cachés présents dans le répertoire actif. En fait, toutes les substitutions du shell sont possibles ! C'est le shell qui "substitue" aux paramètres des valeurs étendues par les caractères spéciaux : * (toute suite de caractères) ? (un caractère grave quelconque), [dze] (l'un des caractères d, z ou e), [d-z] (les caractères de 'd' à 'z')... Autre exemple :
$ ./essai01 \*
le paramètre $1 est "*"
le paramètre $2 est ""
le paramètre $3 est ""$ _
Hé oui, on a "échappé" le caractère *
donc il a perdu sa signification particulière : il est redevenu un simple *
.
C'est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser la commande shift ; à titre d'exemple voici le script essai02
:
#!/bin/sh
echo le paramètre 1 est \"$1\"
shift
echo le paramètre 2 est \"$1\"
shift
echo le paramètre 2 est \"$1\"
shift
echo le paramètre 4 est \"$1\"
shift
echo le paramètre 5 est \"$1\"
shift
echo le paramètre 6 est \"$1\"
shift
echo le paramètre 7 est \"$1\"
shift
echo le paramètre 8 est \"$1\"
shift
echo le paramètre 9 est \"$1\"
shift
echo le paramètre 10 est \"$1\"
shift
echo le paramètre 11 est \"$1\"
Si vous tapez :
$ ./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13
le paramètre 1 est "1"
le paramètre 2 est "2"
le paramètre 2 est "3"
le paramètre 4 est "4"
le paramètre 5 est "5"
le paramètre 6 est "6"
le paramètre 7 est "7"
le paramètre 8 est "8"
le paramètre 9 est "9"
le paramètre 10 est "10"
le paramètre 11 est "11"$ _
A chaque appel de shift
les paramètres sont décalés d'un numéro : le paramètre 2 devient le paramètre 1, 3 devient 2, etc... Évidemment le paramètre 1 est perdu par l'appel de shift : vous devez donc vous en servir avant d'appeler shift
(ou le sauvegarder dans une variable).
Les variables
Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : $1
, $2
etc. Ce sont les substitutions des variables 1
, 2
, etc. par leur valeurs. Mais vous pouvez définir et utiliser n'importe quel nom. Attention toutefois à ne pas confondre le nom d'une variable (notée par exemple machin
) et son contenu (noté dans cas $machin
). Vous connaissez peut-être la variable PATH
(attention, le shell différencie les majuscules des minuscules) qui contient la liste des répertoires (séparés par des ":"
) dans lesquels il doit rechercher les programmes. Si dans un script vous tapez :
1:#!/bin/sh
2:PATH=/bin # PATH contient /bin
3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin
4:PATH=/bin # PATH contient /bin
5:PATH=$PATH:/usr/bin # PATH contient /bin:/usr/bin
Les numéros ne sont là que pour repérer les lignes, il ne faut pas les taper.La ligne 3 est très certainement une erreur, à gauche du signe "="
il faut une variable (donc un nom sans $
) mais à droite de ce même signe il faut une valeur, et la valeur que l'on a mis est "PATH:/usr/bin"
: il n'y a aucune substitution à faire. Par contre la ligne 5 est certainement correcte : à droite du "="
on a mis "$PATH:/usr/bin"
, la valeur de $PATH
étant "/bin"
, la valeur après substitution par le shell de "$PATH:/usr/bin"
est "/bin:/usr/bin"
. Donc, à la fin de la ligne 5, la valeur de la variable PATH
est "/bin:/usr/bin"
.
Attention : il ne doit y avoir aucun espace de part et d'autre du signe "="
.
Résumons : MACHIN
est un nom de variable que l'on utilise lorsque l'on a besoin d'un nom de variable (mais pas de son contenu), et $MACHIN
est le contenu de la variable MACHIN
que l'on utilise lorsque l'on a besoin du contenu de cette variable.
Variables particulières
Il y a un certain nombre de variables particulières, en voici quelques unes :
- la variable
*
(dont le contenu est$*
) contient l'ensemble de tous les "mots" qui on été passé au script (c'est à dire toute la ligne de commande, sans le nom du script). - la variable
#
contient le nombre de paramètres ($#
) qui ont été passés au programme. - la variable
0
(zéro) contient le nom du script (ou du lien si le script a été appelé depuis un lien).
Il y en a d'autres, moins utilisées : allez voir la man page de bash
.
Saisir la valeur d'une variable
Les paramètres permettent à l'utilisateur d'agir sur le déroulement du script avant son exécution. Mais il est aussi souvent intéressant de pouvoir agir sur le déroulement du script lors de son exécution, c'est ce que permet la commande : read nom_variable
. Dans cette commande vous pouvez bien sûr remplacer nom_variable par le nom de variable qui vous convient le mieux. Voici un exemple simple.
#!/bin/sh
echo -n "Entrez votre prénom : "
read prenom
echo -n "Entrez votre nom de login : "
read nomlogin
echo "Le nom de login de $prenom est $nomlogin."
Ce script se déroule ainsi :
./essai02bis
Entrez votre prénom : Marc
Entrez votre nom de login : spoutnik
Le nom de login de Marc est spoutnik.
Lors du déroulement du script vous devez valider vos entrées en appuyant sur la touche "Entrée".
Arithmétique
Vous vous doutez bien qu'il est possible de faire des calculs avec le shell. En fait, le shell ne "sait" faire que des calculs sur les nombres entiers (ceux qui n'ont pas de virgules ;-). Pour faire un calcul il faut encadrer celui-ci de : $(( un calcul ))
ou $[ un calcul ]
. Exemple, le script essai03 :
#!/bin/sh
echo 2+3*5 = $((2+3*5))
MACHIN=12
echo MACHIN*4 = $[$MACHIN*4]
Affichera :
$ sh essai03
2+3*5 = 17
MACHIN*4 = 48
Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est "**"
(ie : 2 puissance 5 s'écrit : 2**5
). On peut utiliser des parenthèses pour modifier l'ordre des calculs.
Les instructions de contrôle de scripts
Les instructions de contrôle du shell permettent de modifier l'exécution purement séquencielle d'un script. Jusqu'à maintenant, les scripts que nous avons créés n'étaient pas très complexes. Ils ne pouvaient de toute façon pas l'être car nous ne pouvions pas modifier l'ordre des instructions, ni en répéter.
L'exécution conditionnelle
Lorsque vous programmerez des scripts, vous voudrez que vos scripts fassent une chose si une certaine condition est remplie et autre chose si elle ne l'est pas. La construction de bash qui permet cela est le fameux test : if then else fi
. Sa syntaxe est la suivante (la partie else...
est optionnelle) :
if <test> ;
then
<instruction 1>
<instruction 2>
...
<instruction n>
else
<instruction n+1>
...
<instruction n+p>
fi
Il faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable ?
dont la valeur est, rappelons le : "$?
". Pour le shell une valeur nulle est synonyme de VRAI et une valeur non nulle est synonyme de FAUX. Ceci parce que, en général les programmes renvoie zéro quand tout c'est bien passé et un code d'erreur (nombre non nul) quand il s'en est produit une.
Il existe deux programmes particuliers : false
et true
. true
renvoie toujours 0 et false
renvoie toujours 1. Sachant cela, voyons ce que fait le programme suivant :
#!/bin/sh
if true ;
then
echo Le premier test est VRAI($?)
else
echo Le premier test est FAUX($?)
fi
if false ;
then
echo Le second test est VRAI($?)
else
echo Le second test est FAUX($?)
fi
Affichera :
$ ./test
Le premier test est VRAI(0)
Le second test est FAUX(1)
$ _
On peut donc conclure que l'instruction if ... then ... else ... fi
, fonctionne de la manière suivante : si (if en anglais) le test est VRAI(0) alors (then en anglais) le bloc d'instructions compris entre le then
et le else
(ou le fi
en l'absence de else
) est exécuté, sinon (else en anglais) le test est FAUX(différent de 0)) et on exécute le bloc d'instructions compris entre le else
et le fi
si ce bloc existe.
Bon, évidemment, des tests de cet ordre ne paraissent pas très utiles. Voyons maintenant de vrais tests.
Les tests
Un test, nous l'avons vu, n'est rien de plus qu'une commande standard. Une des commandes standard est test
, sa syntaxe est un peu complexe, je vais la décrire avec des exemples.
- si l'on veut tester l'existence d'un répertoire
<machin>
, on tapera :test -d <machin>
('d' comme directory) - si l'on veut tester l'existence d'un fichier
<machin>
, on tapera :test -f <machin>
('f' comme file) - si l'on veut tester l'existence d'un fichier ou répertoire
<machin>
, on tapera :test -e <machin>
('e' comme exist)
Pour plus d'information faites : man test
.
On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à -o
('o' comme or), 'et' correspond à -a
('a' comme and) (à nouveau allez voir la man page), exemple :
test -x /bin/sh -a -d /etc
Cette instruction teste l'existence de l'éxécutable /bin/sh
(-x /bin/sh
) et (-a
) la présence d'un répertoire /etc
(-d /etc
).
On peut remplacer la commande test <un test>
par [ <un test> ]
qui est plus lisible, exemple :
if [ -x /bin/sh ] ; then
- ('x' comme executable)
echo /bin/sh est exécutable. C\'est bien.
else
echo /bin/sh n\'est pas exécutable.
echo Votre système n\'est pas normal.
fi
Toujours avec les crochets de test
, si vous n'avez qu'une seule chose à faire en fonction du résultat d'un test, alors vous pouvez utiliser la syntaxe suivante :
[ -x /bin/sh ] && echo /bin/sh est exécutable.
ou encore :[ -x /bin/sh ] || echo /bin/sh n\'est pas exécutable.
L'affichage du message est effectué, dans le premier cas que si le test est vrai et dans le second cas, que si le test est faux. Dans l'exemple on teste si /bin/sh est un fichier exécutable.
Cela allège le script sans pour autant le rendre illisible, si cette syntaxe est utilisée à bon escient.
Mais il n'y a pas que la commande test
qui peut être employée. Par exemple, la commande grep
renvoie 0 quand la recherche a réussi et 1 quand la recherche a échoué.
Par exemple :
if grep -E "^frederic:" /etc/passwd > /dev/null ; then
echo L\'utilisateur frederic existe.
else
echo L'utilisateur frederic n\'existe pas.
fi
Cette série d'instruction teste la présence de l'utilisateur frederic
dans le fichier /etc/passwd
. Vous remarquerez que l'on a fait suivre la commande grep
d'une redirection vers /dev/null
pour que le résultat de cette commande ne soit pas affiché : c'est une utilisation classique. Ceci explique aussi l'expression : "Ils sont tellement intéressants, tes mails, que je les envoie vers /dev/null" ;-).
Faire quelque chose de différent suivant la valeur d'une variable
L'instruction case ... esac
permet de modifier le déroulement du script selon la valeur d'un paramètre ou d'une variable. On l'utilise le plus souvent quand les valeurs possibles sont en nombre restreint et peuvent être prévues. Les imprévus peuvent alors être représentés par le signe *. Demandons par exemple à l'utilisateur s'il souhaite afficher ou non les fichiers cachés du répertoire en cours.
#!/bin/sh
- pose la question et récupère la réponse
echo "Le contenu du répertoire courant va être affiché."
echo -n "Souhaitez-vous afficher aussi les fichiers cachés (oui/non) : "
read reponse
- agit selon la réponse
case $reponse in
oui)
clear
ls -a;;
non)
ls;;
*) echo "Erreur, vous deviez répondre par oui ou par non.";;
esac
Seules les réponses "oui" et "non" sont réellement attendues dans ce script, toute autre réponse engendrera le message d'erreur. On notera qu'ici l'écran est effacé avant l'affichage dans le cas d'une réponse positive, mais pas dans celui d'une réponse négative. Lorsque vous utilisez l'instruction case ... esac
, faites bien attention de ne pas oublier les doubles points-virgules terminant les instructions de chacun des cas envisagés.
Faire la même chose pour tous les éléments d'une liste
Lorsqu'on programme, on est souvent amené à faire la même chose pour tous les élément d'une liste. Dans un shell script, il est bien évidemment possible de ne pas réécrire dix fois la même chose. On dira que l'on fait une boucle. L'instruction qui réalise une boucle est
for <variable> in <liste de valeurs pour la variable> ; do
<instruction 1>
...
<instruction n>
done
Voyons comment ça fonctionne. Supposons que nous souhaitions renommer tous nos fichiers *.tar.gz en *.tar.gz.old, nous taperons le script suivant :
#!/bin/sh
- x prend chacune des valeurs possibles correspondant
- au motif : *.tar.gz
for x in *.tar.gz ; do
# tous les fichiers $x sont renommés $x.old
echo "$x -> $x.old"
mv $x $x.old
# on finit notre boucle
done
Simple, non ? Un exemple plus complexe ? Supposons que nous voulions parcourir tous les sous-répertoires du répertoire courant pour faire cette même manipulation. Nous pourrons taper :
1:#!/bin/sh
2:for REP in `find -type d` ; do
3: for FICH in $REP/*.tar.gz ; do
4: if [ -f $FICH ] ; then
5: mv $FICH $FICH.old
6: else
7: echo On ne renomme pas $FICH car ce n\'est pas un répertoire
8: fi
9: done
10:done
Explications : dans le premier 'for', on a précisé comme liste : `find -type d`
(attention au sens des apostrophes, sur un clavier azerty français, on obtient ce symbole en appuyant sur ALTGR+é
, ce ne sont pas des simples quotes ').
Lorsque l'on tape une commande entre apostrophes inverses, le shell exécute d'abord cette commande, et remplace l'expression entre apostrophes inverses par la sortie standard de cette commande (ce qu'elle affiche à l'écran). Donc, dans le cas qui nous intéresse, la liste est le résultat de la commande find -type d
, c'est à dire la liste de tous les sous-répertoires du répertoire courant.
Ainsi, en ligne 2, on fait prendre à la variable REP le nom de chacun des sous-répertoires du répertoire courant, puis (en ligne 3) on fait prendre à la variable FICH le nom de chacun des fichiers .tar.gz de $REP (un des sous-répertoires), puis si $FICH est un fichier, on le renomme, sinon on affiche un avertissement.
Remarque : ce n'est pas le même fonctionnement que la boucle for
d'autres langage (le pascal, le C ou le basic par exemple).
Faire une même chose tant qu'un certaine condition est remplie
Pour faire une certaine chose tant qu'une condition est remplie, on utilise un autre type de boucle :
while <un test> ; do
<instruction 1>
...
<instruction n>
done
Supposons, par exemple que vous souhaitiez afficher les 100 premiers nombres (pour une obscure raison), alors vous taperez :
i=0
while [ $i -lt 100 ] ; do
echo $i
i=$[$i+1]
done
Remarque : -lt
signifie "l
esser t
han" ou "plus petit que" (et -gt
signifie "plus grand", ou "g
reater t
han").
Ici, on va afficher le contenu de i
et lui ajouter 1 tant que i
sera (-lt
) plus petit que 100. Remarquez que 100 ne s'affiche pas, car -lt
est "plus petit", mais pas "plus petit ou égal" (dans ce cas, utilisez -le
et -ge
pour "plus grand ou égal").
Refaire à un autre endroit la même chose
Souvent, vous voudrez refaire ce que vous venez de taper autre part dans votre script. Dans ce cas il est inutile de retaper la même chose, préférez utiliser l'instruction function
qui permet de réutiliser une portion de script (on dit : une "fonction"). Voyons un exemple :
#!/bin/sh
function addpath ()
{
if echo $PATH | grep -v $1 >/dev/null; then
PATH=$PATH:$1;
fi;
PATH=`echo $PATH|sed s/::/:/g`
}
addpath /opt/apps/bin
addpath /opt/office52/program
addpath /opt/gnome/bin
export PATH
Au début, nous avons défini une fonction nommée addpath
dont le but est d'ajouter le premier argument ($1
) de la fonction addpath
à la varaible PATH
si ce premier argument n'est pas déjà présent (grep -v $1
) dans la variable PATH
, ainsi que supprimer les chemins vides (sed s/::/:/g
) de PATH.
Ensuite, nous exécutons cette fonction pour trois arguments : /opt/apps/bin, /opt/office52/bin et /opt/gnome/bin.
En fait, une fonction est seulement un script écrit à l'intérieur d'un script. Les fonctions permettent surtout de ne pas multiplier les petits scripts, ainsi que de partager des variables sans se préoccuper de la clause export
mais cela constitue une utilisation avancée du shell, nous n'irons pas plus loin dans cet article.
Remarque : le mot function
peut être omis, mais son utilisation facilite la lecture du script.
Autres types de répétitions.
Il existe d'autres types de répétitions, mais nous ne nous en occuperons pas dans cet article, je vous conseille la lecture, forcément profitable, de la "man page" de bash (man bash
).
À vous de jouer !
Copyright
© 29/08/2000 Fred, Marc
Ce document est publié sous licence Creative Commons Attribution, Partage à l'identique, Contexte non commercial 2.0 : http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ |