« Script shell » : différence entre les versions

De Lea Linux
Aller à la navigation Aller à la recherche
(conversion de la documentation originale de Léa par HTML::WikiConverter)
 
m (Lea a déplacé la page Dev-shell script vers Script shell : Titre mal orthographié)
 
(36 versions intermédiaires par 18 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
[[Category:Développer sous Linux]]
[[Catégorie:Développement]]
= Programmation de Script: Une introduction =


<div class="leatitre">Programmation de Script: Une introduction</div><div class="leapar">par Fred, correction et ajouts de Marc</div><div class="leadesc">Comment écrire de petits scripts permettant d'automatiser la réalisation de taches répétitives.</div>
= Programmation de scripts : Une introduction =
 
Comment écrire de petits scripts permettant d’automatiser la réalisation de taches répétitives.
----
----


== 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 ammené à le refaire de nombreuses fois. <br />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. <br />Dans cet article nous nous limiterons à l'utilisation du shell comme langage, et en particulier à du shell <tt>bash</tt>. En guise de première introduction, vous pouvez lire ce qui concerne les commandes du shell dans l'article [http://lea-linux.org/admin/shell.php3 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.
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'' <span class="code">bash</span>. 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 ==


=== 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 : [[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 : <span class="code">/bin/sh</span> (on parle alors de "script shell"). Donc un script shell élémentaire pourrait être :


Pour commencer, il faut savoir qu'un script est un fichier texte standard pouvant être créé par n'importe quel éditeur : [http://lea-linux.org/software/vi.php3 vi], [http://lea-linux.org/software/emacs.php3 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 : <tt>/bin/sh</tt> (on parle alors de "script shell"). Donc un script shell élémentaire pourrait être :
<span class="code">#!/bin/sh</span>


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt></blockquote>  
<cadre type=note>
'''Note :''' <span class="code">"#!"</span> se prononce « ''she bang'' », soit « chi-bang ».
</cadre>


<u>Note</u> : <tt>"#!"</tt> se prononce "''she bang''", soit "chi-bang". <br />Évidemment un tel script ne fait rien ! Changeons cela. La commande qui affiche quelque chose à l'écran est <tt>echo</tt>. Donc pour créer le script <tt>bonjour_monde</tt> nous pouvons écrire :
Évidemment un tel script ne fait rien ! Changeons cela. La commande qui affiche quelque chose à l’écran est <span class="code">echo</span>. Donc pour créer le script <span class="code">bonjour_monde</span> nous pouvons écrire :


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>echo "Bonjour, Monde !"</tt><br /><tt>echo "un premier script est né."</tt></blockquote>
<span class="code">#!/bin/sh
echo "Bonjour, Monde !"
echo "Un premier script est né."</span>


Comment on l'éxécute ? C'est simple il suffit de faire :
Comment on l’exécute ? C’est simple il suffit de taper :


<blockquote><tt>[user@becane user]$ '''sh bonjour_monde'''</tt><br /><tt>Bonjour, Monde !</tt><br /><tt>un premier script est né.</tt><br /><tt>[user@becane user]$ <font color="red">_</font></tt></blockquote>
<span class="code">[user@becane user]$ sh bonjour_monde
Bonjour, Monde !
Un premier script est né.
[user@becane user]$ _</span>


C'est pas cool, vous préféreriez taper quelque chose comme :
C’est pas cool, vous auriez préféré taper quelque chose comme :


<blockquote><tt>[user@becane user]$ '''./bonjour_monde'''</tt><br /><tt>Bonjour, Monde !</tt><br /><tt>un premier script est né.</tt><br /><tt>[user@becane user]$ _</tt></blockquote>
<span class="code">[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
Un premier script est né.
[user@becane user]$ _</span>


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 :


<blockquote><tt>[user@becane user]$ '''chmod +x bonjour_monde'''</tt><br /><tt>[user@becane user]$ '''./bonjour_monde'''</tt><br /><tt>Bonjour, Monde !</tt><br /><tt>un premier script est né.</tt><br /><tt>[user@becane user]$ _</tt></blockquote>  
<span class="code">[user@becane user]$ chmod +x bonjour_monde
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
Un premier script est né.
[user@becane user]$ _</span>


<u>Résumons</u> : un script shell commence par : <tt><nowiki>#!/bin/sh</nowiki></tt>, il contient des commandes du shell et est rendu exécutable par <tt>chmod +x</tt>.
<u>Résumons</u> : un script ''shell'' commence par : <span class="code">#!/bin/sh</span>, il contient des commandes du shell et est rendu exécutable par <span class="code">chmod +x</span>.


=== 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. <br />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 :
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é, en revanche 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.


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt><nowiki># pour i parcourant tous les fichiers,</nowiki></tt><br /><tt>for i in * ; do</tt><br /><tt><nowiki># copier le fichier vers .bak</nowiki></tt><br /><tt> cp $i $i.bak</tt><br /><tt><nowiki># fin pour</nowiki></tt><br /><tt>done</tt></blockquote>
Exemple :


Que fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre :
<span class="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</span>


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt><nowiki># on veut faire un copie de tous les fichiers</nowiki></tt><br /><tt>for i in * ; do</tt><br /><tt><nowiki># sous le nom *.bak</nowiki></tt><br /><tt> cp $i $i.bak</tt><br /><tt>done</tt></blockquote>
Que fait le script ? Les commentaires ne l’expliquent pas ! Ce sont de mauvais commentaires.


, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.)
En revanche :
 
<span class="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</span>
 
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 ===
=== 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 <tt>essai01</tt><nowiki>: </nowiki>
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 <span class="code">essai01</span> :  


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>echo le paramètre \$1 est \"$1\"</tt><br /><tt>echo le paramètre \$2 est \"$2\"</tt><br /><tt>echo le paramètre \$3 est \"$3\"</tt></blockquote>
<span class="code">#!/bin/sh
echo le paramètre \$1 est \"$1\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$3 est \"$3\"</span>


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


<blockquote><tt>$ '''./essai01 paramètre un'''</tt><br /><tt>le paramètre $1 est "paramètre"</tt><br /><tt>le paramètre $2 est "un"</tt><br /><tt>le paramètre $3 est ""</tt><br /><tt>$ _</tt></blockquote>
<span class="code">$ ./essai01 paramètre un
le paramètre $1 est "paramètre"
le paramètre $2 est "un"
le paramètre $3 est ""
$ _</span>


Donc, les variables <tt>$1</tt>, <tt>$2</tt> ... <tt>$9</tt> 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.
Donc, les variables <span class="code">$1</span>, <span class="code">$2</span><span class="code">$9</span> 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 retour chariot quand c’est possible et le point virgule.


Vous avez sans doute remarqué que j'ai utilisé les caractères : <tt>\$</tt> à la place de <tt>$</tt> ainsi que <tt>\"</tt> à la place de <tt>"</tt> dans le script. Pour quelle raison ? La raison est simple, si l'on tape : <tt>echo "essai"</tt> on obtient : <tt>essai</tt>, si l'on veut obtenir <tt>"essai"</tt> il faut dire à <tt>echo</tt> que le caractère <tt>"</tt> 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 <tt>"</tt> en tapant <tt>\"</tt>. En "échappant" le caractère <tt>\</tt> (par <tt>\\</tt>) on obtient le caractère <tt>\</tt> sans signification particulière. On peut dire que le caractère <tt>\</tt> devant un autre lui fait perdre sa signification particulière s'il en a une, ne fait rien si le caractère qui suit <tt>\</tt> n'en a pas.
Vous avez sans doute remarqué que j’ai utilisé les caractères : <span class="code">\$</span> à la place de <span class="code">$</span> ainsi que <span class="code">\"</span> à la place de <span class="code">"</span> dans le script. Pour quelle raison ? C’est simple : si l’on tape <span class="code">echo "essai"</span> on obtient : <span class="code">essai</span>, si l’on veut obtenir <span class="code">"essai"</span> il faut dire à <span class="code">echo</span> (en réalité, au shell qui va lancer <span class="code">echo</span>) que le caractère <span class="code">"</span> 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 <span class="code">"</span> en tapant <span class="code">\"</span>. En « échappant » le caractère <span class="code">\</span> (par <span class="code">\\</span>) on obtient le caractère <span class="code">\</span> sans signification particulière. On peut dire que le caractère <span class="code">\</span> devant un autre lui fait perdre sa signification particulière s’il en a une, ne fait rien si le caractère qui suit <span class="code">\</span> n’en a pas.


Maintenant, essayons de taper :
Maintenant, essayons de taper :


<blockquote><tt>$ '''./essai01 *'''</tt><br /><tt>le paramètre $1 est "Mail"</tt><br /><tt>le paramètre $2 est "essai01"</tt><br /><tt>le paramètre $3 est "nsmail"</tt><br /><tt>$ _</tt></blockquote>
<span class="code">$ ./essai01 *
le paramètre $1 est "Mail"
le paramètre $2 est "essai01"
le paramètre $3 est "nsmail"
$ _</span>


(Le résultat doit être sensiblement différent sur votre machine). 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 :
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 ! Le ''shell'' qui substitue aux paramètres des valeurs étendues par les caractères spéciaux : * (toute suite de caractères) ? (un caractère quelconque), [dze] (l'un des caractères d, z ou e), [d-z] (les caractères de 'd' à 'z')


<blockquote><tt>$ '''./essai01 \*'''</tt><br /><tt>le paramètre $1 est "*"</tt><br /><tt>le paramètre $2 est ""</tt><br /><tt>le paramètre $3 est ""</tt><br /><tt>$ _</tt></blockquote>
Autre exemple :


Hé oui, on a "échappé" le caractère <tt><nowiki>*</nowiki></tt> donc il a perdu sa signification particulière : il est redevenu un simple <tt><nowiki>*</nowiki></tt>.
<span class="code">$ ./essai01 \*
le paramètre $1 est "*"
le paramètre $2 est ""
le paramètre $3 est ""
$ _</span>


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 <tt>essai02</tt> :
Hé oui, on a « échappé » le caractère <span class="code">*</span> donc il a perdu sa signification particulière : il est redevenu un simple <span class="code">*</span>.


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>echo le paramètre 1 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 2 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 2 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 4 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 5 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 6 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 7 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 8 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 9 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 10 est \"$1\"</tt><br /><tt>shift</tt><br /><tt>echo le paramètre 11 est \"$1\"</tt></blockquote>
C’est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser les accolades : <span class="code">${10}</span> ou <span class="code">${23}</span> par exemple. On peut aussi utiliser la commande <span class="code">shift</span> ; à titre d’exemple voici le script <span class="code">essai02</span> :
 
<span class="code">#!/bin/sh
echo le paramètre 1 est \"$1\"
shift
echo le paramètre 2 est \"$1\"
shift
echo le paramètre 3 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\"</span>


Si vous tapez :
Si vous tapez :


<blockquote><tt>$ '''./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13'''</tt><br /><tt>le paramètre 1 est "1"</tt><br /><tt>le paramètre 2 est "2"</tt><br /><tt>le paramètre 2 est "3"</tt><br /><tt>le paramètre 4 est "4"</tt><br /><tt>le paramètre 5 est "5"</tt><br /><tt>le paramètre 6 est "6"</tt><br /><tt>le paramètre 7 est "7"</tt><br /><tt>le paramètre 8 est "8"</tt><br /><tt>le paramètre 9 est "9"</tt><br /><tt>le paramètre 10 est "10"</tt><br /><tt>le paramètre 11 est "11"</tt><br /><tt>$ _</tt></blockquote>
<span class="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 3 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"
$ _</span>


A chaque appel de <tt>shift</tt> 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 <tt>shift</tt> (ou le sauvegarder dans une variable).
À chaque appel de <span class="code">shift</span> 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 <span class="code">shift</span> : vous devez donc vous en servir avant d’appeler <span class="code">shift</span> (ou le sauvegarder dans une variable).


=== Les variables ===
=== Les variables ===


Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : <tt>$1</tt>, <tt>$2</tt> etc. Ce sont les substitutions des variables <tt>1</tt>, <tt>2</tt>, 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 <tt>machin</tt>) et son contenu (noté dans cas <tt>$machin</tt>). Vous connaissez peut-être la variable <tt>PATH</tt> (attention, le shell différencie les majuscules des minuscules) qui contient la liste des répertoires (séparés par des <tt>":"</tt>) dans lesquels il doit rechercher les programmes. Si dans un script vous tapez :
Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : <span class="code">$1</span>, <span class="code">$2</span> etc. Ce sont les substitutions des variables <span class="code">1</span>, <span class="code">2</span>, 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 <span class="code">machin</span>) et son contenu (noté dans notre cas <span class="code">$machin</span>). Vous connaissez peut-être la variable <span class="code">PATH</span> (attention, le shell différencie les majuscules des minuscules) qui contient la liste des répertoires (séparés par des <span class="code">":"</span>) dans lesquels il doit rechercher les programmes. Si dans un script vous tapez :


<blockquote><tt>1:#!/bin/sh</tt><br /><tt>2:PATH=/bin # PATH contient /bin</tt><br /><tt>3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin</tt><br /><tt>4:PATH=/bin # PATH contient /bin</tt><br /><tt>5:PATH=$PATH:/usr/bin # PATH contient /bin:/usr/bin</tt></blockquote>
<span class="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</span>


(Les numéros ne sont là que pour repérer les lignes, il ne faut pas les taper) <br />La ligne 3 est très certainement une erreur, à gauche du signe <tt>"="</tt> il faut une variable (donc un nom sans <tt>$</tt>) mais à droite de ce même signe il faut une valeur, et la valeur que l'on a mis est <tt>"PATH:/usr/bin"</tt> : il n'y a aucune substitution à faire. <br />Par contre la ligne 5 est certainement correcte : à droite du <tt>"="</tt> on a mis <tt>"$PATH:/usr/bin"</tt>, la valeur de <tt>$PATH</tt> étant <tt>"/bin"</tt>, la valeur après substitution par le shell de <tt>"$PATH:/usr/bin"</tt> est <tt>"/bin:/usr/bin"</tt>. Donc, à la fin de la ligne 5, la valeur de la variable <tt>PATH</tt> est <tt>"/bin:/usr/bin"</tt>.
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 <span class="code">=</span> il faut une variable (donc un nom sans <span class="code">$</span>) mais à droite de ce même signe il faut une valeur, et la valeur que l'on a mise est <span class="code">PATH:/usr/bin</span> : il n'y a aucune substitution à faire. Par contre la ligne 5 est certainement correcte : à droite du <span class="code">=</span> on a mis <span class="code">"$PATH:/usr/bin"</span>, la valeur de <span class="code">$PATH</span> étant <span class="code">/bin</span>, la valeur après substitution par le shell de <span class="code">"$PATH:/usr/bin"</span> est <span class="code">/bin:/usr/bin</span>. Donc, à la fin de la ligne 5, la valeur de la variable <span class="code">PATH</span> est <span class="code">"/bin:/usr/bin"</span>.


Attention : il ne doit y avoir aucun espace de part et d'autre du signe <tt>"="</tt>.
Attention : les caractères spéciaux (espaces, <span class="code">*</span>, ...) ne peuvent pas apparaitre à gauche du signe <span class="code">=</span>, et doivent être précédés d'un <span class="code">\</span> ou mis entre guillemets à droite du signe <span class="code">=</span>.


<u>Résumons</u> : <tt>MACHIN</tt> 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 <tt>$MACHIN</tt> est le contenu de la variable <tt>MACHIN</tt> que l'on utilise lorsque l'on a besoin du contenu de cette variable.
<u>Résumons</u> : <span class="code">MACHIN</span> 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 <span class="code">$MACHIN</span> est le contenu de la variable <span class="code">MACHIN</span> que l'on utilise lorsque l'on a besoin du contenu de cette variable.


=== Variables particulières ===
=== Variables particulières ===
Ligne 97 : Ligne 186 :
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 <tt><nowiki>*</nowiki></tt> (dont le contenu est <tt>$*</tt>) 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 <span class="code">@</span> (dont le contenu est <span class="code">$@</span>) 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). On l'utilise en général avec des guillemets : <span class="code">"$@"</span>.
* la variable <tt><nowiki>#</nowiki></tt> contient le nombre de paramètres (<tt>$#</tt>) qui ont été passés au programme.
* la variable <span class="code">#</span> contient le nombre de paramètres (<span class="code">$#</span>) qui ont été passés au programme.
* la variable <tt></tt> (zéro) contient le nom du script (ou du lien si le script a été appelé depuis un lien).
* la variable <span class="code">0</span> (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 <tt>bash</tt>.
Il y en a d'autres, moins utilisées : allez voir la man page de <span class="code">bash</span>.


=== 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 : <tt>read nom_variable</tt>. 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.
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 : <span class="code">read nom_variable</span>. 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.


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>echo -n "Entrez votre prénom : "</tt><br /><tt>read prenom</tt><br /><tt>echo -n "Entrez votre nom de login : "</tt><br /><tt>read nomlogin</tt><br /><tt>echo "Le nom de login de $prenom est $nomlogin."</tt></blockquote>
<span class="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."</span>


Ce script se déroule ainsi :
Ce script se déroule ainsi :


<blockquote><tt>'''./essai02bis<br /> '''Entrez votre prénom : '''Marc<br /> '''Entrez votre nom de login : '''spoutnik<br /> '''Le nom de login de Marc est spoutnik.</tt></blockquote>
<span class="code">$ ./essai02bis
Entrez votre prénom : Marc
Entrez votre nom de login : spoutnik
Le nom de login de Marc est spoutnik.</span>


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".
L'option <span class="code">-s</span> de <span class="code">read</span> permet de masquer la saisie. Par exemple <span class="code">read -s pass</span>.


=== 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 : <tt>$(( un calcul ))</tt> ou <tt>$[ un calcul ]</tt>. Exemple, le script essai03 :
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 : <span class="code">$(( un calcul ))</span>. Exemple, le script essai03 :


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>echo 2+3*5 = $((2+3*5))</tt><br /><tt>MACHIN=12</tt><br /><tt>echo MACHIN*4 = $[$MACHIN*4]</tt></blockquote>
<span class="code">#!/bin/sh
echo 2+3*5 = $((2+3*5))
MACHIN=12
echo MACHIN*4 = $(($MACHIN*4))</span>


Affichera :
Affichera :


<blockquote><tt>$ '''sh essai03'''</tt><br /><tt>2+3*5 = 17</tt><br /><tt>MACHIN*4 = 48</tt></blockquote>
<span class="code">$ ./essai03
2+3*5 = 17
MACHIN*4 = 48</span>
 
Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est <span class="code">"**"</span> (ie : 2 puissance 5 s'écrit : <span class="code">2**5</span>). On peut utiliser des parenthèses pour modifier l'ordre des calculs.
 
== Guillemets, échappement, ... ==
 
Nous avons vu plus haut qu'écrire par exemple <span class="code">*</span> ou
<span class="code">\*</span> étaient différents. En fait, la plupart des caractères
autres que des chiffres ou des lettres ont une signification
particulière pour le shell.


Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est <tt>"**"</tt> (ie : 2 puissance 5 s'écrit : <tt>2**5</tt>). On peut utiliser des parenthèses pour modifier l'ordre des calculs. <br />
Pour exécuter une commande, le shell commence par regarder tous ces
caractères particuliers. Il fait en particulier les choses suivantes
(dans cet ordre) :
 
* Expansion des variables (par exemple, dans <span class="code">echo $var</span>, <span class="code">$var</span> sera remplacé par sa valeur),
 
* Découpage de la ligne de commande selon les blancs (par exemple, la commande <span class="code">echo un deux trois</span> sera découpée en une commande <span class="code">echo</span> avec les trois arguments <span class="code">un</span>, <span class="code">deux</span> et <span class="code">trois</span>, alors que <span class="code">echo "un deux trois"</span>, avec des guillemets en plus, sera découpée en une commande <span class="code">echo</span> avec un seul argument <span class="code">un deux trois</span>).
 
* Expansion des jokers (<span class="code">*</span> remplacé par la liste des fichiers non cachés dans le répertoire courrant, <span class="code">?</span>, ...)
 
Pour éviter ce traitement spécial pour les caractères spéciaux, on a plusieurs solutions :
 
* Précéder le caractère par un anti-slash :
<span class="code">echo \*       # affiche une étoile
echo \$        # affiche un dollar
cat fichier\ avec\ espaces  # affiche le contenu du fichier "fichier avec espaces"</span>
 
* Mettre toute une chaine entre guillemets simples (apostrophes). Dans ce cas, seul le guillemet reste un caractère spécial (pour terminer la chaine) :
<span class="code">echo '*'      # affiche une étoile
echo '$'      # affiche un dollar
cat 'fichier avec espaces' # affiche le contenu du fichier "fichier avec espaces"</span>
 
* Mettre toute une chaine entre guillemets doubles. Dans ce cas, les caractères <span class="code">$</span> et <span class="code">\</span> restent actifs :
<span class="code">echo "*"      # affiche une étoile
var=toto
echo "$var"      # affiche "toto"
f=fichier
e=espaces
cat "$f avec $e"  # affiche le contenu du fichier "fichier avec espaces"
echo "\$f avec $e" # affiche "$f avec espaces"</span>
 
Il reste un autre type de guillemets, le guillemet inversé (backquote en anglais) : <span class="code">`</span>. Quand le shell rencontre une chaine du type <span class="code">`commande`</span>, il exécute <span class="code">commande</span>, et remplace la chaine par le résultat de la commande. Par exemple :
<span class="code">ancienne_date=`date`
sleep 3
echo "La date est maintenant :"
date
echo "Mais tout à l'heure, il était : $ancienne_date"</span>
 
Note : à la place de <span class="code">`commande`</span>, on peut aussi écrire
<span class="code">$(commande)</span>, qui est plutôt plus lisible, et plus
portable.


== Les instructions de contrôle de scripts ==
== Les instructions de contrôle de scripts ==
Ligne 133 : Ligne 286 :
=== 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 : <tt>if then else fi</tt>. Sa syntaxe est la suivante (la partie <tt>else...</tt> en italique est optionnelle) :
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 : <span class="code">if then else fi</span>. Sa syntaxe est la suivante (la partie <span class="code">else...</span> est optionnelle) :


<blockquote><tt>'''if''' <test> ; '''then'''</tt><br /><tt> <instruction 1></tt><br /><tt> <instruction 2></tt><br /><tt> ...</tt><br /><tt> <instruction n></tt><br />''<tt>else</tt>''<br />''<tt> <instruction n+1></tt>''<br />''<tt> ...</tt>''<br />''<tt> <instruction n+p></tt>''<br />'''<tt>fi</tt>'''</blockquote>
<span class="code">if <test> ;  
then
  <instruction 1>
  <instruction 2>
  ...
  <instruction n>
else
  <instruction n+1>
  ...
  <instruction n+p>
fi</span>


Il faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable <tt>?</tt> dont la valeur est, rappelons le : "<tt>$?</tt>". <br />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 (numéro non nul) quand il s'en est produit une).
Il faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable <span class="code">?</span> dont la valeur est, rappelons le : "<span class="code">$?</span>". 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 : <tt>false</tt> et <tt>true</tt>. <tt>true</tt> renvoie toujours 0 et <tt>false</tt> renvoie toujours 1. Sachant cela, voyons ce que fait le programme suivant :
Il existe deux programmes particuliers : <span class="code">false</span> et <span class="code">true</span>. <span class="code">true</span> renvoie toujours 0 et <span class="code">false</span> renvoie toujours 1. Sachant cela, voyons ce que fait le programme suivant :


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>if true ; then</tt><br /><tt> echo Le premier test est VRAI($?)</tt><br /><tt>else</tt><br /><tt> echo Le premier test est FAUX($?)</tt><br /><tt>fi</tt>
<span class="code">#!/bin/sh
if true ;  
then  
  echo Le premier test est VRAI($?)
else
  echo Le premier test est FAUX($?)
fi


<tt>if false ; then</tt><br /><tt> echo Le second test est VRAI($?)</tt><br /><tt>else</tt><br /><tt> echo Le second test est FAUX($?)</tt><br /><tt>fi</tt>
if false ;
 
then
</blockquote>
  echo Le second test est VRAI($?)
else
  echo Le second test est FAUX($?)
fi</span>


Affichera :
Affichera :


<blockquote><tt>$ ./test</tt><br /><tt>Le premier test est VRAI(0)</tt><br /><tt>Le second test est FAUX(1)</tt><br /><tt>$ _</tt></blockquote>
<span class="code">$ ./test
Le premier test est VRAI(0)
Le second test est FAUX(1)
$ _</span>


On peut donc conclure que l'instruction <tt>if ... then ... else ... fi</tt>, 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 <tt>then</tt> et le <tt>else</tt> (ou le <tt>fi</tt> en l'absence de <tt>else</tt>) 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 <tt>else</tt> et le <tt>fi</tt> si ce bloc existe.
On peut donc conclure que l'instruction <span class="code">if ... then ... else ... fi</span>, 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 <span class="code">then</span> et le <span class="code">else</span> (ou le <span class="code">fi</span> en l'absence de <span class="code">else</span>) 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 <span class="code">else</span> et le <span class="code">fi</span> 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 332 :
=== Les tests ===
=== Les tests ===


Un test, nous l'avons vu, n'est rien de plus qu'une commande standard. Une des commandes standard est '<tt>test</tt>', sa syntaxe est un peu complexe, je vais la décrire avec des exemples.
Un test, nous l'avons vu, n'est rien de plus qu'une commande standard.
Une des commandes standard est <span class="code">test</span>, sa syntaxe est un
peu complexe, nous allons la décrire avec des exemples.


* si l'on veut tester l'existence d'un répertoire <tt><machin></tt>, on tapera : <tt>test -d <machin></tt> ('d' comme '''''<font size="+1">d</font>'''irectory'')
* si l'on veut tester l'existence d'un répertoire <span class="code"><machin></span>, on tapera : <span class="code">test -d <machin></span> ('d' comme '''''d'''irectory'')
* si l'on veut tester l'existence d'un fichier <tt><machin></tt>, on tapera : <tt>test -f <machin></tt> ('f' comme '''''<font size="+1">f</font>'''ile'')
* si l'on veut tester l'existence d'un fichier <span class="code"><machin></span>, on tapera : <span class="code">test -f <machin></span> ('f' comme '''''f'''ile'')
* si l'on veut tester l'existence d'un fichier ou répertoire <tt><machin></tt>, on tapera : <tt>test -e <machin></tt> ('e' comme '''''<font size="+1">e</font>'''xist'')
* si l'on veut tester l'existence d'un fichier ou répertoire <span class="code"><machin></span>, on tapera : <span class="code">test -e <machin></span> ('e' comme '''''e'''xist'')


Pour plus d'information faites : <tt>man test</tt>.
Pour plus d'information faites : <span class="code">man test</span>.


On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à <tt>-o</tt> ('o' comme '''''<font size="+1">o</font>'''r''), 'et' correspond à <tt>-a</tt> ('a' comme '''''<font size="+1">a</font>'''nd'') (à nouveau allez voir la man page), exemple :
On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à <span class="code">-o</span> ('o' comme '''''<font size="+1">o</font>'''r''), 'et' correspond à <span class="code">-a</span> ('a' comme '''''<font size="+1">a</font>'''nd'') (à nouveau allez voir la man page), exemple :


<blockquote><tt>test -x /bin/sh -a -d /etc</tt></blockquote>
<span class="code">test -x /bin/sh -a -d /etc</span>


Cette instruction teste l'existence de l'éxécutable <tt>/bin/sh</tt> (<tt>-x /bin/sh</tt>) et (<tt>-a</tt>) la présence d'un répertoire <tt>/etc</tt> (<tt>-d /etc</tt>).
Cette instruction teste l'existence de l'éxécutable <span class="code">/bin/sh</span> (<span class="code">-x /bin/sh</span>) et (<span class="code">-a</span>) la présence d'un répertoire <span class="code">/etc</span> (<span class="code">-d /etc</span>).


On peut remplacer la commande <tt>test <un test></tt> par <tt>[ <un test> ]</tt> qui est plus lisible, exemple :
On peut remplacer la commande <span class="code">test <un test></span> par <span class="code">[ <un test> ]</span> qui est plus lisible, exemple :


<blockquote><tt>if [ -x /bin/sh ] ; then</tt> # ('x' comme ''e'''<font size="+1">x</font>'''ecutable'') <br /><tt> echo /bin/sh est exécutable. C\'est bien.</tt><br /><tt>else</tt><br /><tt> echo /bin/sh n\'est pas exécutable.</tt><br /><tt> echo Votre système n\'est pas normal.</tt><br /><tt>fi</tt></blockquote>
<span class="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</span>


Toujours avec les crochets de <tt>test</tt>, si vous n'avez qu'une seule chose à faire en fonction du résultat d'un test, alors vous pouvez utiliser la syntaxe suivante :<br />
Toujours avec les crochets de <span class="code">test</span>, si vous n'avez qu'une seule chose à faire en fonction du résultat d'un test, alors vous pouvez utiliser la syntaxe suivante :


<blockquote><tt>[ -x /bin/sh ] && echo /bin/sh est exécutable.</tt><br /> ou encore :<br /><tt>[ -x /bin/sh ] || echo /bin/sh n\'est pas exécutable.</tt><br /> </blockquote>
<span class="code">[ -x /bin/sh ] && echo /bin/sh est exécutable.</span><br /> ou encore :<br /><span class="code">[ -x /bin/sh ] || echo "/bin/sh n'est pas exécutable."</span>


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 essient.
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.


<br />Mais il n'y a pas que la commande <tt>test</tt> qui peut être employée. Par exemple, la commande <tt>grep</tt> renvoie 0 quand la recherche a réussi et 1 quand la recherche a échoué. <br />Par exemple :
Mais il n'y a pas que la commande <span class="code">test</span> qui peut être employée. Par exemple, la commande <span class="code">grep</span> renvoie 0 quand la recherche a réussi et 1 quand la recherche a échoué. <br />Par exemple :


<blockquote><tt>if grep -E "^frederic:" /etc/passwd > /dev/null ; then</tt><br /><tt> echo L\'utilisateur frederic existe.</tt><br /><tt>else</tt><br /><tt> echo L'utilisateur frederic n\'existe pas.</tt><br /><tt>fi</tt></blockquote>
<span class="code">if grep -E "^frederic:" /etc/passwd > /dev/null ; then
  echo L'utilisateur frederic existe.
else
  echo L'utilisateur frederic n'existe pas.
fi</span>


Cette série d'instruction teste la présence de l'utilisateur <tt>frederic</tt> dans le fichier <tt>/etc/passwd</tt>. Vous remarquerez que l'on a fait suivre la commande <tt>grep</tt> d'une redirection vers <tt>/dev/null</tt> 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" ;-).
Cette série d'instruction teste la présence de l'utilisateur <span class="code">frederic</span> dans le fichier <span class="code">/etc/passwd</span>. Vous remarquerez que l'on a fait suivre la commande <span class="code">grep</span> d'une redirection vers <span class="code">/dev/null</span> 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 <tt>case ... esac</tt> 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.
L'instruction <span class="code">case ... esac</span> 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.


<blockquote><tt><nowiki>#!/bin/sh</nowiki><br /> # pose la question et récupère la réponse<br /> echo "Le contenu du répertoire courant va être affiché."<br /> echo -n "Souhaitez-vous afficher aussi les fichiers cachés (oui/non) : "<br /> read reponse<br /> # agit selon la réponse<br /> case $reponse in<br /> oui)<br /> clear<br /> ls -a;;<br /> non)<br /> ls;;<br /> *)<br /> echo "Erreur, vous deviez répondre par oui ou par non.";;<br /> esac</tt></blockquote>
<span class="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</span>


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 <tt>case ... esac</tt>, faites bien attention de ne pas oublier les doubles points-virgules terminant les instructions de chacun des cas envisagés.
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 <span class="code">case ... esac</span>, 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 399 :
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


<blockquote><tt>for <variable> in <liste de valeurs pour la variable> ; do</tt><br /><tt> <instruction 1></tt><br /><tt> ...</tt><br /><tt> <instruction n></tt><br /><tt>done</tt></blockquote>
<span class="code">for <variable> in <liste de valeurs pour la variable> ; do
  <instruction 1>
  ...
  <instruction n>
done</span>


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 :


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt><nowiki># I prend chacune des valeurs possibles correspondant</nowiki></tt><br /><tt><nowiki># au motif : *.tar.gz</nowiki></tt><br /><tt>for I in *.tar.gz ; do</tt><br /><tt> # tous les fichiers $I sont renommés $I.old</tt><br /><tt> echo "$I -> $I.old"</tt><br /><tt> mv $I $I.old</tt><br /><tt><nowiki># on finit notre boucle</nowiki></tt><br /><tt>done</tt></blockquote>
<span class="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</span>


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 :


<blockquote><tt> 1:#!/bin/sh</tt><br /><tt> 2:for REP in `find -type d` ; do</tt><br /><tt> 3: for FICH in $REP/*.tar.gz ; do</tt><br /><tt> 4: if [ -f $FICH ] ; then</tt><br /><tt> 5: mv $FICH $FICH.old</tt><br /><tt> 6: else</tt><br /><tt> 7: echo On ne renomme pas $FICH car ce n\'est pas un répertoire</tt><br /><tt> 8: fi</tt><br /><tt> 9: done</tt><br /><tt>10:done</tt></blockquote>  
<span class="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 fichier"
8:     fi
9:   done
10:done</span>  


<u>Explications</u> : dans le premier 'for', on a précisé comme liste : <tt>`find -type d`</tt> (attention au sens des apostrophes, sur un clavier azerty français, on obtient ce symbole en appuyant sur <tt>ALTGR+é</tt>, 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). <br />Donc, dans le cas qui nous intéresse, la liste est le résultat de la commande <tt>find -type d</tt>, 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>Explications</u> : dans le premier 'for', on a précisé comme liste : <span class="code">`find -type d`</span> (attention au sens des apostrophes, sur un clavier azerty français, on obtient ce symbole en appuyant sur <span class="code">ALTGR+è</span>, 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 <span class="code">find -type d</span>, 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 <tt>for</tt> d'autres langage (le pascal, le C ou le basic par exemple).
<u>note</u> : ce script ne marchera pas si des noms de répertoires contiennent des espaces : dans ce cas, <span class="code">`find -type d`</span> sera exécuté, remplacé par son résultat, et ensuite découpé selon les blancs.
 
<u>Remarque</u> : ce n'est pas le même fonctionnement que la boucle <span class="code">for</span> 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 440 :
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 :


<blockquote><tt>while <un test> ; do</tt><br /><tt> <instruction 1></tt><br /><tt> ...</tt><br /><tt> <instruction n></tt><br /><tt>done</tt></blockquote>
<span class="code">while <un test> ; do
  <instruction 1>
  ...
  <instruction n>
done</span>


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 :


<blockquote><tt>i=0</tt><br /><tt>while [ $i -lt 100 ] ; do</tt><br /><tt> echo $i</tt><br /><tt> i=$[$i+1]</tt><br /><tt>done</tt></blockquote>  
<span class="code">i=0
while [ $i -lt 100 ] ; do
  echo $i
  i=$[$i+1]
done</span>  


<u>Remarque</u> : <tt>-lt</tt> signifie "'''<tt><font color="#ff0000">l</font></tt>'''esser '''<tt><font color="#ff0000">t</font></tt>'''han" ou "plus petit que" (et <tt>-gt</tt> signifie "plus grand", ou "'''<tt><font color="#ff0000">g</font></tt>'''reater '''<tt><font color="#ff0000">t</font></tt>'''han").
<u>Remarque</u> : <span class="code">-lt</span> signifie "'''l'''esser '''t'''han" ou "plus petit que" (et <span class="code">-gt</span> signifie "plus grand", ou "'''g'''reater '''t'''han").


Ici, on va afficher le contenu de <tt>i</tt> et lui ajouter 1 tant que <tt>i</tt> sera (<tt>-lt</tt>) plus petit que 100. Remarquez que 100 ne s'affiche pas, car <tt>-lt</tt> est "plus petit", mais pas "plus petit ou égal" (dans ce cas, utilisez <tt>-le</tt> et <tt>-ge</tt> pour "plus grand ou égal").
Ici, on va afficher le contenu de <span class="code">i</span> et lui ajouter 1 tant que <span class="code">i</span> sera (<span class="code">-lt</span>) plus petit que 100. Remarquez que 100 ne s'affiche pas, car <span class="code">-lt</span> est "plus petit", mais pas "plus petit ou égal" (dans ce cas, utilisez <span class="code">-le</span> et <span class="code">-ge</span> 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 <tt>function</tt> qui permet de réutiliser une portion de script (on dit : une "'''fonction'''"). Voyons un exemple :
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 des fonctions qui permettent de réutiliser
une portion de script. Voyons un exemple :


<blockquote><tt><nowiki>#!/bin/sh</nowiki></tt><br /><tt>function addpath ()</tt><br /><tt>{</tt><br /><tt> if echo $PATH | grep -v $1 >/dev/null; then</tt><br /><tt> PATH=$PATH:$1;</tt><br /><tt> fi;</tt><br /><tt> PATH=`echo $PATH|sed s/::/:/g`</tt><br /><tt>}</tt>
<span class="code">#!/bin/sh
addpath ()
{
  if echo $PATH | grep -v $1 >/dev/null; then
      PATH=$PATH:$1;
  fi;
  PATH=`echo $PATH|sed s/::/:/g`
}


<tt>addpath /opt/apps/bin</tt><br /><tt>addpath /opt/office52/program</tt><br /><tt>addpath /opt/gnome/bin</tt>
addpath /opt/apps/bin
addpath /opt/office52/program
addpath /opt/gnome/bin
export PATH</span>


</blockquote>


Au début, nous avons défini une fonction nommée <tt>addpath</tt> dont le but est d'ajouter le premier argument (<tt>$1</tt>) de la fonction <tt>addpath</tt> à la varaible <tt>PATH</tt> si ce premier argument n'est pas déjà présent (<tt>grep -v $1</tt>) dans la variable <tt>PATH</tt>, ainsi que supprimer les chemins vides (<tt>sed s/::/:/g</tt>) de PATH. <br />Ensuite, nous exécutons cette fonction pour trois arguments : /opt/apps/bin, /opt/office52/bin et /opt/gnome/bin.
Au début, nous avons défini une fonction nommée <span class="code">addpath</span> dont le but est d'ajouter le premier argument (<span class="code">$1</span>) de la fonction <span class="code">addpath</span> à la variable <span class="code">PATH</span> si ce premier argument n'est pas déjà présent (<span class="code">grep -v $1</span>) dans la variable <span class="code">PATH</span>, ainsi que supprimer les chemins vides (<span class="code">sed s/::/:/g</span>) 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 <tt>export</tt> mais cela constitue une utilisation avancée du shell, nous n'irons pas plus loin dans cet article.
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 <span class="code">export</span> mais cela constitue une utilisation avancée du shell, nous n'irons pas plus loin dans cet article.


<u>Remarque</u> : le mot <tt>function</tt> peut être omis, mais son utilisation facilite la lecture du script.
<u>Remarque</u> : on peut aussi utiliser le mot clé
<span class="code">function</span>, mais cette syntaxe n'est pas supportée par tous
les shells, et n'est donc pas portable :
<span class="code">function addpath ()
{
# ...
}</span>


=== 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 (<tt>man bash</tt>).
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 (<span class="code">man bash</span>).


À vous de jouer !
À vous de jouer !
=Autres ressources=
* [http://jipe.homelinux.org/trucs_bash.html Pour aller plus loin...] (des trucs et astuces pour bash)
* [http://abs.traduc.org/abs-4.2.01-fr/ Aller à 100%] (Guide avancé d'écriture des scripts Bash)
* [http://download.gna.org/unix-initiation/ Introduction à Unix et à la programmation Shell] (cours sur Unix de l'école d'ingénieur de l'ESME SUDRIA, publié sur [http://gna.org])
* [http://cours.enise.fr/info/unix/ Tout ce que vous avez toujours voulu savoir sur Unix] (cours de l'Énise)
<br/>
<br/>
'''<b>[[Dev-index|@ Retour à la rubrique Développement]]</b>'''
<br/>


<div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Frédéric Bonnaud le 29/08/2000.</div>
<div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Frédéric Bonnaud le 29/08/2000.</div>
{{Copy|29/08/2000|[[Utilisateur:Fred|Fred]], Marc|CC-BY-NC-SA}}

Dernière version du 1 décembre 2023 à 18:39


Programmation de scripts : Une introduction

Comment écrire de petits scripts permettant d’automatiser la réalisation de taches répétitives.


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 : vi, 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’exécute ? C’est simple il suffit de taper :

[user@becane user]$ sh bonjour_monde Bonjour, Monde ! Un premier script est né. [user@becane user]$ _

C’est pas cool, vous auriez préféré 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é, en revanche 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

  1. pour i parcourant tous les fichiers,

for i in ./* ; do

  1. copier le fichier vers .bak
cp "$i" "$i.bak"
  1. fin pour

done

Que fait le script ? Les commentaires ne l’expliquent pas ! Ce sont de mauvais commentaires.

En revanche :

#!/bin/sh

  1. on veut faire un copie de tous les fichiers

for i in ./* ; do

  1. 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 tape :

$ ./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 retour chariot quand c’est possible et 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 ? C’est simple : si l’on tape echo "essai" on obtient : essai, si l’on veut obtenir "essai" il faut dire à echo (en réalité, au shell qui va lancer 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 ! Le shell qui substitue aux paramètres des valeurs étendues par les caractères spéciaux : * (toute suite de caractères) ? (un caractère 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 les accolades : ${10} ou ${23} par exemple. On peut aussi 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 3 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 3 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" $ _

À 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 notre 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 mise 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 : les caractères spéciaux (espaces, *, ...) ne peuvent pas apparaitre à gauche du signe =, et doivent être précédés d'un \ ou mis entre guillemets à droite 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). On l'utilise en général avec des guillemets : "$@".
  • 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".

L'option -s de read permet de masquer la saisie. Par exemple read -s pass.

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 )). Exemple, le script essai03 :

#!/bin/sh echo 2+3*5 = $((2+3*5)) MACHIN=12 echo MACHIN*4 = $(($MACHIN*4))

Affichera :

$ ./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.

Guillemets, échappement, ...

Nous avons vu plus haut qu'écrire par exemple * ou \* étaient différents. En fait, la plupart des caractères autres que des chiffres ou des lettres ont une signification particulière pour le shell.

Pour exécuter une commande, le shell commence par regarder tous ces caractères particuliers. Il fait en particulier les choses suivantes (dans cet ordre) :

  • Expansion des variables (par exemple, dans echo $var, $var sera remplacé par sa valeur),
  • Découpage de la ligne de commande selon les blancs (par exemple, la commande echo un deux trois sera découpée en une commande echo avec les trois arguments un, deux et trois, alors que echo "un deux trois", avec des guillemets en plus, sera découpée en une commande echo avec un seul argument un deux trois).
  • Expansion des jokers (* remplacé par la liste des fichiers non cachés dans le répertoire courrant, ?, ...)

Pour éviter ce traitement spécial pour les caractères spéciaux, on a plusieurs solutions :

  • Précéder le caractère par un anti-slash :

echo \* # affiche une étoile echo \$ # affiche un dollar cat fichier\ avec\ espaces # affiche le contenu du fichier "fichier avec espaces"

  • Mettre toute une chaine entre guillemets simples (apostrophes). Dans ce cas, seul le guillemet reste un caractère spécial (pour terminer la chaine) :

echo '*' # affiche une étoile echo '$' # affiche un dollar cat 'fichier avec espaces' # affiche le contenu du fichier "fichier avec espaces"

  • Mettre toute une chaine entre guillemets doubles. Dans ce cas, les caractères $ et \ restent actifs :

echo "*" # affiche une étoile var=toto echo "$var" # affiche "toto" f=fichier e=espaces cat "$f avec $e" # affiche le contenu du fichier "fichier avec espaces" echo "\$f avec $e" # affiche "$f avec espaces"

Il reste un autre type de guillemets, le guillemet inversé (backquote en anglais) : `. Quand le shell rencontre une chaine du type `commande`, il exécute commande, et remplace la chaine par le résultat de la commande. Par exemple : ancienne_date=`date` sleep 3 echo "La date est maintenant :" date echo "Mais tout à l'heure, il était : $ancienne_date"

Note : à la place de `commande`, on peut aussi écrire $(commande), qui est plutôt plus lisible, et plus portable.

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, nous allons 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

  1. ('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

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

  1. 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

  1. 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

  1. x prend chacune des valeurs possibles correspondant
  2. 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 fichier" 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.

note : ce script ne marchera pas si des noms de répertoires contiennent des espaces : dans ce cas, `find -type d` sera exécuté, remplacé par son résultat, et ensuite découpé selon les blancs.

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 "lesser than" ou "plus petit que" (et -gt signifie "plus grand", ou "greater than").

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 des fonctions qui permettent de réutiliser une portion de script. Voyons un exemple :

#!/bin/sh 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 variable 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 : on peut aussi utiliser le mot clé function, mais cette syntaxe n'est pas supportée par tous les shells, et n'est donc pas portable : function addpath () {

  1. ...

}

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 !


Autres ressources




@ Retour à la rubrique Développement

Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Frédéric Bonnaud le 29/08/2000.

Copyright

© 29/08/2000 Fred, Marc

Creative Commons License
Creative Commons Attribution iconCreative Commons Share Alike iconCreative Commons Noncommercial
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/