« Script shell » : différence entre les versions

De Lea Linux
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
(balises code)
Ligne 3 : Ligne 3 :
= Programmation de scripts : Une introduction =
= Programmation de scripts : Une introduction =


<div class="leatitre">Programmation de Script: Une introduction</div><div class="leadesc">Comment écrire de petits scripts permettant d’automatiser la réalisation de taches répétitives.</div>
Comment écrire de petits scripts permettant d’automatiser la réalisation de taches répétitives.
----
----


Ligne 10 : Ligne 10 :
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.
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.
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 ==
Ligne 16 : Ligne 16 :
=== 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 : <code>/bin/sh</code> (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 : [[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 :


<code multi>#!/bin/sh</code>
<span class="code">#!/bin/sh</span>


<cadre type=note>
<cadre type=note>
'''Note :''' <code>"#!"</code> se prononce « ''she bang'' », soit « chi-bang ».
'''Note :''' <span class="code">"#!"</span> se prononce « ''she bang'' », soit « chi-bang ».
</cadre>
</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 :
É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 :


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


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


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


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


<code>[user@becane user]$ ./bonjour_monde
<span class="code">[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
Bonjour, Monde !
Un premier script est né.
Un premier script est né.
[user@becane user]$ _</code>
[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 :


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


<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>.
<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 ===
Ligne 60 : Ligne 60 :
Exemple :
Exemple :


<code>#!/bin/sh
<span class="code">#!/bin/sh
# pour i parcourant tous les fichiers,
# pour i parcourant tous les fichiers,
for i in ./* ; do
for i in ./* ; do
Ligne 66 : Ligne 66 :
  cp "$i" "$i.bak"
  cp "$i" "$i.bak"
# fin pour
# fin pour
done</code>
done</span>


Que fait le script ? Les commentaires ne l’expliquent pas ! Ce sont de mauvais commentaires.
Que fait le script ? Les commentaires ne l’expliquent pas ! Ce sont de mauvais commentaires.
Ligne 72 : Ligne 72 :
En revanche :
En revanche :


<code>#!/bin/sh
<span class="code">#!/bin/sh
# on veut faire un copie de tous les fichiers
# on veut faire un copie de tous les fichiers
for i in ./* ; do
for i in ./* ; do
# sous le nom *.bak
# sous le nom *.bak
  cp "$i" "$i.bak"
  cp "$i" "$i.bak"
done</code>
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).
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 83 : Ligne 83 :
=== 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 <code>essai01</code> :  
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> :  


<code>#!/bin/sh
<span class="code">#!/bin/sh
echo le paramètre \$1 est \"$1\"
echo le paramètre \$1 est \"$1\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$3 est \"$3\"</code>
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 tape :
Que fait-il ? Il affiche les uns après les autres les trois premiers paramètres du script, donc si l’on tape :


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


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 retour chariot quand c’est possible et 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 : <code>\$</code> à la place de <code>$</code> ainsi que <code>\"</code> à la place de <code>"</code> dans le script. Pour quelle raison ? C’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> (en réalité, au shell qui va lancer <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.
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 :


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


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')…
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')…
Ligne 114 : Ligne 114 :
Autre exemple :
Autre exemple :


<code>$ ./essai01 \*
<span class="code">$ ./essai01 \*
le paramètre $1 est "*"
le paramètre $1 est "*"
le paramètre $2 est ""
le paramètre $2 est ""
le paramètre $3 est ""
le paramètre $3 est ""
$ _</code>
$ _</span>


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


C’est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser les accolades : <code>${10}</code> ou <code>${23}</code> par exemple. On peut aussi utiliser la commande <code>shift</code> ; à titre d’exemple voici le script <code>essai02</code> :
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> :


<code>#!/bin/sh
<span class="code">#!/bin/sh
echo le paramètre 1 est \"$1\"
echo le paramètre 1 est \"$1\"
shift
shift
Ligne 145 : Ligne 145 :
echo le paramètre 10 est \"$1\"
echo le paramètre 10 est \"$1\"
shift
shift
echo le paramètre 11 est \"$1\"</code>
echo le paramètre 11 est \"$1\"</span>


Si vous tapez :
Si vous tapez :


<code>$ ./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13
<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 1 est "1"
le paramètre 2 est "2"
le paramètre 2 est "2"
Ligne 161 : Ligne 161 :
le paramètre 10 est "10"
le paramètre 10 est "10"
le paramètre 11 est "11"
le paramètre 11 est "11"
$ _</code>
$ _</span>


À 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.
À 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 <code>shift</code> : vous devez donc vous en servir avant d’appeler <code>shift</code> (ou le sauvegarder dans une variable).
É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 : <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 notre 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 :
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 :


<code>1:#!/bin/sh
<span class="code">1:#!/bin/sh
2:PATH=/bin # PATH contient /bin
2:PATH=/bin # PATH contient /bin
3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin
3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin
4:PATH=/bin # PATH contient /bin
4:PATH=/bin # PATH contient /bin
5:PATH="$PATH:/usr/bin" # PATH contient /bin:/usr/bin</code>
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. 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 mise 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>.
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 : les caractères spéciaux (espaces, <code>*</code>, ...) ne peuvent pas apparaitre à gauche du signe <code>=</code>, et doivent être précédés d'un <code>\</code> ou mis entre guillemets à droite du signe <code>=</code>.
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> : <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.
<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 186 : 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 <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). On l'utilise en général avec des guillemets : <code>"$@"</code>.
* 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 <code>#</code> contient le nombre de paramètres (<code>$#</code>) 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 <code>0</code> (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 <code>bash</code>.
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 : <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.
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.


<code>#!/bin/sh
<span class="code">#!/bin/sh
echo -n "Entrez votre prénom : "
echo -n "Entrez votre prénom : "
read prenom
read prenom
echo -n "Entrez votre nom de login : "
echo -n "Entrez votre nom de login : "
read nomlogin
read nomlogin
echo "Le nom de login de $prenom est $nomlogin."</code>
echo "Le nom de login de $prenom est $nomlogin."</span>


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


<code>$ ./essai02bis
<span class="code">$ ./essai02bis
Entrez votre prénom : Marc
Entrez votre prénom : Marc
Entrez votre nom de login : spoutnik
Entrez votre nom de login : spoutnik
Le nom de login de Marc est spoutnik.</code>
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 <code>-s</code> de <code>read</code> permet de masquer la saisie. Par exemple <code>read -s pass</code>.
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 : <code>$(( un calcul ))</code>. 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 :


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


Affichera :
Affichera :


<code>$ ./essai03
<span class="code">$ ./essai03
2+3*5 = 17
2+3*5 = 17
MACHIN*4 = 48</code>
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 <code>"**"</code> (ie : 2 puissance 5 s'écrit : <code>2**5</code>). On peut utiliser des parenthèses pour modifier l'ordre des calculs.
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, ... ==
== Guillemets, échappement, ... ==


Nous avons vu plus haut qu'écrire par exemple <code>*</code> ou
Nous avons vu plus haut qu'écrire par exemple <span class="code">*</span> ou
<code>\*</code> étaient différents. En fait, la plupart des caractères
<span class="code">\*</span> étaient différents. En fait, la plupart des caractères
autres que des chiffres ou des lettres ont une signification
autres que des chiffres ou des lettres ont une signification
particulière pour le shell.
particulière pour le shell.
Ligne 242 : Ligne 242 :
(dans cet ordre) :
(dans cet ordre) :


* Expansion des variables (par exemple, dans <code>echo $var</code>, <code>$var</code> sera remplacé par sa valeur),
* 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 <code>echo un deux trois</code> sera découpée en une commande <code>echo</code> avec les trois arguments <code>un</code>, <code>deux</code> et <code>trois</code>, alors que <code>echo "un deux trois"</code>, avec des guillemets en plus, sera découpée en une commande <code>echo</code> avec un seul argument <code>un deux trois</code>).
* 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 (<code>*</code> remplacé par la liste des fichiers non cachés dans le répertoire courrant, <code>?</code>, ...)
* 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 :
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 :  
* Précéder le caractère par un anti-slash :  
<code>echo \*        # affiche une étoile
<span class="code">echo \*        # affiche une étoile
echo \$        # affiche un dollar
echo \$        # affiche un dollar
cat fichier\ avec\ espaces  # affiche le contenu du fichier "fichier avec espaces"</code>
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) :
* Mettre toute une chaine entre guillemets simples (apostrophes). Dans ce cas, seul le guillemet reste un caractère spécial (pour terminer la chaine) :
<code>echo '*'      # affiche une étoile
<span class="code">echo '*'      # affiche une étoile
echo '$'      # affiche un dollar
echo '$'      # affiche un dollar
cat 'fichier avec espaces'  # affiche le contenu du fichier "fichier avec espaces"</code>
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 <code>$</code> et <code>\</code> restent actifs :
* Mettre toute une chaine entre guillemets doubles. Dans ce cas, les caractères <span class="code">$</span> et <span class="code">\</span> restent actifs :
<code>echo "*"      # affiche une étoile
<span class="code">echo "*"      # affiche une étoile
var=toto
var=toto
echo "$var"      # affiche "toto"
echo "$var"      # affiche "toto"
Ligne 267 : Ligne 267 :
e=espaces
e=espaces
cat "$f avec $e"  # affiche le contenu du fichier "fichier avec espaces"
cat "$f avec $e"  # affiche le contenu du fichier "fichier avec espaces"
echo "\$f avec $e" # affiche "$f avec espaces"</code>
echo "\$f avec $e" # affiche "$f avec espaces"</span>


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


Note : à la place de <code>`commande`</code>, on peut aussi écrire
Note : à la place de <span class="code">`commande`</span>, on peut aussi écrire
<code>$(commande)</code>, qui est plutôt plus lisible, et plus
<span class="code">$(commande)</span>, qui est plutôt plus lisible, et plus
portable.
portable.


Ligne 286 : 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 : <code>if then else fi</code>. Sa syntaxe est la suivante (la partie <code>else...</code> 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) :


<code>if <test> ;  
<span class="code">if <test> ;  
then
then
   <instruction 1>
   <instruction 1>
Ligne 298 : Ligne 298 :
   ...
   ...
   <instruction n+p>
   <instruction n+p>
fi</code>
fi</span>


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


<code>#!/bin/sh
<span class="code">#!/bin/sh
if true ;  
if true ;  
then  
then  
Ligne 317 : Ligne 317 :
else
else
   echo Le second test est FAUX($?)
   echo Le second test est FAUX($?)
fi</code>
fi</span>


Affichera :
Affichera :


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


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.
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 333 : Ligne 333 :


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


* 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 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 <code><machin></code>, on tapera : <code>test -f <machin></code> ('f' comme '''''f'''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 <code><machin></code>, on tapera : <code>test -e <machin></code> ('e' comme '''''e'''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 : <code>man test</code>.
Pour plus d'information faites : <span class="code">man test</span>.


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


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


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>).
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 <code>test <un test></code> par <code>[ <un test> ]</code> 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 :


<code>if [ -x /bin/sh ] ; then
<span class="code">if [ -x /bin/sh ] ; then
# ('x' comme "e_x_ecutable")
# ('x' comme "e_x_ecutable")
   echo "/bin/sh est exécutable. C'est bien."
   echo "/bin/sh est exécutable. C'est bien."
Ligne 356 : Ligne 356 :
   echo "/bin/sh n'est pas exécutable."
   echo "/bin/sh n'est pas exécutable."
   echo "Votre système n'est pas normal."
   echo "Votre système n'est pas normal."
fi</code>
fi</span>


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


<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>
<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 escient.
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 :
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 :


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


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" ;-).
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 <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.
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.


<code>#!/bin/sh
<span class="code">#!/bin/sh
# pose la question et récupère la réponse
# pose la question et récupère la réponse
echo "Le contenu du répertoire courant va être affiché."
echo "Le contenu du répertoire courant va être affiché."
Ligne 391 : Ligne 391 :
     ls;;
     ls;;
   *) echo "Erreur, vous deviez répondre par oui ou par non.";;
   *) echo "Erreur, vous deviez répondre par oui ou par non.";;
esac</code>
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 <code>case ... esac</code>, 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 399 : 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


<code>for <variable> in <liste de valeurs pour la variable> ; do
<span class="code">for <variable> in <liste de valeurs pour la variable> ; do
   <instruction 1>
   <instruction 1>
   ...
   ...
   <instruction n>
   <instruction n>
done</code>
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 :


<code>#!/bin/sh
<span class="code">#!/bin/sh
# x prend chacune des valeurs possibles correspondant
# x prend chacune des valeurs possibles correspondant
# au motif : *.tar.gz
# au motif : *.tar.gz
Ligne 415 : Ligne 415 :
   mv "$x" "$x.old"
   mv "$x" "$x.old"
   # on finit notre boucle
   # on finit notre boucle
done</code>
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 :


<code> 1:#!/bin/sh
<span class="code"> 1:#!/bin/sh
2:for REP in `find -type d` ; do
2:for REP in `find -type d` ; do
3:  for FICH in "$REP/*.tar.gz" ; do
3:  for FICH in "$REP/*.tar.gz" ; do
Ligne 428 : Ligne 428 :
8:      fi
8:      fi
9:  done
9:  done
10:done</code>  
10:done</span>  


<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>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>note</u> : ce script ne marchera pas si des noms de répertoires contiennent des espaces : dans ce cas, <code>`find -type d`</code> sera exécuté, remplacé par son résultat, et ensuite découpé selon les blancs.
<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 <code>for</code> d'autres langage (le pascal, le C ou le basic par exemple).
<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 440 : 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 :


<code>while <un test> ; do
<span class="code">while <un test> ; do
   <instruction 1>
   <instruction 1>
   ...
   ...
   <instruction n>
   <instruction n>
done</code>
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 :


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


<u>Remarque</u> : <code>-lt</code> signifie "'''l'''esser '''t'''han" ou "plus petit que" (et <code>-gt</code> signifie "plus grand", ou "'''g'''reater '''t'''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 <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").
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 ===
Ligne 465 : Ligne 465 :
une portion de script. Voyons un exemple :
une portion de script. Voyons un exemple :


<code>#!/bin/sh
<span class="code">#!/bin/sh
addpath ()
addpath ()
{
{
Ligne 477 : Ligne 477 :
addpath /opt/office52/program
addpath /opt/office52/program
addpath /opt/gnome/bin
addpath /opt/gnome/bin
export PATH</code>
export PATH</span>




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 variable <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.
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 <code>export</code> 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> : on peut aussi utiliser le mot clé
<u>Remarque</u> : on peut aussi utiliser le mot clé
<code>function</code>, mais cette syntaxe n'est pas supportée par tous
<span class="code">function</span>, mais cette syntaxe n'est pas supportée par tous
les shells, et n'est donc pas portable :
les shells, et n'est donc pas portable :
<code>function addpath ()
<span class="code">function addpath ()
{
{
# ...
# ...
}</code>
}</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 (<code>man bash</code>).
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 !

Version du 5 juin 2012 à 13:05


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/