<chapter id="chap_07">
<title>Les instructions de condition</title>
<abstract>
<para>Dans ce chapitre nous traiterons de l'emploi de conditions dans les scripts Bash.  Ceci comprend les sujets suivants&nbsp;:</para>
<para>
<itemizedlist>
<listitem><para>L'instruction <command>if</command></para></listitem>
<listitem><para>L'usage du statut d'exécution d'une commande</para></listitem>
<listitem><para>Comparer et tester les entrées et des fichiers</para></listitem>
<listitem><para>les blocs <command>if/then/else</command></para></listitem>
<listitem><para>Les blocs <command>if/then/elif/else</command></para></listitem>
<listitem><para>Utiliser et tester les paramètres positionnels</para></listitem>
<listitem><para>Les instructions <command>if</command> imbriquées</para></listitem>
<listitem><para>Les expressions booléennes</para></listitem>
<listitem><para>Utiliser les instructions <command>case</command></para></listitem>
</itemizedlist>
</para>
</abstract>
<sect1 id="sect_07_01"><title>Introduction de if</title>
<sect2 id="sect_07_01_01"><title>Généralité</title>
<para>A certains moments vous pouvez vouloir donner une alternative au traitement effectué par le script, en fonction de l'échec ou la réussite d'une commande.  Le bloc <command>if</command> permet de spécifier de telles conditions.</para>
<para>La syntaxe la plus compacte de la commande <command>if</command> est&nbsp;:</para>
<cmdsynopsis><command>if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi</command></cmdsynopsis>
<para>La liste <command>TEST-COMMAND</command> est exécutée, et si elle retourne le statut zéro, la liste <command>CONSEQUENT-COMMANDS</command> est exécutée.  Le statut retourné est le statut d'exécution de la dernière commande exécutée, ou zéro si aucune condition n'est vraie.</para>
<para>Le <command>TEST-COMMAND</command> souvent comprend des comparaisons de numériques ou de chaînes, mais cela peut être aussi toute commande qui retourne un statut à zéro quand elle s'est bien exécutée et d'autres valeurs en cas d'échec.  Une expression unaire est souvent utilisée pour examiner le statut d'un fichier.  Si l'argument <filename>FILE</filename> d'une de ces primitives est de la forme  <filename>/dev/fd/N</filename>, alors le descripteur de fichier <quote>N</quote> est contrôlé.  <filename>stdin</filename>, <filename>stdout</filename> et <filename>stderr</filename> et leur descripteur de fichier respectif peuvent aussi être employés dans les tests.</para>
<sect3 id="sect_07_01_01_01"><title>Expressions employées avec if</title>
<para>Le tableau ci-dessous contient un aperçu de ce qu'on appelle <quote>primitives</quote> et qui servent aux commandes <command>TEST-COMMAND</command> ou liste de commandes.  Ces primitives sont mises entre crochets pour indiquer le test d'une expression conditionnelle..</para>

<table id="tab_07_01" frame="all"><title>Expressions primitives</title><tgroup cols="2" align="left" colsep="1" rowsep="1"><thead>
<row><entry>Primitives</entry><entry>sens</entry></row>
</thead>
<tbody>
<row><entry>[ <option>-a</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe.</entry></row>
<row><entry>[ <option>-b</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un fichier de type bloc.</entry></row>
<row><entry>[ <option>-c</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un fichier de type caractère.</entry></row>
<row><entry>[ <option>-d</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est de type répertoire.</entry></row>
<row><entry>[ <option>-e</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe.</entry></row>
<row><entry>[ <option>-f</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un fichier régulier.</entry></row>
<row><entry>[ <option>-g</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et son bit SGID est positionné.</entry></row>
<row><entry>[ <option>-h</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un lien symbolique.</entry></row>
<row><entry>[ <option>-k</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et son bit collant est positionné.</entry></row>
<row><entry>[ <option>-p</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un tube nommé (FIFO).</entry></row>
<row><entry>[ <option>-r</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est lisible.</entry></row>
<row><entry>[ <option>-s</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et a une taille supérieure à zéro.</entry></row>
<row><entry>[ <option>-t</option> <filename>FD</filename> ]</entry><entry>Vrai si le descripteur de fichier <filename>FD</filename> est ouvert et qu'il se réfère à un terminal.</entry></row>
<row><entry>[ <option>-u</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FILE</filename> existe et son bit SUID (Set User ID) est positionné.</entry></row>
<row><entry>[ <option>-w</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est en écriture.</entry></row>
<row><entry>[ <option>-x</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est exécutable.</entry></row>
<row><entry>[ <option>-O</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et appartient à l'identifiant effectif de l'utilisateur.</entry></row>
<row><entry>[ <option>-G</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et appartient à l'identifiant effectif du groupe.</entry></row>
<row><entry>[ <option>-L</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un lien symbolique.</entry></row>
<row><entry>[ <option>-N</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et qu'il a été modifié depuis qu'il a été lu.</entry></row>
<row><entry>[ <option>-S</option> <filename>FICHIER</filename> ]</entry><entry>Vrai si <filename>FICHIER</filename> existe et est un connecteur réseau (socket).</entry></row>
<row><entry>[ <filename>FILE1</filename> <option>-nt</option> <filename>FILE2</filename> ]</entry><entry>Vrai si <filename>FILE1</filename> a été modifié plus récemment que <filename>FILE2</filename>, ou si <filename>FILE1</filename> existe et <filename>FILE2</filename> n'existe pas.</entry></row>
<row><entry>[ <filename>FILE1</filename> <option>-ot</option> <filename>FILE2</filename> ]</entry><entry>Vrai si <filename>FILE1</filename> est plus ancien que <filename>FILE2</filename>, ou si <filename>FILE2</filename> existe et <filename>FILE1</filename> non.</entry></row>
<row><entry>[ <filename>FILE1</filename> <option>-ef</option> <filename>FILE2</filename> ]</entry><entry>Vrai si <filename>FILE1</filename> et <filename>FILE2</filename> se réfère à la même entité et même numéro d'inode.</entry></row>
<row><entry>[ <option>-o</option> OPTIONNAME ]</entry><entry>Vrai si l'option Shell <quote>OPTIONNAME</quote> est activée.</entry></row>
<row><entry><option>[ -z</option> STRING ]</entry><entry>Vrai si la longueur de  <quote>STRING</quote> est zéro.</entry></row>
<row><entry><option>[ -n</option> STRING ] or [ STRING ]</entry><entry>Vrai si la longueur de <quote>STRING</quote> n'est pas zéro.</entry></row>
<row><entry>[ STRING1 == STRING2 ] </entry><entry>Vrai si les chaînes sont identiques.  <quote>=</quote> peut être employé au lieu de <quote>==</quote> pour une stricte compatibilité POSIX.</entry></row>
<row><entry>[ STRING1 != STRING2 ] </entry><entry>Vrai si les chaînes ne sont pas égales.</entry></row>
<row><entry>[ STRING1 &lt; STRING2 ] </entry><entry>Vrai si <quote>STRING1</quote> précède <quote>STRING2</quote> selon le lexique du paramétrage local.</entry></row>
<row><entry>[ STRING1 &gt; STRING2 ] </entry><entry>Vrai si <quote>STRING1</quote> suit <quote>STRING2</quote> selon le lexique du paramétrage local.</entry></row>
<row><entry>[ ARG1 OP ARG2 ]</entry><entry><quote>OP</quote> est  <option>-eq</option>, <option>-ne</option>, <option>-lt</option>, <option>-le</option>, <option>-gt</option> ou l'option <option>-ge</option>.  Ces opérateurs arithmétiques binaires<indexterm><primary>arithmetic expansion</primary><secondary>operators</secondary></indexterm> renvoient vrai si <quote>ARG1</quote> est égal, non égal, inférieur, supérieur ou égal, supérieur, ou supérieur ou égal à <quote>ARG2</quote>, respectivement.  <quote>ARG1</quote> et <quote>ARG2</quote> sont des entiers.</entry></row>
</tbody>
</tgroup>
</table>

<para>Les expressions peuvent être combinées avec les opérateurs suivants dans l'ordre de leur préséance&nbsp;:</para>

<table id="tab_07_02" frame="all"><title>Combinaison d'expressions</title><tgroup cols="2" align="left" colsep="1" rowsep="1"><thead>
<row><entry>Opération</entry><entry>Effet</entry></row>
</thead>
<tbody>
<row><entry>[ ! EXPR ]</entry><entry>Vrai si <command>EXPR</command> est faux.</entry></row>
<row><entry>[ ( EXPR ) ]</entry><entry>Renvoie la valeur de <command>EXPR</command>.  Ceci peut être utilisé pour modifier la préséance normale des opérateurs.</entry></row>
<row><entry>[ EXPR1 -a EXPR2 ]</entry><entry>Vrai si <command>EXPR1</command> et <command>EXPR2</command> sont vrai..</entry></row>
<row><entry>[ EXPR1 -o EXPR2 ]</entry><entry>Vrai si soit <command>EXPR1</command> ou <command>EXPR2</command> est vrai.</entry></row>
</tbody>
</tgroup>
</table>
<para>L'intégrée <command>[</command> (ou <command>test</command>) évalue les expressions conditionnelles en utilisant un jeux de règles basé sur le nombre d'arguments.  Plus d'information sur ce sujet se trouve dans la documentation Bash.  Tout comme le <command>if</command> est terminé par <command>fi</command>, le crochet ouvrant devrait être fermé après que les conditions aient été listées.</para>
</sect3>
<sect3 id="sect_07_01_01_02"><title>Les commandes qui suivent l'instruction then</title>
<para>La liste <command>CONSEQUENT-COMMANDS</command> qui suit l'instruction <command>then</command> peut être toute commande UNIX valide , tout programme exécutable, tout script Shell exécutable ou toute instruction Shell, à l'exception de <command>fi</command>.  Il est important de se rappeler que <command>then</command> et <command>fi</command> sont considérés comme des instructions à part entière dans le Shell.  De ce fait, quand elles sont fournies à la ligne de commande, elles sont séparées par un point-virgule.</para>
<para>Dans un script, les différentes parties de l'instruction <command>if</command> sont d'ordinaire bien séparées.  Ci-dessous, quelques exemples.</para>
</sect3>
<sect3 id="sect_07_01_01_03"><title>Contrôler des fichiers</title>
<para>Le premier exemple contrôle l'existence d'un fichier&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>cat <filename>msgcheck.sh</filename></command>
#!/bin/bash

echo "Ce script vérifie si le fichier des messages existe."
echo "Vérification..."
if [ -f /var/log/messages ]
  then
    echo "/var/log/messages existe."
fi
echo
echo "...fait."

<prompt>anny ~&gt;</prompt> <command>./msgcheck.sh</command>
Ce script vérifie si le fichier des messages existe.
Vérification...
/var/log/messages existe.

...fait.
</screen>
</sect3>
<sect3 id="sect_07_01_01_04"><title>Vérifier des options Shell</title>
<para>A ajouter dans vos fichier de configuration Bash&nbsp;:</para>
<screen>
# Ces lignes affichent un message si l'option noclobber option est positionnée&nbsp;:

if [ -o noclobber ]
  then
        echo "Vos fichiers sont protégés contre une réécriture accidentelle du fait d'une redirection."
fi

</screen>
<note><title>L'environnement</title>
<para>L'exemple ci-dessus fonctionnera si il est soumis à la ligne de commande&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>if <parameter>[ -o noclobber ]</parameter> ; then echo ; echo <parameter>"Vos fichiers sont protégés contre une réécriture."</parameter> ; echo ; fi</command>

Vos fichiers sont protégés contre une réécriture.

<prompt>anny ~&gt;</prompt>
</screen>
<para>Cependant, si vous employez les tests de conditions qui dépendent de l'environnement, vous pourriez obtenir des résultats variables alors que vous exécutez la même commande dans un script, parce que le script ouvrira un nouveau Shell, dans lequel les variables et options attendues pourraient ne pas être définies automatiquement.</para>
</note>
</sect3>
</sect2>
<sect2 id="sect_07_01_02"><title>Applications simples de if</title>
<sect3 id="sect_07_01_02_01"><title>Tester le statut d'exécution</title>
<para>La variable <varname>?</varname> stocke le statut d'exécution de la commande précédemment exécutée (le processus le plus récemment achevé au premier plan).</para>
<para>L'exemple suivant montre un simple test&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>if <parameter>[ $? -eq 0 ]</parameter></command>
<prompt>More input&gt;</prompt> <command>then echo <parameter>'That was a good job!'</parameter></command>
<prompt>More input&gt;</prompt> <command>fi</command>
That was a good job!

<prompt>anny ~&gt;</prompt>
</screen>
<para>L'exemple suivant démontre que <command>TEST-COMMANDS</command> pourrait être toute commande UNIX qui retourne un statut, et le <command>if</command> à son tour renvoie un statut à zéro&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>if <parameter>! grep $USER</parameter> <filename>/etc/passwd</filename></command>
<prompt>More input&gt;</prompt> <command>then echo <parameter>"votre compte utilisateur ne se trouve pas sur le système local"</parameter>; fi</command>
votre compte utilisateur ne se trouve pas sur le système local

<prompt>anny &gt;</prompt> <command>echo <varname>$?</varname></command>
0

<prompt>anny &gt;</prompt>
</screen>
<para>Le même résultat peut être obtenu comme ceci&nbsp;:</para>
<screen>
<prompt>anny &gt;</prompt> <command>grep <varname>$USER</varname> <filename>/etc/passwd</filename></command>

<prompt>anny &gt;</prompt> <command>if <parameter>[ $? -ne 0 ]</parameter> ; then echo <parameter>"pas un compte local"</parameter> ; fi</command>
pas un compte local

<prompt>anny &gt;</prompt>
</screen>

</sect3>
<sect3 id="sect_07_01_02_02"><title>Comparaisons numériques</title>
<para>Les exemples ci-dessous emploient des comparaisons numériques&nbsp;:</para>
<screen>
<prompt>anny &gt;</prompt> <command><varname>num</varname>=<parameter>`wc -l work.txt`</parameter></command>

<prompt>anny &gt;</prompt> <command>echo <varname>$num</varname></command>
201

<prompt>anny &gt;</prompt> <command>if <parameter>[ "$num" -gt "150" ]</parameter></command>
<prompt>More input&gt;</prompt> <command>then echo ; echo <parameter>"vous avez assez travaillé pour aujourd'hui."</parameter></command>
<prompt>More input&gt;</prompt> <command>echo ; fi</command>

vous avez assez travaillé pour aujourd'hui.


<prompt>anny &gt;</prompt>
</screen>
<para>Ce script est exécuté par cron chaque dimanche.  Si le numéro de semaine est pair, cela vous rappelle le passage des éboueurs&nbsp;:</para>
<screen>
#!/bin/bash

# Calculer le numéro de semaine à partir de la commande date&nbsp;:

WEEKOFFSET=$[ $(date +"%V") % 2 ]

# Tester si il y a un reste.  Si pas de reste, c'est une semaine paire donc envoyer un message.
# Sinon, ne rien faire.

if [ $WEEKOFFSET -eq "0" ]; then
  echo "Dimanche soir, les éboueurs passent." | mail -s "Les éboueurs passent" your@your_domain.org
fi
</screen>

</sect3>
<sect3 id="sect_07_01_02_03"><title>Comparaisons de chaînes</title>
<para>Un exemple de comparaison de chaînes avec le test de l'identifiant utilisateur&nbsp;:</para>
<screen>
if [ "$(whoami)" != 'root' ]; then
        echo "Vous ne détenez pas la permission de lancer $0 en tant que non administrateur."
        exit 1;
fi
</screen>
<para>Avec Bash, vous pouvez raccourcir ce type de construction.  L'équivalent compact du test ci-dessus est ce qui suit&nbsp;:</para>
<screen>
[ "$(whoami)" != 'root' ] &amp;&amp; ( echo vous êtes connectés avec un compte non administrateur; exit 1 )
</screen>
<para>Similaire à l'expression <quote>&amp;&amp;</quote> qui indique quoi faire si le test s'avère vrai, <quote>||</quote> spécifie quoi faire si le test est faux.</para>
<para>Une expression régulière peut être employée aussi dans les comparaisons&nbsp;:</para>
<screen>
<prompt>anny &gt;</prompt> <command><varname>genre</varname>=<parameter>"féminin"</parameter></command>

<prompt>anny &gt;</prompt> <command>if <parameter>[[ "$genre" == f* ]]</parameter></command>
<prompt>More input&gt;</prompt> <command>then echo <parameter>"Très honoré, Madame."</parameter>; fi</command>
Très honoré, Madame.

<prompt>anny &gt;</prompt>
</screen>
<note><title>Les vrais Programmeurs</title>
<para>La plupart des programmeurs préféreront employer l'intégrée <command>test</command> qui est équivalent à l'emploi du crochet de comparaison, comme ceci&nbsp;:</para>
<screen>
test "$(whoami)" != 'root' &amp;&amp; (echo vous êtes connectés avec un compte non administrateur; exit 1)
</screen>
</note>
<note><title>No exit?</title>
<para>Si vous appelez <command>exit</command> dans un sous-Shell, celui-ci ne passera pas de variables à son parent.  Employez { and } au lieu de ( and ) si vous ne voulez pas que Bash en fourchant crée un sous-Shell.</para>
</note>
<para>Voir les pages info de Bash pour plus de détails sur la correspondance de patron avec les blocs <quote>(( EXPRESSION ))</quote> et <quote>[[ EXPRESSION ]]</quote>.</para>
</sect3>
</sect2>

</sect1>
<sect1 id="sect_07_02"><title>L'emploi avancé de if</title>
<sect2 id="sect_07_02_01"><title>les blocs if/then/else</title>
<sect3 id="sect_07_02_01_01"><title>Exemple simple</title>
<para>Voici la construction à employer pour que le cours du traitement s'oriente d'une façon si la commande <command>if</command> renvoie vrai, et d'une autre si elle renvoie faux.  Un exemple&nbsp;:</para>
<screen>
<prompt>freddy scripts&gt;</prompt> <command><varname>genre</varname>=<parameter>"masculin"</parameter></command>

<prompt>freddy scripts&gt;</prompt> <command>if <parameter>[[ "$genre" == "f*" ]]</parameter></command>
<prompt>More input&gt;</prompt> <command>then echo <parameter>"Très honoré, Madame."</parameter></command>
<prompt>More input&gt;</prompt> <command>else echo <parameter>"Comment se fait-il que le verre de Madame  soit vide&nbsp;?"</parameter></command>
<prompt>More input&gt;</prompt> <command>fi</command>
Comment se fait-il que le verre de Madame  soit vide&nbsp;?

<prompt>freddy scripts&gt;</prompt>
</screen>
<important><title>[] versus [[]]</title>
<para>Contrairement à <parameter>[</parameter>, <parameter>[[</parameter> empêche le découpage en mots des valeurs de variables.  Donc, si <varname>VAR="var with spaces"</varname>, vous n'avez pas besoin de mettre des guillemets à <varname>$VAR</varname> dans un test - même si cela reste une bonne habitude.  Aussi, <parameter>[[</parameter> inhibe l'analyse des chemins, de sorte que des chaînes littérales avec des jokers ne seront pas interprétées comme des noms de fichiers.  Les <parameter>[[</parameter>, <parameter>==</parameter> et <parameter>!=</parameter> font interpréter la chaîne à la droite comme un patron glob du Shell qui doit correspondre à la valeur à la gauche, par exemple&nbsp;: <parameter>[[ "value" == val* ]]</parameter>.</para>
</important>
<para>De même que la liste <command>CONSEQUENT-COMMANDS</command> suivant le  <command>then</command>, la liste <command>ALTERNATE-CONSEQUENT-COMMANDS</command> suivant le  <command>else</command> peut contenir toute commande de style UNIX qui retourne un statut d'exécution.</para> 
<para>Un autre exemple, tiré de celui de la <xref linkend="sect_07_01_02_01" />&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>su <parameter>-</parameter></command>
Password:
<prompt>[root@elegance root]#</prompt> <command>if <parameter>! grep ^$USER</parameter> <filename>/etc/passwd</filename> 1&gt; <filename>/dev/null</filename></command>
<prompt>&gt;</prompt> <command>then echo <parameter>"votre compte utilisateur ne se trouve pas sur le système local"</parameter></command>
<prompt>&gt;</prompt> <command>else echo <parameter>"votre compte utilisateur ne se trouve pas sur le système local"</parameter></command>
<prompt>&gt;</prompt> <command>fi</command>
votre compte utilisateur se trouve dans /etc/passwd file
<prompt>[root@elegance root]#</prompt>
</screen>
<para>Nous permutons vers le compte <emphasis>root</emphasis> pour montrer l'effet du  <command>else</command> - votre <emphasis>root</emphasis> est d'ordinaire un compte local tandis que votre compte personnel peut être géré par un système central, tel qu'un serveur LDAP.</para>
</sect3>
<sect3 id="sect_07_02_01_02"><title>Contrôle des paramètres de la ligne de commande</title>
<para>Au lieu de déclarer une variable puis d'exécuter un script, il est fréquemment plus élégant de mettre la valeur de la variable dans la ligne de commande<indexterm><primary>arguments</primary><secondary>testing</secondary></indexterm>.</para>
<para>Pour ce faire nous employons les paramètres positionnels <indexterm><primary>arguments</primary><secondary>positional parameters</secondary></indexterm> <varname>$1</varname>, <varname>$2</varname>, ..., <varname>$N</varname>.  <varname>$#</varname> mémorise le nombre de paramètres de la ligne de commande<indexterm><primary>arguments</primary><secondary>number of arguments</secondary></indexterm>.  <varname>$0</varname> mémorise le nom du script.</para>
<para>Voici un exemple simple&nbsp;:</para>

<figure><title>Test d'une ligne de commande avec if</title>
<mediaobject>
<imageobject>
<imagedata fileref="&images;penguin.sh.eps" format="EPS"></imagedata></imageobject><imageobject>
<imagedata fileref="&images;penguin.sh.png" format="PNG"></imagedata>
</imageobject>
<textobject>
<phrase>Simple bloc if/then/else/fi : if [ "$1" == poisson ]; then echo "Tux adore ça"; else echo "Tux veut du poisson&nbsp;!"; fi</phrase>
</textobject>
</mediaobject>
</figure>
<para>Voici un autre exemple avec 2<indexterm><primary>arguments</primary><secondary>examples</secondary></indexterm> paramètres&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>cat <filename>weight.sh</filename></command>
#!/bin/bash

# Ce script affiche un message au sujet de votre poids si vous donnez 
# votre poids en kilos et votre taille en centimètres.

weight="$1"
height="$2"
idealweight=$[$height - 110]

if [ $weight -le $idealweight ] ; then
  echo "Vous devriez manger un peu plus gras."
else
  echo "Vous devriez manger un peu plus de fruits."
fi

<prompt>anny ~&gt;</prompt> <command>bash <option>-x</option> <filename>weight.sh</filename> <parameter>55 169</parameter></command>
+ weight=55
+ height=169
+ idealweight=59
+ '[' 55 -le 59 ']'
+ echo 'Vous devriez manger un peu plus gras.'
Vous devriez manger un peu plus gras.
</screen>
</sect3>
<sect3 id="sect_07_02_01_03"><title>Tester le nombre de paramètres</title>
<para>L'exemple suivant<indexterm><primary>arguments</primary><secondary>example test number</secondary></indexterm> montre comment changer le script précédent de sorte qu'il affiche un message si plus ou moins de 2 paramètres sont donnés&nbsp;:</para>
<screen>
<prompt>anny ~&gt;</prompt> <command>cat <filename>weight.sh</filename></command>
#!/bin/bash

# Ce script affiche un message au sujet de votre poids si vous donnez 
# votre poids en kilos et votre taille en centimètres.

if [ ! $# == 2 ]; then
  echo "Usage: $0 poids_en_kilos taille_en_centimètres"
  exit
fi

weight="$1"
height="$2"
idealweight=$[$height - 110]

if [ $weight -le $idealweight ] ; then
  echo "Vous devriez manger un peu plus gras."
else
  echo "Vous devriez manger un peu plus de fruits."
fi

<prompt>anny ~&gt;</prompt> <command>weight.sh <parameter>70 150</parameter></command>
Vous devriez manger un peu plus de fruits.

<prompt>anny ~&gt;</prompt> <command>weight.sh <parameter>70 150 33</parameter></command>
Usage: ./weight.sh poids_en_kilos taille_en_centimètres
</screen>
<para>Le premier paramètre est référencé par <varname>$1</varname>, le second par <varname>$2</varname> et ainsi de suite.  Le nombre total de paramètres est stocké dans <varname>$#</varname>.</para>
<para>Consulter la <xref linkend="sect_07_02_05" /> pour voir une façon plus élégante d'afficher des messages de mode d'emploi.</para>
</sect3>
<sect3 id="sect_07_02_01_04"><title>Test de l'existence d'un fichier</title>
<para>Ce test est fait dans beaucoup de scripts<indexterm><primary>arguments</primary><secondary>example existence</secondary></indexterm>, parce que il n'y a pas d'intérêt à lancer un programme si vous savez qu'il ne va pas fonctionner&nbsp;:</para>
<screen>
#!/bin/bash

# Ce script donne des informations au sujet d'un fichier.

FILENAME="$1"

echo "Properties for $FILENAME:"

if [ -f $FILENAME ]; then
  echo "Size is $(ls -lh $FILENAME | awk '{ print $5 }')"
  echo "Type is $(file $FILENAME | cut -d":" -f2 -)"
  echo "Inode number is $(ls -i $FILENAME | cut -d" " -f1 -)"
  echo "$(df -h $FILENAME | grep -v Mounted | awk '{ print "On",$1", \
which is mounted as the",$6,"partition."}')"
else
  echo "Le fichier est non-existant."
fi
</screen>
<para>

Notez que le fichier est référencé au moyen d'une variable&nbsp;; dans 
ce cas c'est le premier paramètre du script. Alternativement, quand 
aucun paramètre n'est donné, l'emplacement du fichier est mémorisé 
généralement dans une variable au début du script, et son contenu est 
connu par l'invocation de la variable. De sorte que si vous voulez 
changer le nom d'un fichier dans un script, vous n'avez que à le 
modifier une fois.

</para>
<tip><title>Nom de fichier avec des espaces</title>
<para>L'exemple plus haut échouera si la valeur de <varname>$1</varname> peut être découpée en plusieurs mots.  Dans ce cas, la commande <command>if</command> peut être figée soit en mettant des guillemets autour du nom de fichier, soit en employant <parameter>[[</parameter> au lieu de  <parameter>[</parameter>.</para>
</tip>
</sect3>
</sect2>
<sect2 id="sect_07_02_02"><title>Les blocs if/then/elif/else</title>
<sect3 id="sect_07_02_02_01"><title>Généralité</title>
<para>C'est la forme complète de l'instruction <command>if</command>&nbsp;:</para>
<cmdsynopsis><command>if TEST-COMMANDS; then</command></cmdsynopsis>
<cmdsynopsis><command>CONSEQUENT-COMMANDS;</command></cmdsynopsis>
<cmdsynopsis><command>elif MORE-TEST-COMMANDS; then</command></cmdsynopsis>
<cmdsynopsis><command>MORE-CONSEQUENT-COMMANDS;</command></cmdsynopsis>
<cmdsynopsis><command>else ALTERNATE-CONSEQUENT-COMMANDS;</command></cmdsynopsis>
<cmdsynopsis><command>fi</command></cmdsynopsis>
<para>La liste <command>TEST-COMMANDS</command> est exécutée, et si son statut d'exécution est zéro, la liste <command>CONSEQUENT-COMMANDS</command> est exécutée.  Si 
<command>TEST-COMMANDS</command> renvoie un statut différent de zéro, chaque liste <command>elif</command> est exécutée à son tour, et si leur statut d'exécution est zéro, le <command>MORE-CONSEQUENT-COMMANDS</command> correspondant est exécuté et la commande se termine.  Si <command>else</command> est suivi par une liste  <command>ALTERNATE-CONSEQUENT-COMMANDS</command>, et que la dernière commande dans le dernier  <command>if</command> ou <command>elif</command> renvoie un statut différent de zéro, alors  <command>ALTERNATE-CONSEQUENT-COMMANDS</command> est exécuté.  Le statut retourné est le statut d'exécution de la dernière commande exécutée, ou zéro si aucune condition n'est vraie.</para>
</sect3>
<sect3 id="sect_07_02_02_02"><title>Exemple</title>
<para>Ceci est un exemple que vous pouvez mettre dans votre crontab pour une exécution quotidienne&nbsp;:</para>
<screen>
<prompt>anny /etc/cron.daily&gt;</prompt> <command>cat <filename>disktest.sh</filename></command>
#!/bin/bash

# Ce script fait un test très simple pour contrôler l'espace disque.

space=`df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`
alertvalue="80"

if [ "$space" -ge "$alertvalue" ]; then
  echo "Au moins un de mes disques est bientôt plein&nbsp;!" | mail -s "daily diskcheck" root
else
  echo "Espace disque correct" | mail -s "daily diskcheck" root
fi
</screen>
</sect3>
</sect2>

<sect2 id="sect_07_02_03"><title>Les instructions if imbriquées</title>
<para>Comprise dans une instruction <command>if</command> on peut inclure une autre instruction <command>if</command>.  Vous pouvez inclure autant de niveaux de <command>if</command> imbriqués que vous pouvez appréhender logiquement.</para>
<para>Voici un exemple testant l'année bissextile&nbsp;:</para>
<screen>
<prompt>anny ~/testdir&gt;</prompt> <command>cat <filename>testleap.sh</filename></command>
#!/bin/bash
# Ce script teste si nous sommes dans une année bissextile ou pas.

year=`date +%Y`

if [ $[$year % 400] -eq "0" ]; then
  echo "This is a leap year.  Février a 29 jours."
elif [ $[$year % 4] -eq 0 ]; then
        if [ $[$year % 100] -ne 0 ]; then
          echo "Année bissextile, Février a 29 jours."
        else
          echo "Année non bissextile.  Février a 28 jours."
        fi
else
  echo "Année non bissextile.  Février a 28 jours."
fi

<prompt>anny ~/testdir&gt;</prompt> <command>date</command>
Tue Jan 14 20:37:55 CET 2003

<prompt>anny ~/testdir&gt;</prompt> <command>testleap.sh</command>
Année non bissextile.
</screen>

</sect2>
<sect2 id="sect_07_02_04"><title>Opérations booléennes</title>
<para>Le script ci-dessus peut être abrégé avec les opérateurs booléens <quote>AND</quote> (&amp;&amp;) et <quote>OR</quote> (||).</para>
<figure><title>Exemple employant les opérateurs booléens</title>
<mediaobject>
<imageobject>
<imagedata fileref="&images;leaptest.sh.eps" format="EPS"></imagedata></imageobject><imageobject>
<imagedata fileref="&images;leaptest.sh.png" format="PNG"></imagedata>
</imageobject>
<textobject>
<phrase>year=`date +%Y`; if (( ("$year" % 400) == "0" )) || (( ("$year" % 4 == "0") &amp;&amp; ("$year" % 100 != "0") )); then echo "Année bissextile."; else echo "Année non bissextile"; fi</phrase>
</textobject>
</mediaobject>
</figure>
<para>Nous employons le double crochet pour tester<indexterm><primary>arithmetic expression</primary><secondary>testing</secondary></indexterm> les expressions arithmétiques, voir la <xref linkend="sect_03_04_05" />.  Ceci est équivalent à l'instruction  <command>let</command>.  Ici, vous allez être bloqué si vous employez les crochets, si vous essayez quelque chose de la sorte <command>$[$year % 400]</command>,  parce que ici, les crochets ne représentent pas une vraie commande mais eux-mêmes.</para>
<para>Parmi d'autres éditeurs, <command>gvim</command> est l'un de ceux qui supporte les codes de couleur selon le format de fichier&nbsp;; de tel éditeurs sont pratiques pour pister les erreurs d'écriture.</para>

</sect2>


<sect2 id="sect_07_02_05"><title>Emploi de l'instruction exit et du if</title>
<para>Nous avons déjà rencontré l'instruction <command>exit</command> dans la <xref linkend="sect_07_02_01_03" />.  Il achève l'exécution du script.  Il est plus souvent utilisé si l'entrée requise de l'utilisateur est incorrecte, si une instruction a échouée ou si une autre erreur intervient.</para>
<para>L'instruction <command>exit</command> admet un argument optionnel<indexterm><primary>arguments</primary><secondary>exit status</secondary></indexterm>.  Cet argument est le code sous forme d'entier du statut d'exécution, qui est renvoyé au parent et stocké dans la variable <varname>$?</varname>.</para>
<para>Un argument à zéro<indexterm><primary>exit status</primary><secondary>arguments</secondary></indexterm> signifie que le script s'est exécuté correctement.  Tout autre valeur peut être employée par le programmeur pour renvoyer divers messages au parent, afin que divers traitements soient activés selon l'échec ou la réussite du processus enfant.  Si aucun argument n'est donné à la commande <command>exit</command>, le Shell parent exploite la valeur courante de la variable <varname>$?</varname>.</para>
<para>Ci-dessous un exemple avec un script <filename>penguin.sh</filename> légèrement adapté,  lequel renvoie son statut d'exécution vers son parent, <filename>feed.sh</filename>&nbsp;:</para>
<screen>
<prompt>anny ~/testdir&gt;</prompt> <command>cat <filename>penguin.sh</filename></command>
#!/bin/bash
                                                                                                 
# Ce script vous laisse présenter divers menus à Tux.  Il ne sera heureux que
# quand il aura du poisson.  Nous avons aussi ajouté un dauphin et (logiquement) un chameau.
                                                                                                 
if [ "$menu" == "poisson" ]; then
  if [ "$animal" == "pingouin" ]; then
    echo "Hmmmmmm poisson... Tux heureux&nbsp;!"
  elif [ "$animal" == "dauphin" ]; then
    echo "Pweetpeettreetppeterdepweet&nbsp;!"
  else
    echo "*prrrrrrrt*"
  fi
else
  if [ "$animal" == "pingouin" ]; then
    echo "Tux déteste ça.  Tux veut du poisson&nbsp;!"
    exit 1
  elif [ "$animal" == "dauphin" ]; then
    echo "Pweepwishpeeterdepweet&nbsp;!"
    exit 2
  else
    echo "Voulez-vous lire cette affiche&nbsp;?!"
    exit 3
  fi
fi
</screen>
<para>Ce script est appelé depuis le suivant, qui donc exporte ses variables  <varname>menu</varname> et <varname>animal</varname>&nbsp;:</para>
<screen>
<prompt>anny ~/testdir&gt;</prompt> <command>cat <filename>feed.sh</filename></command>
#!/bin/bash
# Ce script procède selon le statut d'exécution renvoyé par penguin.sh
                                                                                                 
export menu="$1"
export animal="$2"
                                                                                                 
feed="/nethome/anny/testdir/penguin.sh"
                                                                                                 
$feed $menu $animal
                                                                                                 
case $? in
                                                                                                 
1)
  echo "Gaffe&nbsp;: Vous feriez mieux de lui donner du poisson, avant qu'il ne s'énerve..."
  ;;
2)
  echo "Gaffe&nbsp;: C'est à cause de gens comme vous qu'il quitte la terre tout le temps..."
  ;;
3)
  echo "Gaffe&nbsp;: Achetez la nourriture que le zoo fournit pour les animaux, i@**@, comment pensez-vous que nous survivons&nbsp;?"
  ;;
*)
  echo "Gaffe&nbsp;: N'oubliez pas le guide&nbsp;!"
  ;;
esac
                                                                                                 
<prompt>anny ~/testdir&gt;</prompt> <command>./feed.sh <parameter>apple penguin</parameter></command>
Tux déteste ça.  Tux veut du poisson&nbsp;!
Gaffe&nbsp;: Vous feriez mieux de lui donner du poisson, avant qu'il ne s'énerve...
</screen>
<para>Comme vous le voyez, le statut d'exécution peut être déterminé librement.  Les commandes ont souvent une série de codes définis&nbsp;; voir le manuel du programmeur pour plus d'informations sur chaque commande.</para></sect2>


</sect1>
<sect1 id="sect_07_03"><title>Utiliser les instructions case</title>
<sect2 id="sect_07_03_01"><title>Les conditions simplifiées</title>
<para>Les instructions <command>if</command> imbriquées paraissent pratiques, mais dès que vous êtes confrontés à quelques variantes, cela engendre la confusion.  Pour des conditions complexes, employez la syntaxe de <command>case</command>&nbsp;:</para>
<cmdsynopsis><command>case <function>EXPRESSION</function> in <function>CASE1</function>) COMMAND-LIST;; <function>CASE2</function>) COMMAND-LIST;; ... <function>CASEN</function>) COMMAND-LIST;; esac</command></cmdsynopsis>
<para>Chaque cas est une expression qui cible un patron.  Les commandes <command>COMMAND-LIST</command> de la première correspondance trouvée sont exécutées.  Le symbole <quote>|</quote> est employé pour séparer de multiples patrons, et l'opérateur <quote>)</quote> termine la liste des patrons.  Chaque case et ses commandes associées est appelé une <emphasis>clause</emphasis>.  Chaque clause doit se terminer par <quote>;;</quote>.  Chaque instruction <command>case</command> est terminée par l'instruction <command>esac</command>.</para>
<para>Dans l'exemple, nous montrons l'emploi de case pour envoyer un message d'avertissement plus précis avec le script <filename>disktest.sh</filename>&nbsp;:</para>
<screen>
<prompt>anny ~/testdir&gt;</prompt> <command>cat <filename>disktest.sh</filename></command>
#!/bin/bash

# Ce script fait un test très simple pour vérifier l'espace disque.

space=`df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`

case $space in
[1-6]*)
  Message="Tout est bon."
  ;;
[7-8]*)
  Message="Commencer à songer à faire de la place.  Il y a une partition qui est $space % pleine."
  ;;
9[1-8])
  Message="Dépêchez-vous avec ce nouveau disque..  Une partition est $space % pleine."
  ;;
99)
  Message="Je suis en train de me noyer&nbsp;!  Il y a une partition à $space %&nbsp;!"
  ;;
*)
  Message="Il semble que je tourne avec un espace disque inexistant..."
  ;;
esac

echo $Message | mail -s "disk report `date`" anny

<prompt>anny ~/testdir&gt;</prompt>
Vous avez un nouveau mail.

<prompt>anny ~/testdir&gt;</prompt> <command>tail <parameter>-16</parameter> <filename>/var/spool/mail/anny</filename></command>
From anny@octarine Tue Jan 14 22:10:47 2003
Return-Path: &lt;anny@octarine&gt;
Received: from octarine (localhost [127.0.0.1])
        by octarine (8.12.5/8.12.5) with ESMTP id h0ELAlBG020414
        for &lt;anny@octarine&gt;; Tue, 14 Jan 2003 22:10:47 +0100
Received: (from anny@localhost)
        by octarine (8.12.5/8.12.5/Submit) id h0ELAltn020413
        for anny; Tue, 14 Jan 2003 22:10:47 +0100
Date: Tue, 14 Jan 2003 22:10:47 +0100
From: Anny &lt;anny@octarine&gt;
Message-Id: &lt;200301142110.h0ELAltn020413@octarine&gt;
To: anny@octarine
Subject: disk report Tue Jan 14 22:10:47 CET 2003

Commencer à songer à faire de la place.  Il y a une partition qui est 87 % pleine.

<prompt>anny ~/testdir&gt;</prompt>
</screen>
<para>Bien sûr vous pourriez avoir ouvert votre programme de messagerie pour contrôler le résultat&nbsp;; c'est juste pour montrer que le script envoie un mail correct avec  <quote>To:</quote>, <quote>Subject:</quote> and <quote>From:</quote> header lines.</para>
<para>Beaucoup plus d'exemples de l'instruction <command>case</command> peuvent être trouvés dans le répertoire des scripts d'initialisation de votre système.  Le script de démarrage emploie un case <command>start</command> et <command>stop</command> pour démarrer ou arrêter les processus du système.  Un exemple théorique peut être trouvé dans la section suivante.</para>
</sect2>

<sect2 id="sect_07_03_02"><title>Exemple de script d'initialisation</title>
<para>Les scripts d'initialisation ont souvent l'usage d'instructions <command>case</command> pour démarrer, arrêter et mettre en file d'attente les services du système.  Voici un extrait du script qui démarre <application>Anacron</application>, un démon qui lance des commandes périodiquement avec une fréquence spécifiée en jours.</para>
<screen>
case "$1" in
        start)
            start
            ;;
         
        stop)
            stop
            ;;
         
        status)
            status anacron
            ;;
        restart)
            stop
            start
            ;;
        condrestart)
            if test "x`pidof anacron`" != x; then
                stop
                start
            fi
            ;;
         
        *)
            echo $"Usage: $0 {start|stop|restart|condrestart|status}"
            exit 1
 
esac
</screen>
<para>Les tâches à exécuter dans chaque cas, telles arrêter ou démarrer le démon, sont définies par des fonctions, dont la source est partiellement dans le fichier <filename>/etc/rc.d/init.d/functions</filename>.  Voir le <xref linkend="chap_11" /> pour plus d'explications.</para>
</sect2>

</sect1>
<sect1 id="sect_07_04"><title>Résumé</title>
<para>Dans ce chapitre nous avons appris comment écrire des conditions dans un script de sorte que différentes tâches puissent être menées à bien selon le succès ou l'échec d'une commande.  L'action peut être déterminée par l'emploi de l'instruction <command>if</command>.  Ceci permet d'effectuer des comparaisons de chaînes et arithmétique, et de tester le statut d'exécution, l'entrée et les fichiers requis par le script.</para>
<para>Un simple test <command>if/then/fi</command> souvent précède des commandes dans un script Shell afin d'éviter la production de résultat, de sorte que le script peut aisément être lancé en tâche de fond ou via l'outil <application>cron</application>.  Les conditions trop complexes sont généralement intégrées à une instruction  <command>case</command>.</para>
<para>A la suite d'un test positif, le script peut explicitement informer le parent par le biais du statut <command>exit 0</command>.  A la suite d'un échec, tout autre nombre peut être retourné.  Basé sur ce code retour, le programme parent peut déterminer l'action appropriée.</para>

</sect1>
<sect1 id="sect_07_05"><title>Exercices</title>
<para>Voici quelques idées pour vous lancer dans l'écriture de scripts <command>if</command>&nbsp;:</para>
<orderedlist>

<listitem><para>Employez un bloc <command>if/then/elif/else</command> 
qui affiche les informations du mois courant. Le script devrait afficher 
le nombre de jours du mois, et donner des informations sur l'année 
bissextile si le mois courant est février.</para></listitem>

<listitem><para>Faire la même chose avec une instruction 
<command>case</command> et une variante de l'usage de la commande 
<command>date</command>.</para></listitem>

<listitem><para>Modifier <filename>/etc/profile</filename> afin d'être 
accueilli par un message personnalisé quand vous vous connectez au 
système en tant que <emphasis>root</emphasis>.</para></listitem>

<listitem><para>Modifier le script <filename>leaptest.sh</filename> dans 
<xref linkend="sect_07_02_04" /> afin qu'il nécessite un paramètre, 
l'année. Tester que exactement un seul paramètre est 
passé.</para></listitem>

<listitem><para>Ecrire un script appelé 
<filename>whichdaemon.sh</filename> qui vérifie que les démons 
<command>httpd</command> et <command>init</command> sont lancés sur 
votre système. Si un <command>httpd</command> est lancé, le script 
devrait afficher un message comme <quote>Cette machine fait tourner un 
serveur WEB.</quote> Employez <command>ps</command> pour contrôler les 
processus.</para></listitem>

<listitem><para>Écrire un script qui fait la sauvegarde de votre 
répertoire racine sur une machine distante en utilisant 
<command>scp</command>. Le script devrait écrire son rapport dans un 
fichier journal, par exemple <filename>~/log/homebackup.log</filename>. 
Si vous n'avez pas de seconde machine pour y copier la sauvegarde, se 
servir de <command>scp</command> pour tester la copie sur la machine 
locale. Ceci nécessite des clés SSH entre 2 hôtes, ou sinon vous devez 
fournir un mot de passe. La création de clés SSH est expliquée dans le 
<command>man 
<parameter>ssh-keygen</parameter></command>.</para></listitem>

<listitem><para>Adaptez le script à partir du 1er exemple à la <xref 
linkend="sect_07_03_01" /> pour inclure le cas de l'occupation de 
exactement 90% de l'espace disque, et de moins de 10% de l'espace 
disque.</para>

<para>Le script devrait se servir de <command>tar 
<option>cf</option></command> pour la création de la sauvegarde et 
<command>gzip</command> ou <command>bzip2</command> pour la 
décompression du fichier <filename>.tar</filename>. Mettre tous les noms 
de fichiers dans des variables. Mettre le nom du serveur distant et du 
répertoire distant dans une variable. Ce sera plus facile de réutiliser 
ce script ou d'y faire des modifications dans le futur.</para>

<para>Le script devrait vérifier l'existence d'une archive compressée. 
Si elle existe, la supprimer d'abord pour éviter les messages 
d'erreur.</para>

<para>Le script devrait aussi contrôler l'espace disque disponible. 
Avoir à l'esprit qu'à un moment donné vous pourrez avoir en même temps 
sur le disque les données dans votre répertoire racine, celles dans le 
fichier <filename>.tar</filename> et celle dans l'archive compressée. Si 
il n'y a pas assez d'espace, quitter avec un message d'erreur dans le 
fichier journal.</para>

<para>Le script devrait nettoyer l'archive compressée avant de se 
terminer.</para></listitem>

</orderedlist>

</sect1>
</chapter>