<chapter id="chap_02"><title>Ecrire et corriger des scripts</title>

<abstract>
<para>A la fin de ce chapitre vous serez capable de&nbsp;:</para>
<para><itemizedlist>
<listitem><para>Ecrire un script simple</para></listitem>
<listitem><para>Définir le type de Shell qui doit exécuter le script</para></listitem>
<listitem><para>Ajouter des commentaires</para></listitem>
<listitem><para>Changer les permissions du script</para></listitem>
<listitem><para>Exécuter et débugger un script</para></listitem>
</itemizedlist></para>
</abstract>

<sect1 id="sect_02_01"><title>Créer et lancer un script</title>
<sect2 id="sect_02_01_01"><title>Écrire et nommer</title>

<para>

Un script Shell est une séquence de commandes dont vous avez un usage 
répété. Cette séquence est en principe exécutée en entrant le nom du 
script sur la ligne de commande. Alternativement, vous pouvez utiliser 
des scripts pour automatiser des tâches via l'outil cron. Un autre usage 
des scripts est celui fait par la procédure de démarrage et d'arrêt 
d'UNIX où les opérations des services et démons sont définies dans des 
scripts <quote>init</quote>.

</para>

<para>Pour créer<indexterm><primary>scripts</primary><secondary>creation</secondary></indexterm> un script Shell, ouvrez un nouveau fichier avec l'éditeur.  N'importe quel éditeur fera l'affaire&nbsp;: <command>vim</command>, <command>emacs</command>, <command>gedit</command>, <command>dtpad</command> et cetera sont tous valides.  Vous pouvez songer à utiliser un éditeur sophistiqué comme <command>vim</command> ou <command>emacs</command>, parce qu'ils peuvent être configurés pour reconnaître la syntaxe Shell et Bash et donc peuvent être d'une grande aide en évitant ces erreurs que les débutants font, tel que oublier un crochet ou un point-virgule.</para>
<tip><title>Le vidéo-inverse dans vim</title>
<para>Pour activer le vidéo-inverse dans <command>vim</command>, passer la commande</para>
<cmdsynopsis><command>:set syntax enable</command></cmdsynopsis>
<para>Vous pouvez ajouter ce paramètre à votre fichier <filename>.vimrc</filename> pour rendre permanent cette configuration.</para>
</tip>
<para>Entrez des commandes UNIX dans ce nouveau fichier, comme vous le feriez sur la ligne de commande.   Ainsi que nous l'avons vu dans le chapitre précédent (voir <xref linkend="sect_01_03" />), les commandes peuvent être des fonctions Shell, des commandes intégrées, des commandes UNIX et le nom d'un autre script.</para>
<para>Donnez à votre script un nom significatif<indexterm><primary>scripts</primary><secondary>naming</secondary></indexterm> qui donne une idée de ce qu'il fait.  Assurez vous que ce nom ne soit pas en conflit avec une commande existante.  Afin d'éviter des confusions, les noms de scripts souvent finissent par <filename>.sh</filename>&nbsp;; mais même dans ce cas, il peut y avoir un autre script dans votre système qui porte le même nom.  Vérifier avec <command>which</command>, <command>whereis</command> et les autres commandes qui renvoyent des informations sur les programmes et les fichiers&nbsp;:</para>
<cmdsynopsis><command>which <option>-a</option> <filename>script_name</filename></command></cmdsynopsis>
<cmdsynopsis><command>whereis <filename>script_name</filename></command></cmdsynopsis>
<cmdsynopsis><command>locate <filename>script_name</filename></command></cmdsynopsis>
</sect2>
<sect2 id="sect_02_01_02"><title>script1.sh</title>
<para>Dans cet exemple<indexterm><primary>scripts</primary><secondary>example</secondary></indexterm> nous employons l'intégrée <command>echo</command> pour informer l'utilisateur sur ce qui va se dérouler, avant que la tâche qui crééra le résultat s'exécute.  Il est fortement recommandé d'informer les utilisateurs sur ce que fait le script, de façon à éviter qu'ils deviennent anxieux <emphasis>parce que le script ne fait rien</emphasis>.  Nous reviendrons sur le sujet de la notification aux utilisateurs au <xref linkend="chap_08" />.</para>

<figure><title>script1.sh</title>
<mediaobject>
<imageobject>
<imagedata fileref="&images;script1.sh.eps" format="EPS"></imagedata></imageobject><imageobject>
<imagedata fileref="&images;script1.sh.png" format="PNG"></imagedata>
</imageobject>
<textobject>
<phrase>Exemple de script utilisant <quote>echo hello</quote>, 
<quote>echo hello $USER</quote> et 
<quote>VARIABLE=value</quote>.</phrase>
</textobject>
</mediaobject>
</figure>


<para>Ecrivez ce script.  Ce peut être une bonne idée de créer un répertoire <filename>~/scripts</filename> pour ranger vos scripts.  Ajoutez le répertoire au contenu de la variable <varname>PATH</varname> variable<indexterm><primary>variables</primary><secondary>PATH</secondary></indexterm>&nbsp;:</para>
<cmdsynopsis><command>export <varname>PATH</varname>="<varname>$PATH</varname>:<filename>~/scripts</filename>"</command></cmdsynopsis>
<para>Si vous êtes tout nouveau avec Bash, utilisez un éditeur de texte qui emploie différentes couleurs pour les différentes constructions syntaxiques.  Le code de couleur est une fonction de <command>vim</command>, <command>gvim</command>, <command>(x)emacs</command>, <command>kwrite</command> et de beaucoup d'autres éditeurs. Se référer à la documentation de votre éditeur.</para>
<note><title>Des invites différentes</title><para>L'invite varie au long de ce guide selon l'humeur de l'auteur.  Ce qui ressemble plus à la vie réelle que l'invite classique <emphasis>$</emphasis>.  La seule convention que nous avons gardé est que l'invite de <emphasis>root</emphasis> finit par #.</para></note>
</sect2>
<sect2 id="sect_02_01_03"><title>Exécuter le script</title>
<para>Le script doit avoir les permissions<indexterm><primary>scripts</primary><secondary>execution</secondary></indexterm> d'exécution pour le propriétaire afin d'être exécutable.  Quand vous définissez des permissions<indexterm><primary>scripts</primary><secondary>permissions</secondary></indexterm>, contrôlez que vous avez obtenu les permissions voulues.  Une fois fait, le script peut être lancé comme toute autre commande&nbsp;:</para>
<screen>
<prompt>willy:~/scripts&gt;</prompt> <command>chmod <option>u+x</option> <filename>script1.sh</filename></command>

<prompt>willy:~/scripts&gt;</prompt> <command>ls <option>-l</option> <filename>script1.sh</filename></command>
-rwxrw-r--    1 willy   willy           456 Dec 24 17:11 script1.sh

<prompt>willy:~&gt;</prompt> <command>script1.sh</command>
Le script démarre.
Salut, willy&nbsp;!

Je vais afficher une liste des utilisateurs connectés&nbsp;:

  3:38pm  up 18 days,  5:37,  4 users,  load average: 0.12, 0.22, 0.15
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
root     tty2     -                Sat 2pm  4:25m  0.24s  0.05s  -bash
willy    :0       -                Sat 2pm   ?     0.00s   ?     -
willy    pts/3    -                Sat 2pm  3:33m 36.39s 36.39s  BitchX willy ir
willy    pts/2    -                Sat 2pm  3:33m  0.13s  0.06s  /usr/bin/screen

Je définis 2 variables, maintenant.
Ceci est une chaîne&nbsp;: noir
Et ceci est un nombre&nbsp;: 9

Je vous rends la main, maintenant.

<prompt>willy:~/scripts&gt;</prompt> <command>echo <varname>$COLOUR</varname></command>

<prompt>willy:~/scripts&gt;</prompt> <command>echo <varname>$VALUE</varname></command>

<prompt>willy:~/scripts&gt;</prompt>
</screen>

<para>C'est la façon la plus courante d'exécuter un script.  C'est préférable d'exécuter le script comme ça dans un sous-Shell.  Les variables, fonctions et alias créés dans le sous-Shell sont seulement connus dans la session Bash de ce sous-Shell.  Quand le sous-Shell finit et que le parent reprend le contrôle, tout est réinitialisé et les changements faits dans l'environnement du Shell par le script sont oubliés.</para>
<para>Si vous ne mettez pas le répertoire de <filename>scripts</filename> dans votre  <varname>PATH</varname>, et si <filename>.</filename> (le répertoire courant) n'est pas dans le <varname>PATH</varname> non plus, vous pouvez lancer le script comme ça&nbsp;:</para>

<cmdsynopsis><command>./script_name.sh</command></cmdsynopsis>

<para>Un script peut aussi être explicitement exécuté par un Shell particulier, mais généralement on ne fait ça que pour obtenir un comportement spécial, comme vérifier que le script tourne avec un autre Shell ou afficher une trace pour debugger.</para>

<cmdsynopsis><command>rbash <filename>script_name.sh</filename></command></cmdsynopsis>
<cmdsynopsis><command>sh <filename>script_name.sh</filename></command></cmdsynopsis>
<cmdsynopsis><command>bash <option>-x</option> <filename>script_name.sh</filename></command></cmdsynopsis>

<para>Le Shell spécifié démarrera en tant que sous-Shell de votre Shell actif et exécutera le script.  On le fait quand on veut que le script démarre avec des options ou des conditions spécifiques qui ne sont pas indiquées dans le script.</para>
<para>Si vous ne voulez pas démarrer un nouveau Shell mais exécuter le script dans le Shell courant, vous faites <emphasis>source<indexterm><primary>built-ins</primary><secondary>source</secondary></indexterm></emphasis>&nbsp;:</para>

<cmdsynopsis><command>source <filename>script_name.sh</filename></command></cmdsynopsis>

<tip><title>source = .</title><para>L'intégrée Bash <command>source</command> est un synonyme de la commande Bourne shell <command>.</command> (dot).</para></tip>

<para>Le script n'a pas besoin de permission d'exécution dans ce cas.  Les commandes sont exécutées dans l'environnement du Shell actif, par conséquent tout changement restera tel quel quand le script aura terminé&nbsp;:</para>

<screen>
<prompt>willy:~/scripts&gt;</prompt> <command>source <filename>script1.sh</filename></command>
--output ommitted--

<prompt>willy:~/scripts&gt;</prompt> <command>echo <varname>$VALUE</varname></command>
9

<prompt>willy:~/scripts&gt;</prompt>
</screen>
</sect2>

</sect1>
<sect1 id="sect_02_02"><title>Les bases du script</title>
<sect2 id="sect_02_02_01"><title>Quel Shell exécutera le script&nbsp;?</title>
<para>Quand un script s'exécute dans un sous-Shell<indexterm><primary>scripts</primary><secondary>executing shell</secondary></indexterm>, vous devriez définir quel Shell doit exécuter ce script.  Le type de Shell pour lequel vous avez écrit le script peut ne pas être celui par défaut de votre système, alors les commandes peuvent ne pas être interprétées par un Shell inadéquat.</para>
<para>La première ligne du script définit le Shell à lancer.  Les 2 premiers caractères de la première ligne devraient être <emphasis>#!</emphasis>, puis suit le chemin vers le Shell qui doit interpréter les commandes qui suivent.  Les lignes blanches sont aussi prises en compte, donc ne commencez pas votre script par une ligne vide.</para>
<para>Dans ce guide, tous les scripts commenceront par la ligne</para>
<cmdsynopsis><command>#!/bin/bash</command></cmdsynopsis>
<para>Comme indiqué auparavant, ceci implique que le programme Bash doit se trouver dans <filename>/bin</filename>.</para>
</sect2>
<sect2 id="sect_02_02_02"><title>Ajout de commentaires</title>
<para>Vous devriez vous rappeler que vous ne serez peut-être pas la seule personne à lire votre code.  Beaucoup d'utilisateurs et d'administrateurs système lancent des scripts qui ont été écrits par d'autres.  Si ils veulent voir comment vous avez fait, les commentaires  <indexterm><primary>scripts</primary><secondary>comments</secondary></indexterm> sont utiles pour éclairer le lecteur.</para>
<para>Les commentaires<indexterm><primary>comments</primary><secondary>usage</secondary></indexterm> vous rendent aussi la vie plus facile.  Par exemple vous avez lu beaucoup de pages man pour obtenir de certaines commandes de votre script un résultat donné.   Vous ne vous souviendrez plus de ce qu'il fait après quelques semaines, à moins d'avoir commenté ce que vous avez fait, comment et pourquoi.</para>
<para>Prenez en exemple <filename>script1.sh</filename> et copiez le sur <filename>commented-script1.sh</filename>, que vous éditez de sorte que les commentaires réflètent ce que le script fait.  Tout ce qui apparaît après le # sur une ligne est ignoré par le Shell et n'apparaît qu'à l'édition.</para>
<screen>
#!/bin/bash
# Ce script efface le terminal, affiche un message d'accueil et donne des informations
# sur les utilisateurs connectés.  Les 2 exemples de variables sont définis et affichés.

clear                           # efface le terminal

echo "Le script démarre."

echo "Salut, $USER&nbsp;!"              # le signe dollar est employé pour obtenir le contenu d'une variable
echo

echo "Je vais maintenant vous afficher une liste des utilisateurs connectés&nbsp;:"
echo                                                    
w                               # montre qui est connecté
echo                            # et ce qu'ils font

echo "Je définis 2 variables maintenant."
COLOUR="noir"                           # définit une variable locale Shell 
VALUE="9"                                       # définit une variable locale Shell 
echo "Ceci est une chaîne&nbsp;: $COLOUR"               # affiche le contenu de la variable 
echo "Et ceci est un nombre&nbsp;: $VALUE"              # affiche le contenu de la variable
echo

echo "Je vous redonne la main maintenant."
echo
</screen>
<para>Dans un script correct, les premières lignes sont habituellement des commentaires sur son but.  Puis chaque portion importante de code devrait être commentée autant que la clarté le demande.  Les scripts d'init Linux, par exemple, dans le répertoire <filename>init.d</filename> sont généralement bien documentés puisque ils doivent être lisibles et éditables par quiconque utilise Linux.</para>
</sect2>


</sect1>

<sect1 id="sect_02_03"><title>Débugger (NdT&nbsp;: corriger) les scripts Bash</title>
<sect2 id="sect_02_03_01"><title>Débugger le script globalement</title>
<para>Quand les choses<indexterm><primary>scripts</primary><secondary>debugging</secondary></indexterm> ne vont pas comme attendues, vous devez déterminer qu'est-ce qui provoque cette situation.  Bash fournit divers moyens pour débugger<indexterm><primary>debugging</primary></indexterm>.  La plus commune est de lancer le sous-Shell avec l'option  <option>-x</option> ce qui fait s'exécuter le script en mode débug.  Une trace de chaque commande avec ses arguments est affichée sur la sortie standard après que la commande ait été interprétée mais avant son exécution.</para>
<para>Ceci est le script <filename>commented-script1.sh</filename> exécuté en mode débug<indexterm><primary>debugging</primary><secondary>on entire script</secondary></indexterm>.  Notez que les commentaires ne sont pas visibles dans la sortie du script.</para>
<screen>
<prompt>willy:~/scripts&gt;</prompt> <command>bash <option>-x</option> <filename>script1.sh</filename></command>
+ clear

+ echo 'Le script démarre.'
Le script démarre.
+ echo 'Salut, willy&nbsp;!'
Salut, willy&nbsp;!
+ echo

+ echo 'Je vais maintenant vous afficher une liste des utilisateurs connectés&nbsp;:'
Je vais maintenant vous afficher une liste des utilisateurs connectés&nbsp;:
+ echo

+ w
  4:50pm  up 18 days,  6:49,  4 users,  load average: 0.58, 0.62, 0.40
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
root     tty2     -                Sat 2pm  5:36m  0.24s  0.05s  -bash
willy    :0       -                Sat 2pm   ?     0.00s   ?     -
willy    pts/3    -                Sat 2pm 43:13  36.82s 36.82s  BitchX willy ir
willy    pts/2    -                Sat 2pm 43:13   0.13s  0.06s  /usr/bin/screen
+ echo

+ echo 'Je définis 2 variables maintenant.'
Je définis 2 variables maintenant.
+ COLOUR=noir
+ VALUE=9
+ echo 'Ceci est une chaîne&nbsp;: '
Ceci est une chaîne&nbsp;:
+ echo 'Et ceci est un nombre&nbsp;: '
Et ceci est un nombre&nbsp;:
+ echo

+ echo 'Je vous redonne la main maintenant.'
Je vous redonne la main maintenant.
+ echo
</screen>
<note><title>Fonctionnalités à venir du Bash</title>
<para>Il y a maintenant un débugger complet pour Bash, disponible sur <ulink url="http://bashdb.sourceforge.net">SourceForge</ulink>.  Cependant, cela nécessite une version modifiée de bash-2.05.  Cette fonctionnalité devrait être disponible dans la version bash-3.0.</para>
</note>

</sect2>
<sect2 id="sect_02_03_02"><title>Débugger qu'une partie du script</title>
<para>Avec l'intégrée <command>set</command> vous pouvez exécuter en mode normal ces portions<indexterm><primary>debugging</primary><secondary>partial</secondary></indexterm> de code où vous êtes sûr qu'il n'y a pas d'erreurs, et afficher les informations de débuggage seulement pour les portions douteuses.  Admettons que nous ne soyons pas sûr de ce que fait la commande <command>w</command> dans l'exemple <filename>commented-script1.sh</filename>, alors nous pouvons l'entourer dans le script comme ceci&nbsp;:</para>

<screen>
set -x                  # active le mode débug
w
set +x                  # stoppe le mode débug
</screen>
<para>La sortie affiche alors ceci&nbsp;:</para>
<screen>
<prompt>willy: ~/scripts&gt;</prompt> <command>script1.sh</command>
Le script démarre.
Salut, willy&nbsp;!

Je vais maintenant vous afficher une liste des utilisateurs connectés&nbsp;:

+ w
  5:00pm  up 18 days,  7:00,  4 users,  load average: 0.79, 0.39, 0.33
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
root     tty2     -                Sat 2pm  5:47m  0.24s  0.05s  -bash
willy    :0       -                Sat 2pm   ?     0.00s   ?     -
willy    pts/3    -                Sat 2pm 54:02  36.88s 36.88s  BitchX willyke
willy    pts/2    -                Sat 2pm 54:02   0.13s  0.06s  /usr/bin/screen
+ set +x

Je définis 2 variables maintenant.
Ceci est une chaîne&nbsp;:
Et ceci est un nombre&nbsp;:

Je vous redonne la main maintenant.

<prompt>willy: ~/scripts&gt;</prompt>
</screen>
<para>On peut basculer du mode activé à désactivé autant de fois que l'on veut dans le script.</para>
<para>La table ci-dessous donne un aperçu d'autres options Bash utiles<indexterm><primary>debugging</primary><secondary>options</secondary></indexterm>&nbsp;:</para>
<table id="table_02_01" frame="all">
<title>Aperçu des options de débug</title>
<tgroup cols="3" align="left" colsep="1" rowsep="1">
<thead>
<row><entry>Syntaxe abrégée</entry><entry>Syntaxe longue</entry><entry>Effet</entry></row>
</thead>
<tbody>
<row><entry>set -f</entry><entry>set -o noglob</entry><entry>Désactive la génération de noms de fichiers à partir des métacaractères (globbing).</entry></row>
<row><entry>set -v</entry><entry>set -o verbose</entry><entry>Affiche les lignes fournies au Shell telles qu'elles ont été lues.</entry></row>
<row><entry>set -x</entry><entry>set -o xtrace</entry><entry>Affiche la trace des commandes avant leur exécution.</entry></row>
</tbody>
</tgroup>
</table>
<para>Le signe - est utilisé pour activer une option Shell et le + pour la désactiver.  Ne vous faites pas avoir&nbsp;!</para>
<para>Dans l'exemple qui suit, nous montrons l'usage de ces options depuis la ligne de commande&nbsp;:</para>
<screen>
<prompt>willy:~/scripts&gt;</prompt> <command>set <option>-v</option></command>

<prompt>willy:~/scripts&gt;</prompt> <command>ls</command>
ls 
commented-scripts.sh    script1.sh

<prompt>willy:~/scripts&gt;</prompt> <command>set <option>+v</option></command>
set +v

<prompt>willy:~/scripts&gt;</prompt> <command>ls <parameter>*</parameter></command>
commented-scripts.sh    script1.sh

<prompt>willy:~/scripts&gt;</prompt> <command>set <option>-f</option></command>

<prompt>willy:~/scripts&gt;</prompt> <command>ls <filename>*</filename></command>
ls: *: No such file or directory

<prompt>willy:~/scripts&gt;</prompt> <command>touch <filename>*</filename></command>

<prompt>willy:~/scripts&gt;</prompt> <command>ls</command>
*   commented-scripts.sh    script1.sh

<prompt>willy:~/scripts&gt;</prompt> <command>rm <filename>*</filename></command>

<prompt>willy:~/scripts&gt;</prompt> <command>ls</command>
commented-scripts.sh    script1.sh
</screen>
<para>De façon alternative, ces modes peuvent être indiqués dans le script lui-même, en ajoutant l'option voulue sur la première ligne de déclaration du Shell.  Les options peuvent être combinées, comme c'est généralement le cas pour les commandes UNIX&nbsp;:</para>
<cmdsynopsis><command>#!/bin/bash <option>-xv</option></command></cmdsynopsis>
<para>Une fois que vous avez localisé<indexterm><primary>debugging</primary><secondary>echo statements</secondary></indexterm> la partie douteuse, vous pouvez ajouter des instructions <command>echo</command> devant chaque commande douteuse, de sorte que vous verrez exactement où et pourquoi le résultat n'est pas satisfaisant.  Dans le script <filename>commented-script1.sh</filename>, ça pourrait être fait comme ça, toujours en supposant que l'affichage des utilisateurs nous cause des soucis&nbsp;:</para>
<screen>
echo "debug message&nbsp;: avant exécution de la commande w"; w
</screen>
<para>Dans des scripts plus élaborés <command>echo</command> peut être inséré pour faire afficher le contenu de variables à différentes étapes du script, afin de détecter les erreurs&nbsp;:</para>
<screen>
echo "Variable VARNAME a la valeur $VARNAME."
</screen>
</sect2>

</sect1>
<sect1 id="sect_02_05"><title>Résumé</title>
<para>Un script Shell est une série réutilisable de commandes saisies dans un fichier de texte exécutable.  Tout type d'éditeur de texte peut être utilisé pour écrire des scripts.</para>
<para>Un script commence par<emphasis>#!</emphasis> suivi par le chemin vers le Shell qui exécutera les commandes qui viennent après.  Les commentaires sont ajoutés au script pour vos propres besoins ultérieurs, et aussi pour le rendre compréhensible par les autres.  Mieux vaut avoir trop d'explications que pas assez.</para>
<para>Corriger un script peut être fait grâce aux options Shell de débug.  Ces options peuvent être utilisées sur une partie ou sur la totalité du script.  Ajouter des commandes <command>echo</command> à des endroits judicieusement choisis est aussi un bon moyen de traquer l'erreur.</para>

</sect1>
<sect1 id="sect_02_06"><title>Exercices</title>
<para>Cet exercice vous aidera à créer votre premier script.</para>
<orderedlist>
<listitem><para>Ecrire un script au moyen de votre éditeur favori.  Le script devrait afficher le chemin de votre répertoire utilisateur et le type de terminal que vous utilisez.  De plus il montrera tous les services lancés par le niveau d'exécution 3 de votre système. (tuyau&nbsp;: employez <varname>HOME</varname>, <varname>TERM</varname> et <command>ls <filename>/etc/rc3.d/S*</filename></command>)</para></listitem>
<listitem><para>Ajoutez des commentaires.</para></listitem>
<listitem><para>Ajoutez des informations à destination de l'utilisateur.</para></listitem>
<listitem><para>Changer les permissions de sorte que vous puissiez le lancer.</para></listitem>
<listitem><para>Exécuter le script en mode normal puis en mode débug.  Il doit s'exécuter sans erreurs.</para></listitem>
<listitem><para>Faites que le script fasse une erreur&nbsp;: voyez ce qui arrive si la première ligne n'est pas correcte ou si vous libellez mal une commande ou une variable - par exemple déclarez une variable par un nom en majuscule et référencez-la avec le nom en minuscule.  Voyez ce que les commentaires de débug affichent dans ce cas.</para>
</listitem>
</orderedlist>

</sect1>
</chapter>