<chapter id="chap_09">
<title>Tâches répétitives</title>
<abstract>
<para>À la fin de ce chapitre, vous serez capable de</para>
<para>
<itemizedlist>
<listitem><para>Ecrire des boucles <command>for</command>, <command>while</command> and <command>until</command> , et décider quelle boucle convient à quel besoin.</para></listitem>
<listitem><para>Utiliser les intégrées Bash <command>break</command> et <command>continue</command></para></listitem>
<listitem><para>Ecrire des scripts avec l'instruction <command>select</command>.</para></listitem>
<listitem><para>Ecrire des scripts qui admettent un nombre variable de paramètres.</para></listitem>
</itemizedlist></para>
</abstract>
<sect1 id="sect_09_01"><title>La boucle loop</title>
<sect2 id="sect_09_01_01"><title>Comment ça marche&nbsp;?</title>
<para>La boucle <command>for</command> est la première des 3 structures de boucles du Shell.  Cette boucle autorise la spécification d'une liste de valeurs.  Une liste de commandes est exécutée pour chaque valeur de la liste.</para>
<para>La syntaxe de cette boucle est&nbsp;:</para>
<cmdsynopsis><command>for <varname>NOM</varname> [in LIST ]; do COMMANDES; done</command></cmdsynopsis>
<para>Si <command>[in LIST]</command> est absent, il est remplacé par <command>in <varname>$@</varname></command> et <command>for</command> exécute les <command>COMMANDES</command> une fois pour chaque paramètre positionnel déclaré (voir <xref linkend="sect_03_02_05" /> et <xref linkend="sect_07_02_01_02" />).</para>
<para>Le statut retourné est le statut d'exécution de la dernière commande exécutée.  Si aucune commande n'est exécutée parce que <varname>LIST</varname> ne résulte en aucun élément, le code retour est zéro.</para>
<para><varname>NOM</varname> peut être tout nom de variable, même si <varname>i</varname> est employé très souvent.  <varname>LIST</varname> peut être toute liste de mots, chaînes ou nombres qui peuvent être des littéraux ou générés par toute commande.  Les <command>COMMANDES</command> à exécuter peuvent être aussi toute commande système, script, programme ou instruction Shell.  Au premier passage dans la boucle, <varname>NOM</varname> est valorisé à la valeur du premier élément dans <varname>LIST</varname>.  Au deuxième passage, sa valeur est donnée par le second élément dans la liste, et ainsi de suite.  La boucle termine quand <varname>NOM</varname> a pris une fois la valeur de chaque élément de <varname>LIST</varname> et qu'il ne reste plus d'éléments dans <varname>LIST</varname>.</para>
</sect2>
<sect2 id="sect_09_01_02"><title>Exemples</title>
<sect3 id="sect_09_01_02_03"><title>Utiliser la substitution de commande pour spécifier les éléments de LIST</title>
<para>Le premier exemple est une ligne de commande qui montre l'emploi d'une boucle <command>for</command> pour effectuer une copie de sauvegarde de chaque fichier <filename>.xml</filename>.  Une fois cette commande exécutée, il est plus sûr de travailler sur les sources&nbsp;:</para>
<screen>
<prompt>[carol@octarine ~/articles]</prompt> <command>ls <filename>*.xml</filename></command>
file1.xml  file2.xml  file3.xml

<prompt>[carol@octarine ~/articles]</prompt> <command>ls <filename>*.xml</filename> &gt; <filename>list</filename></command>

<prompt>[carol@octarine ~/articles]</prompt> <command>for <varname>i</varname> in <parameter>`cat list`</parameter>; do cp <filename>"$i" "$i".bak</filename> ; done</command>

<prompt>[carol@octarine ~/articles]</prompt> <command>ls <filename>*.xml*</filename></command>
file1.xml  file1.xml.bak  file2.xml  file2.xml.bak  file3.xml  file3.xml.bak
</screen>
<para>Celui-ci liste les fichiers de <filename>/sbin</filename> qui sont des purs fichiers texte, et donc peut-être des scripts&nbsp;:</para>
<screen>
<command>for <varname>i</varname> in <parameter>`ls /sbin`</parameter>; do file <filename>/sbin/$i</filename> | grep <parameter>ASCII</parameter>; done</command>
</screen>
</sect3>
<sect3 id="sect_09_01_02_02"><title>Utiliser la valeur d'une variable pour spécifier les éléments de LIST</title>
<para>Ce qui suit est un script spécifique pour convertir des fichiers HTML respectant un certain schéma en fichiers PHP.  La conversion est faite en extrayant les 25 premières lignes et les 21 dernières, les remplaçant par 2 étiquettes PHP qui correspondent aux lignes d'entête et d'empied&nbsp;:</para>
<screen>
<prompt>[carol@octarine ~/html]</prompt> <command>cat <filename>html2php.sh</filename></command>
#!/bin/bash
# specific conversion script for my html files to php
LIST="$(ls *.html)"
for i in "$LIST"; do
     NEWNAME=$(ls "$i" | sed -e 's/html/php/')
     cat beginfile &gt; "$NEWNAME"
     cat "$i" | sed -e '1,25d' | tac | sed -e '1,21d'| tac &gt;&gt; "$NEWNAME"
     cat endfile &gt;&gt; "$NEWNAME"
done
</screen>
<para>Comme nous ne faisons pas un comptage des lignes, il n'y a pas moyen de savoir le numéro de ligne à laquelle commencer la suppression avant d'avoir atteint la fin.  Cette difficulté est surmontée en utilisant <command>tac</command>, lequel inverse l'ordre des lignes dans un fichier.</para>
<tip><title>La commande basename</title>
<para>Plutôt que d'employer <command>sed</command> pour remplacer le suffixe <filename>html</filename> par <filename>php</filename>, il serait plus propre d'employer la commande <command>basename</command>.  Voir les pages man pour plus de détails.</para>
</tip>
<warning><title>Caractères spéciaux</title>
<para>Vous aurez des soucis si la liste est transformée en noms de fichiers contenant des espaces et autres caractères irréguliers.  Une construction plus appropriée pour obtenir la liste serait d'utiliser la fonction globale du Shell, comme ceci&nbsp;:</para>
<screen>
for i in $PATHNAME/*; do
        commands
done
</screen>
</warning>
</sect3>
</sect2>

</sect1>
<sect1 id="sect_09_02"><title>La boucle while</title>
<sect2 id="sect_09_02_01"><title>Qu'est-ce que c'est&nbsp;?</title>
<para>La structure <command>while</command> permet une exécution répétitive d'une liste de commandes tant que la commande qui contrôle le <command>while</command> s'exécute avec succès (code retour égal à zéro).  La syntaxe est&nbsp;:</para>
<cmdsynopsis><command>while CONTROL-COMMAND; do CONSEQUENT-COMMANDS; done</command></cmdsynopsis>
<para><command>CONTROL-COMMAND</command> peut être toute(s) commande(s) qui peut s'achever avec un statut de succès ou d'échec.  Le <command>CONSEQUENT-COMMANDS</command> peut être tout programme, script ou bloc Shell.</para>
<para>Dès que <command>CONTROL-COMMAND</command> échoue, la boucle est arrêtée.  Dans un script, la commande suivant l'instruction <command>done</command> est exécutée.</para>
<para>Le statut retourné est le statut d'exécution de la dernière commande de  <command>CONSEQUENT-COMMANDS</command> ou zéro si aucune n'est exécutée</para>
</sect2>
<sect2 id="sect_09_02_02"><title>Exemples</title>
<sect3 id="sect_09_02_02_01"><title>Exemple simple d'utilisation de while</title>
<para>Voici un exemple pour les impatients&nbsp;:</para>
<screen>
#!/bin/bash

# Le script ouvre 4 fenêtres de terminal.

i="0"

while [ $i -lt 4 ]
do
xterm &amp;
i=$[$i+1]
done
</screen>
</sect3>
<sect3 id="sect_09_02_02_02"><title>Des boucles while imbriquées</title>
<para>L'exemple ci-dessous a été écrit pour copier des images qui sont prises par une WEBcam vers un répertoire WEB.  Toute les 5 minutes une image est prise.  Toute les heures, un nouveau répertoire est créé, pour contenir les images de cette heure.  Chaque jour, un nouveau répertoire est créé contenant 24 sous-répertoires.  Le script s'exécute en tâche de fond.</para>
<screen>
#!/bin/bash

# Ce script copie les fichiers de mon répertoire racine dans le répertoire du serveur WEB.
# (Utilisez des clés scp et SSH pour un répertoire distant)
# Un nouveau répertoire est créé à chaque heure.

PICSDIR=/home/carol/pics
WEBDIR=/var/www/carol/webcam

while true; do 
        DATE=`date +%Y%m%d`
        HOUR=`date +%H`
        mkdir $WEBDIR/"$DATE"
        
        while [ $HOUR -ne "00" ]; do 
                DESTDIR=$WEBDIR/"$DATE"/"$HOUR"
                mkdir "$DESTDIR"
                mv $PICDIR/*.jpg "$DESTDIR"/
                sleep 3600
                HOUR=`date +%H`
        done
done
</screen>

<para>Notez l'emploi de l'instruction <command>true</command>.  Il signifie&nbsp;: continuer l'exécution jusqu'à une interruption forcée (avec  <command>kill</command> ou <keycap>Ctrl</keycap>+<keycap>C</keycap>).</para>
<para>Ce petit script peut être utilisé pour des tests de simulation&nbsp;; il génère des fichiers&nbsp;:</para>
<screen>
#!/bin/bash

# Ce script génère un fichier toute les 5 minutes

while true; do
touch pic-`date +%s`.jpg
sleep 300
done
</screen>
<para>Notez l'emploi de la commande <command>date</command> pour générer toute sorte de nom de fichier et de répertoire.  Voir les pages man pour plus de détails.</para>
<note><title>Mettez à profit le système</title>
<para>L'exemple précédent existe pour le besoin de la démonstration.  Des contrôles réguliers peuvent être facilement faits avec l'outil système  <emphasis>cron</emphasis>.  Ne pas oublier de rediriger les sorties et les erreurs quand un script est utilisé par crontab!</para>
</note>
</sect3>
<sect3 id="sect_09_02_02_03"><title>Contrôle d'une boucle while avec des saisies au clavier</title>
<para>Ce script peut être interrompu par l'usager quand une séquence  <keycap>Ctrl</keycap>+<keycap>C</keycap> est frappée&nbsp;:</para>
<screen>
#!/bin/bash

# Ce script vous apporte sagesse

FORTUNE=/usr/games/fortune

while true; do
echo "Sur quel sujet voulez-vous un conseil&nbsp;?"
cat &lt;&lt; topics
politique
startrek
noyau
sports
excusesbidon
magie
amour
littérature
drogues
éducation
topics

echo
echo -n "Faites votre choix&nbsp;: "
read topic
echo
echo "Conseil gratuit sur le sujet $topic&nbsp;: "
echo
$FORTUNE $topic
echo

done
</screen>
<para>Un document <emphasis>intégré</emphasis> est utilisé pour présenter à l'usager les choix possibles.  Et de nouveau le test <command>true</command> répète les commandes de la liste  <command>CONSEQUENT-COMMANDS</command> encore et encore.</para>
</sect3>
<sect3 id="sect_09_02_02_04"><title>Calcul d'une moyenne</title>
<para>Ce script calcule la moyenne à partir de la saisie utilisateur, qui est testée avant d'être traitée&nbsp;: Si la saisie n'est pas dans un intervalle, un message est affiché.  Si <keycap>q</keycap> est frappée la boucle est abandonnée&nbsp;:</para>
<screen>
#!/bin/bash

# Calcul de la moyenne d'une série de nombres.

SCORE="0"
AVERAGE="0"
SUM="0"
NUM="0"

while true; do

  echo -n "Entrez votre score [0-100%] ('q' pour quitter)&nbsp;: "; read SCORE;

  if (("$SCORE" &lt; "0"))  || (("$SCORE" &gt; "100")); then
    echo "Soyez sérieux.  Banal, essayer encore&nbsp;: "
  elif [ "$SCORE" == "q" ]; then
    echo "Evaluation moyenne&nbsp;: $AVERAGE%."
    break
  else
    SUM=$[$SUM + $SCORE]
    NUM=$[$NUM + 1]
    AVERAGE=$[$SUM / $NUM]
  fi

done

echo "Je quitte."
</screen>
<para>Remarquez que les variables sur les dernières lignes ne sont pas protégées pour pouvoir en faire des calculs.</para>

</sect3>
</sect2>

</sect1>
<sect1 id="sect_09_03"><title>La boucle until</title>
<sect2 id="sect_09_03_01"><title>Qu'est-ce que c'est&nbsp;?</title>
<para>La boucle <command>until</command> est très similaire à <command>while</command>, à part qu'elle s'exécute jusqu'à ce que <command>TEST-COMMAND</command> s'exécute avec succès.  Tant que cette commande échoue, la boucle se poursuit.  La syntaxe est la même que pour la boucle <command>while</command>&nbsp;:</para>
<cmdsynopsis><command>until TEST-COMMAND; do CONSEQUENT-COMMANDS; done</command></cmdsynopsis>
<para>Le statut retourné est le statut d'exécution de la dernière commande exécutée dans la liste <command>CONSEQUENT-COMMANDS</command> ou zéro si aucune n'est exécutée.  <command>TEST-COMMAND</command> peut être toute commande qui peut s'achever avec un statut de succès ou d'échec, et <command>CONSEQUENT-COMMANDS</command> peut être toute commande UNIX, script ou bloc Shell.</para>
<para>Comme nous l'avons déjà expliqué auparavant&nbsp;; le <quote>;</quote> peut être remplacé par un ou plusieurs sauts de lignes quelque soit l'endroit où il apparaît.</para>
</sect2>
<sect2 id="sect_09_03_02"><title>Exemple</title>
<para>Script amélioré de <filename>picturesort.sh</filename> (voir <xref linkend="sect_09_02_02_02" />), qui teste l'espace disque disponible.  Si pas assez d'espace disque disponible, supprimer les images des mois précédents&nbsp;:</para>

<screen>
#!/bin/bash

# Ce script copie les fichiers de mon répertoire racine dans le répertoire du serveur WEB.
# Un nouveau répertoire est créé à chaque heure.
# Si les images prennent trop de place, les plus anciennes sont supprimées.

while true; do 
        DISKFUL=$(df -h $WEBDIR | grep -v File | awk '{print $5 }' | cut -d "%" -f1 -)

        until [ $DISKFUL -ge "90" ]; do 

                DATE=`date +%Y%m%d`
                HOUR=`date +%H`
                mkdir $WEBDIR/"$DATE"
                                                                                
                while [ $HOUR -ne "00" ]; do
                        DESTDIR=$WEBDIR/"$DATE"/"$HOUR"
                        mkdir "$DESTDIR"
                        mv $PICDIR/*.jpg "$DESTDIR"/
                        sleep 3600
                        HOUR=`date +%H`
                done

        DISKFULL=$(df -h $WEBDIR | grep -v File | awk '{ print $5 }' | cut -d "%" -f1 -)
        done

        TOREMOVE=$(find $WEBDIR -type d -a -mtime +30)
        for i in $TOREMOVE; do
                rm -rf "$i";
        done

done
</screen>
<para>Notez l'initialisation des variables <varname>HOUR</varname> et <varname>DISKFULL</varname> et l'emploi d'options avec <command>ls</command> et <command>date</command> afin d'obtenir une liste correcte pour <varname>TOREMOVE</varname>.</para>
</sect2>

</sect1>
<sect1 id="sect_09_04"><title>Redirection d'entrée/sortie et boucles</title>
<sect2 id="sect_09_04_01"><title>Redirection des entrées</title>
<para>Plutôt que de contrôler une boucle en testant le résultat d'une commande ou la saisie utilisateur, vous pouvez spécifier un fichier depuis lequel l'entrée est lue pour contrôler la boucle.  Dans un tel cas, <command>read</command> est souvent la commande de contrôle.  Tant que des lignes sont entrées dans la boucle, les commandes de la boucle sont exécutées.  Dès que toutes les lignes ont été lues la boucle s'arrête.</para>
<para>Parce que la structure de boucle est considérée comme étant une structure de commande (tel que <command>while TEST-COMMAND; do CONSEQUENT-COMMANDS; done</command>), la redirection doit apparaître après l'instruction <command>done</command> afin de respecter la syntaxe.</para>
<cmdsynopsis><command>command &lt; <filename>file</filename></command></cmdsynopsis>
<para>Ce genre de redirection convient aux autres types de boucles.</para>
</sect2>
<sect2 id="sect_09_04_02"><title>Redirection des sorties</title>
<para>Dans l'exemple suivant, le résultat de la commande <command>find</command> est utilisé comme entrée de la commande <command>read</command> afin de contrôler une boucle  <command>while</command>&nbsp;:</para>
<screen>
<prompt>[carol@octarine ~/testdir]</prompt> <command>cat <filename>archiveoldstuff.sh</filename></command>
#!/bin/bash

# Ce script crée un sous-répertoire dans le répertoire courant où sont gardés les
# fichiers supprimés.
# Cela pourrait être adapté à cron (avec modifications) pour être exécuté 
# chaque semaine ou mois.

ARCHIVENR=`date +%Y%m%d`
DESTDIR="$PWD/archive-$ARCHIVENR"

mkdir "$DESTDIR"

# avec guillemets pour récupérer les noms de fichiers ayant des espaces, avec read -d pour la suite 
# fool-proof usage:
find "$PWD" -type f -a -mtime +5 | while read -d $'\000' file

do
gzip "$file"; mv "$file".gz "$DESTDIR"
echo "$file archived"
done
</screen>
<para>Les fichiers sont compressés avant d'être déplacés dans le répertoire d'archive.</para>
</sect2>

</sect1>
<sect1 id="sect_09_05"><title>Break et continue</title>
<sect2 id="sect_09_05_01"><title>L'intégrée break</title>
<para>L'instruction <command>break</command> est employée pour quitter la boucle en cours avant sa fin normale.  Ceci peut être nécessaire quand vous ne savez pas à l'avance combien de fois la boucle devra s'exécuter, par exemple parce qu'elle est dépendante de la saisie utilisateur.</para>
<para>Cet exemple montre une boucle <command>while</command> qui peut être interrompue.  Ceci est une version légèrement améliorée du script <filename>wisdom.sh</filename> de la <xref linkend="sect_09_02_02_03" />.</para>
<screen>
#!/bin/bash

# Ce script vous apporte sagesse
# Vous pouvez maintenant quitter d'une façon décente.

FORTUNE=/usr/games/fortune

while true; do
echo "Sur quel sujet voulez-vous un conseil?"
echo "1.  politique"
echo "2.  startrek"
echo "3.  noyau"
echo "4.  sports"
echo "5.  excusesbidon"
echo "6.  magie"
echo "7.  amour"
echo "8.  littérature"
echo "9.  drogues"
echo "10. éducation"
echo

echo -n "Entrez votre choix, ou 0 pour quitter&nbsp;: "
read choice
echo

case $choice in
     1)
     $FORTUNE politique
     ;;
     2)
     $FORTUNE startrek
     ;;
     3)
     $FORTUNE noyau
     ;;
     4)
     echo "Le sport est une perte d'argent, d'énergie et de temps."
     echo "Retournez à votre clavier."
     echo -e "\t\t\t\t -- \"Unhealthy is my middle name\" Soggie."
     ;;
     5)
     $FORTUNE excusesbidon
     ;;
     6)
     $FORTUNE magie
     ;;
     7)
     $FORTUNE amour
     ;;
     8)
     $FORTUNE littérature
     ;;
     9)
     $FORTUNE drogues
     ;;
     10)
     $FORTUNE éducation
     ;;
     0)
     echo "OK, au revoir!"
     break
     ;;
     *)
     echo "Ceci n'est pas un choix valide, taper un chiffre entre 0 et 10."
     ;;
esac  
done
</screen>
<para>Mémorisez que <command>break</command> quitte la boucle, pas le script.  Ceci se voit en ajoutant une commande <command>echo</command> à la fin du script.  Cet <command>echo</command> sera aussi exécuté à la saisie qui provoque l'exécution du <command>break</command> (quand l'usager frappe <quote>0</quote>).</para>
<para>Dans les boucles imbriquées, <command>break</command> autorise la spécification de la boucle dont il faut sortir.  Voir les pages Bash <command>info</command> pour plus de détails.</para>
</sect2>
<sect2 id="sect_09_05_02"><title>L'intégrée continue</title>
<para>L'instruction <command>continue</command> repart à l'itération d'une boucle  <command>for</command>, <command>while</command>, <command>until</command> ou <command>select</command>.</para>
<para>Quand elle est utilisée dans une boucle <command>for</command> la variable de contrôle prend la valeur de l'élément suivant de la liste.  Quand elle est utilisée dans une structure <command>while</command> ou <command>until</command> à contrario, l'exécution repart avec la première commande de  <command>TEST-COMMAND</command> en haut de la boucle.</para>
</sect2>
<sect2 id="sect_09_05_03"><title>Exemples</title>
<para>Dans les exemples suivants, les noms de fichiers sont convertis en minuscule.  Si la conversion n'est pas nécessaire, une instruction <command>continue</command>  recommence l'exécution de la boucle.  Ces commandes ne consomment pas trop de ressources système, et la plupart du temps, un effet similaire peut être obtenu avec <command>sed</command> et <command>awk</command>.  Cependant, il est utile de connaître ces structures pour l'exécution de travaux coûteux, cela ne serait sans doute pas nécessaire si les tests étaient positionnés aux bons endroits dans le script, en partageant les ressources système.</para>
<screen>
<prompt>[carol@octarine ~/test]</prompt> <command>cat <filename>tolower.sh</filename></command>
#!/bin/bash

# Ce script convertit tous les noms de fichiers contenant des majuscules en nom de fichier contenant que des minuscules

LIST="$(ls)"

for name in "$LIST"; do

if [[ "$name" != *[[:upper:]]* ]]; then
continue
fi

ORIG="$name"
NEW=`echo $name | tr 'A-Z' 'a-z'`

mv "$ORIG" "$NEW"
echo "nouveau nom pour $ORIG est $NEW"
done
</screen>
<para>Ce script a au moins un inconvénient&nbsp;: il écrase les fichiers existants.  L'option <option>noclobber</option> de Bash est seulement utile quand intervient des redirections.  L'option <option>-b</option> de la commande <command>mv</command> offre plus de sécurité, mais seulement dans le cas de réécriture accidentelle, comme le montre ce test&nbsp;:</para>
<screen>
<prompt>[carol@octarine ~/test]</prompt> <command>rm <filename>*</filename></command>

<prompt>[carol@octarine ~/test]</prompt> <command>touch <filename>test Test TEST</filename></command>

<prompt>[carol@octarine ~/test]</prompt> <command>bash <option>-x</option> <filename>tolower.sh</filename></command>
++ ls
+ LIST=test
Test
TEST
+ [[ test != *[[:upper:]]* ]]
+ continue
+ [[ Test != *[[:upper:]]* ]]
+ ORIG=Test
++ echo Test
++ tr A-Z a-z
+ NEW=test
+ mv -b Test test
+ echo 'nouveau nom pour Test est test'
new name for Test is test
+ [[ TEST != *[[:upper:]]* ]]
+ ORIG=TEST
++ echo TEST
++ tr A-Z a-z
+ NEW=test
+ mv -b TEST test
+ echo 'nouveau nom pour TEST est test'
nouveau nom pour TEST est test

<prompt>[carol@octarine ~/test]</prompt> <command>ls <option>-a</option></command>
./  ../  test  test~
</screen>
<para><command>tr</command> fait parti du paquet <emphasis>textutils</emphasis>, il peut effectuer toute sorte de transformation de caractère.</para>
</sect2>

</sect1>
<sect1 id="sect_09_06"><title>Faire des menus avec l'intégrée select</title>
<sect2 id="sect_09_06_01"><title>Généralité</title>
<sect3 id="sect_09_06_01_01"><title>Emploi de select</title>
<para>La structure <command>select</command> permet la génération facile de menus.  La syntaxe est assez similaire à celle de la boucle <command>for</command>&nbsp;:</para>
<cmdsynopsis><command>select <varname>WORD</varname> [in <varname>LIST</varname>]; do RESPECTIVE-COMMANDS; done</command></cmdsynopsis>
<para><varname>LIST</varname> est interprété, ce qui génère une liste d'éléments. L'expansion est affichée sur le standard d'erreurs&nbsp;; chaque élément est précédé d'un numéro.  Si <command>in <varname>LIST</varname></command> est absent, les paramètres positionnels sont affichés, comme si <command>in <varname>$@</varname></command> avait été spécifié.  <varname>LIST</varname> est affiché une fois seulement.</para>
<para>A l'affichage des tous les éléments, l'invite <varname>PS3</varname> est affichée et une ligne du standard d'entrée est lue.  Si la ligne consiste en un nombre qui correspond à un des éléments, la valeur de <varname>WORD</varname> est définie avec le nom de cet élément. Si la ligne est vide, les éléments et l'invite <varname>PS3</varname> sont réaffichés.  Si un caractère <emphasis>EOF</emphasis> (End Of File) est lu, la boucle se termine.  Parce que la plupart des usagers n'ont pas idée de la combinaison de touches pour EOF, il est plus convivial d'ajouter une commande <command>break</command> comme l'un des éléments.  Tout autre valeur issue de la ligne lue définira <varname>WORD</varname> comme une chaîne nulle.</para>
<para>La ligne lue est mémorisée dans la variable <varname>REPLY</varname>.</para>
<para>Les <command>RESPECTIVE-COMMANDS</command> sont exécutées après chaque choix valide jusqu'à ce que le nombre représentant le <command>break</command> soit lu.  Ce qui fait quitter la boucle.</para>
</sect3>
<sect3 id="sect_09_06_01_02"><title>Exemples</title>
<para>Voici un exemple très simple, mais comme vous le constatez, il n'est pas très convivial&nbsp;:</para>
<screen>
<prompt>[carol@octarine testdir]</prompt> <command>cat <filename>private.sh</filename></command>
#!/bin/bash

echo "Ce script peut mettre un accès privé à tout fichier de ce répertoire."
echo "Entrez le numéro du fichier que vous voulez protéger&nbsp;:"

select FILENAME in *;
do
     echo "Vous avez sélectionné $FILENAME ($REPLY), il est maintenant accessible que par vous."
     chmod go-rwx "$FILENAME"
done

<prompt>[carol@octarine testdir]</prompt> <command>./private.sh</command>
Ce script peut mettre un accès privé à tout fichier de ce répertoire.
Entrez le numéro du fichier que vous voulez protéger&nbsp;:
1) archive-20030129
2) bash
3) private.sh
#? 1
Vous avez sélectionné archive-20030129 (1)
#?
</screen>
<para>Déclarer l'invite <varname>PS3</varname> et ajouter la possibilité de quitter l'améliore&nbsp;:</para>
<screen>
#!/bin/bash

echo "Ce script peut mettre un accès privé à tout fichier de ce répertoire."
echo "Entrez le numéro du fichier que vous voulez protéger"

PS3="Votre choix&nbsp;: "
QUIT="QUITTER CE PROGRAMME - Je me sens plus en confiance là."
touch "$QUIT"

select FILENAME in *;
do
  case $FILENAME in
        "$QUIT")
          echo "Fin."
          break
          ;;
        *)
          echo "Vous avez sélectionné $FILENAME ($REPLY)"
          chmod go-rwx "$FILENAME"
          ;;
  esac
done
rm "$QUIT"
</screen>
</sect3>
</sect2>
<sect2 id="sect_09_06_02"><title>Sous-menus</title>
<para>Toute instruction dans une structure <command>select</command> peut être un autre <command>select</command> ce qui autorise un(des) sous-menu(s) dans un menu.</para>
<para>Par défaut la variable <varname>PS3</varname> n'est pas changée dans une boucle <command>select</command> imbriquée.  Si vous voulez une invite différente dans le sous-menu, assurez-vous de la définir aux bons moments.</para>
</sect2>

</sect1>
<sect1 id="sect_09_07"><title>L'intégrée shift</title>
<sect2 id="sect_09_07_01"><title>Qu'est-ce qu'elle fait&nbsp;?</title>
<para>La commande <command>shift</command> est l'une des intégrées Bourne Shell qui est fournie par Bash.  Cette commande prend un paramètre, un nombre.  Les paramètres positionnels sont décalés sur la gauche le nombre de fois  <emphasis>N</emphasis>.  Les paramètres positionnels de <varname>N+1</varname> à <varname>$#</varname> sont renommés avec les noms de variable <varname>$1</varname> à <varname>$# - N+1</varname>.</para>
<para>Disons que nous avons une commande qui a 10 paramètres, et N vaut 4, alors <varname>$4</varname> devient <varname>$1</varname>, <varname>$5</varname> devient <varname>$2</varname> et ainsi de suite.  <varname>$10</varname> devient <varname>$7</varname> et les anciens<varname>$1</varname>, <varname>$2</varname> et <varname>$3</varname> sont éliminés.</para>
<para>Si N est zéro ou supérieur à <varname>$#</varname> les paramètres positionnels ne sont pas changés (le nombre total de paramètres, voir <xref linkend="sect_07_02_01_02" />) et la commande n'a pas d'effet.  Si N est absent, il est considéré valant 1.  Le code renvoyé est zéro à moins que N soit supérieur à <varname>$#</varname> ou inférieur à zéro, sinon il est différent de zéro.</para>
</sect2>
<sect2 id="sect_09_07_02"><title>Exemples</title>
<para>Une instruction shift est typiquement employée quand le nombre de paramètres d'une commande n'est pas connu par avance, par exemple quand l'usager peut donner autant de paramètres qu'il le souhaite.  Dans de tels cas, les paramètres sont généralement traités dans une boucle <command>while</command> avec un test sur <command>(( $# ))</command>.  Ce test est vrai tant que le nombre de paramètres est supérieur à zéro.  La variable <varname>$1</varname> et l'instruction <command>shift</command> traite chaque paramètre.  Le nombre de paramètres est diminué chaque fois que <command>shift</command> est exécutée et finalement devient zéro, sur quoi la boucle <command>while</command> s'arrête.</para>
<para>L'exemple suivant, <filename>cleanup.sh</filename>, emploie l'instruction  <command>shift</command> pour traiter chaque fichier d'une liste générée par <command>find</command>&nbsp;:</para>
<screen>
#!/bin/bash

# Ce script peut éliminer des fichiers qui n'ont pas été accédés depuis plus de 365 jours.

USAGE="Utilisation&nbsp;: $0 dir1 dir2 dir3 ... dirN"

if [ "$#" == "0" ]; then
        echo "$USAGE"
        exit 1
fi

while (( "$#" )); do

if [[ $(ls "$1") == "" ]]; then 
        echo "Répertoire vide, rien à faire."
  else 
        find "$1" -type f -a -atime +365 -exec rm -i {} \;
fi

shift

done
</screen>
<note><title>-exec versus xargs</title>
<para>La commande <command>find</command> ci-dessus peut être remplacée par ce qui suit&nbsp;:</para>
<cmdsynopsis><command>find <option>options</option> | xargs [commandes_a_executer_sur_fichiers_trouves]</command></cmdsynopsis>
<para>La commande <command>xargs</command> construit et exécute des lignes de commandes depuis l'entrée standard.  Ceci présente l'avantage que la ligne de commande est renseignée jusqu'à ce que la limite du système soit atteinte.  Seulement à ce moment la commande à exécuter sera lancée, dans l'exemple ci-dessus ce serait <command>rm</command>.  Si il y a plus de paramètres, une nouvelle ligne de commande sera utilisée, jusqu'à ce qu'elle soit elle aussi pleine ou jusqu'à ce qu'il n'y ait plus de paramètres.  La même chose avec <command>find <option>-exec</option></command> appelle la commande à exécuter sur chaque fichier trouvé.  Donc, l'usage de <command>xargs</command> accélère grandement l'exécution des scripts et améliore les performances de votre machine.</para>
</note>
<para>Dans l'exemple suivant, nous avons modifié le script de la <xref linkend="sect_08_02_04_04" /> afin qu'il accepte de multiples paquets à installer d'un coup&nbsp;:</para>
<screen>
#!/bin/bash
if [ $# -lt 1 ]; then
        echo "Utilisation&nbsp;: $0 package(s)"
        exit 1
fi
while (($#)); do
        yum install "$1" &lt;&lt; CONFIRM
y
CONFIRM
shift
done
</screen>
</sect2>

</sect1>
<sect1 id="sect_09_08"><title>Résumé</title>
<para>Dans ce chapitre, nous avons vu comment les commandes répétitives peuvent être incorporées dans des structures de boucles.  La plupart des boucles sont construites avec <command>for</command>, <command>while</command> ou <command>until</command> ou une combinaison de ces commandes.  La boucle <command>for</command> exécute une tâche un nombre défini de fois.  Si vous ne savez pas combien de fois une commande devrait s'exécuter, employez plutôt <command>until</command> ou <command>while</command> pour spécifier quand la boucle devrait s'arrêter.</para>
<para>Les boucles peuvent être interrompues ou réitérées avec <command>break</command> et <command>continue</command>.</para>
<para>Un fichier peut être utilisé comme entrée pour une boucle qui a un opérateur de redirection, les boucles peuvent aussi lire les résultats de commandes qui sont fournis à la boucle par un tube.</para>
<para>La structure <command>select</command> est employée pour afficher des menus dans les scripts interactifs.  Le décalage des paramètres d'une ligne de commande peut être fait avec  <command>shift</command>.</para>

</sect1>
<sect1 id="sect_09_09"><title>Exercices</title>
<para>Rappelez-vous&nbsp;: quand vous écrivez des scripts, travaillez par étapes et les tester avant de les incorporer à votre script.</para>

<orderedlist>
<listitem><para>Créer un script qui fera une copie (récursive) des fichiers dans <filename>/etc</filename> de sorte qu'un administrateur système débutant puisse les éditer sans crainte.</para></listitem>
<listitem><para>Ecrire un script qui prendra exactement un paramètre, le nom d'un répertoire.  Si le nombre de paramètres est supérieur ou inférieur à 1, afficher le message d'utilisation.  Si l'argument n'est pas un répertoire, afficher un autre message.  Pour le répertoire donné, afficher les 5 fichiers les plus gros et les 5 fichiers le plus récemment modifiés.</para></listitem>
<listitem><para>Pouvez-vous expliquer pourquoi il est si important de mettre les variables entre guillemets dans l'exemple de la <xref linkend="sect_09_04_02" />&nbsp;?</para></listitem>
<listitem><para>Ecrivez un script similaire à celui de la <xref linkend="sect_09_05_01" />, mais pensez à un moyen de quitter après que l'usager ait effectué 3 boucles.</para></listitem>
<listitem><para>Penser à une meilleur solution que <command>move <option>-b</option></command> pour le script de la <xref linkend="sect_09_05_03" /> pour éviter d'écraser les fichiers existants.  Par exemple, tester si un fichier existe ou pas.  Ne faites pas de travail inutile!</para></listitem>
<listitem><para>Réécrire le script <filename>whichdaemon.sh</filename> de la <xref linkend="sect_07_02_04" />, de sorte qu'il&nbsp;:</para>

<itemizedlist>
<listitem><para>Affiche une liste des serveurs à contrôler, tel que Apache, le serveur SSH, le démon NTP, un démon nom, un démon d'administration, etc.</para></listitem>
<listitem><para>Pour chaque choix l'utilisateur peut afficher des informations importantes, telles que le nom du serveur WEB, les informations de trace NTP, etc.</para></listitem>
<listitem><para>Optionnellement, prévoir une possibilité pour les utilisateurs de contrôler d'autres serveurs que ceux listés.  Dans de tels cas, vérifiez que au moins le processus en question tourne.</para></listitem>
<listitem><para>Revoir les scripts de la <xref linkend="sect_09_02_02_04" />.  Remarquez comment les caractères entrés autre que <keycap>q</keycap> sont traités.  Réécrire ce script afin qu'il affiche un message si des caractères sont saisis.</para>
</listitem>
</itemizedlist>
</listitem>
</orderedlist>

</sect1>
</chapter>