Extension du tutorial shell --Bap.

This commit is contained in:
meles 2005-06-03 13:56:21 +00:00
parent 74df6e2ff8
commit 95fdd248b3
3 changed files with 822 additions and 176 deletions

633
unix/shell/boucle.tml Normal file
View file

@ -0,0 +1,633 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html
PUBLIC "-//ENS/Tuteurs//DTD TML 1//EN"
"tuteurs://DTD/tml.dtd">
<html>
<head>
<title>Boucles</title>
</head>
<body>
<h1>Structures de contrôle</h1>
<p>
Une fois que vous avez compris qu'un script n'est rien d'autre qu'une
suite de commandes, vous avez fait un grand pas&nbsp;; chaque script
consiste à poser des rails, il ne reste plus à l'utilisateur qu'à les
parcourir.
</p>
<p>Toutefois, avoir posé les rails ne suffit pas toujours&nbsp;: il peut
importer de disposer de mécanismes d'aiguillage. Les boucles vous
permettront de réaliser ces mécanismes d'aiguillage, qui donneront à vos
scripts souplesse et efficacité. </p>
<h2>Examiner une condition&nbsp;: <code>if</code> et <code>case</code></h2>
<h3>La boucle <code>if</code></h3>
<h4>Description</h4>
<p>
Robert le jardinier programme un hachoir en shell, et veut en interdire
l'accès à tout autre utilisateur que lui-même, afin de protéger le petit
Émile de ce dangereux instrument. Le programme <code>hachoir</code> se
pose la question suivante&nbsp;: est-ce que <code>$USER</code> vaut
«&nbsp;Robert&nbsp;»&nbsp;?
</p>
<ul>
<li> Si oui, alors le hachoir doit se mettre en marche&nbsp;;</li>
<li> si non, alors le hachoir doit refuser de se mettre en
marche.</li>
</ul>
<p>
C'est pour les structures de type «&nbsp;si... alors... ou sinon&nbsp;»,
la commande <code>if</code> a été faite. Elle est constituée de cinq
termes, dont trois sont obligatoires&nbsp;:</p>
<ol>
<li> <code>if</code> (si), qui marque la condition à remplir&nbsp;;
</li>
<li> <code>then</code> (alors), qui marque les conséquences si la
condition est remplie&nbsp;; </li>
<li> <code>elif</code> (contraction de <em>else if</em>, qui signifie
«&nbsp;sinon, si...&nbsp;»), qui est <em>facultatif</em> et marque une
condition alternative&nbsp;;</li>
<li> <code>else</code> (sinon), qui est <em>facultatif</em> et marque le
comportement à adopter si la condition n'est pas remplie&nbsp;; </li>
<li> <code>fi</code> (<code>if</code> à l'envers), qui marque la fin de
la boucle.</li>
</ol>
<p>On peut donc taper&nbsp;:</p>
<pre>if <em>commande</em> ; then <em>commandes</em> ; else <em>commandes</em> ; fi</pre>
<p class="continue"> ou bien (car le point et virgule équivaut à un saut
de ligne)&nbsp;:</p>
<pre>if <em>condition</em>
then <em>commandes</em>
elif <em>condition</em>
then <em>commandes</em>
else <em>commandes</em>
fi</pre>
<h4>Exemples</h4>
<h5>Exemple 1</h5>
<p>
Par exemple, pour le hachoir de Robert le jardinier, on aura&nbsp;:
</p>
<pre>
if test $USER = Robert
then echo &quot;Le hachoir va se mettre en marche.&quot;
<em>mettre en marche le hachoir</em>
else echo &quot;Quand tu seras grand, $USER.&quot;
echo &quot;Et fais bien attention en traversant la rue.&quot;
fi</pre>
<h5>Observations</h5>
<p> Comme vous pouvez le constater, <strong>à la suite des commandes
<code>then</code> et <code>else</code>, vous pouvez mettre autant de
commandes que vous le voulez</strong>. Le shell exécute tout ce qu'il
trouve jusqu'à la prochaine instruction.</p>
<p> Par exemple, si la condition indiquée par <code>if</code> est
remplie, le shell va exécuter tout ce qui suit <code>then</code> jusqu'à
ce qu'il trouve l'une de ces instructions&nbsp;:</p>
<ul>
<li> <code>elif</code>,</li>
<li> <code>else</code> ou</li>
<li> <code>fi</code>.</li>
</ul>
<h5>Exemple 2</h5>
<p>Autre exemple&nbsp;:</p>
<pre>
#!/bin/sh
# Fichier &quot;mon-pwd&quot;
if test $PWD = $HOME
then echo &quot;Vous êtes dans votre répertoire d'accueil.&quot;
elif test $PWD = $HOME/bin
then echo &quot;Vous êtes dans votre répertoire d'exécutables.&quot;
elif test $PWD = $HOME/bin/solaris
then echo &quot;Vous êtes dans votre répertoire d'exécutables pour Solaris.&quot;
else echo 'Vous êtes dans un répertoire quelconque, qui n'est ni $HOME,'
echo 'ni $HOME/bin, ni $HOME/bin/solaris. '"Vous êtes dans $PWD."
fi
</pre>
<h5>Observations</h5>
<ol>
<li> <strong>Le nombre de <code>elif</code> successifs est
illimité</strong>, alors qu'il ne peut (et c'est logique) y avoir qu'une
seule condition <code>if</code>, une seule instruction <code>else</code>
et une seule instruction <code>fi</code> dans une boucle. </li>
<li> Remarquez les dernières lignes du script&nbsp;: <code>$HOME</code>
est cité dans des guillemets simples, alors que <code>$PWD</code> est
cité entre guillemets doubles. Par conséquent, $HOME ne sera pas
interprété (le mot «&nbsp;$HOME&nbsp;» apparaîtra tel quel), tandis que
<code>$PWD</code> sera interprété (il sera remplacé, par exemple, par
<code>/home/toto</code>).</li>
</ol>
<h3>La boucle <code>case</code></h3>
<h4>Quand préférer <code>case</code> à <code>if</code>&nbsp;?</h4>
<p>La boucle <code>if</code> est très pratique, mais devient rapidement
illisible lorsqu'un aiguillage offre plusieurs sorties, et que l'on doit
répéter une condition pusieurs fois sous des formes à peine
différentes. Typiquement, un programme comme celui-ci est pénible à
écrire, à lire et à débuguer&nbsp;:</p>
<pre>
if test $PWD = $HOME
then echo &quot;Vous êtes dans votre répertoire d'accueil.&quot;
elif test $PWD = $HOME/bin
then echo &quot;Vous êtes dans votre répertoire d'exécutables.&quot;
elif test $PWD = $HOME/bin/solaris
then echo &quot;Vous êtes dans votre répertoire d'exécutables pour Solaris.&quot;
elif test $PWD = $HOME/prive
then echo &quot;Vous êtes dans votre répertoire privé.&quot;
else echo 'Vous êtes dans un répertoire quelconque, qui n'est ni $HOME,'
echo 'ni $HOME/bin, ni $HOME/bin/solaris. '&quot;Vous êtes dans $PWD.&quot;
fi
</pre>
<p>Pourquoi ce programme est-il pénible à écrire, à lire et à
débuguer&nbsp;?</p>
<ol>
<li> parce qu'on a du mal, en le survolant simplement, à saisir
immédiatement son enjeu&nbsp;; </li>
<li> parce qu'il y a une redondance de la séquence «&nbsp;<code>test
$PWD =</code>&nbsp;»&nbsp;; et qui dit redondance dit possibilité de
faute de frappe&nbsp;&ndash; erreur particulièrement difficile à repérer
à l'&oelig;il nu&nbsp;;</li>
<li> parce que les différentes conditions ne sont pas mises sur le même
plan (<code>if</code> n'est pas, dans la boucle, sur le même plan que
<code>elif</code>, alors que logiquement les deux instructions le
sont), ce qui nuit à la lisibilité.</li>
</ol>
<p>La boucle <code>case</code> entend remédier à ces inconvénients. Il
s'agit tout simplement de la simplification d'un usage fréquent de la
structure <code>if</code>. On peut récrire le programme suivant avec
<code>case</code>&nbsp;:</p>
<pre>
case &quot;$PWD&quot; in
&quot;$HOME&quot;) echo &quot;Vous êtes dans votre répertoire d'accueil.&quot;;;
&quot;$HOME/bin&quot;) echo &quot;Vous êtes dans votre répertoire d'exécutables.&quot;;;
&quot;$HOME/bin/solaris&quot;) echo &quot;Vous êtes dans votre répertoire d'exécutables pour Solaris.&quot;;;
&quot;$HOME/prive&quot;) echo &quot;Vous êtes dans votre répertoire privé.&quot;;;
&quot;*&quot;) echo 'Vous êtes dans un répertoire quelconque, qui n'est ni $HOME,'
echo 'ni $HOME/bin, ni $HOME/bin/solaris. '&quot;Vous êtes dans $PWD.&quot;;;
esac
</pre>
<p>Le gain de lisibilité est flagrant (si si, c'est flagrant, je vous
assure)&nbsp;:</p>
<ol>
<li> la structure de la boucle et son enjeu sont
transparents&nbsp;;</li>
<li> la variable que l'on teste n'est citée qu'une seule fois, au lieu
d'être répétée à chaque instruction <code>elif</code>&nbsp;;</li>
<li>les valeurs possibles sont citées à la suite, et toutes sur le même
plan.</li>
</ol>
<h4>Syntaxe de <code>case</code></h4>
<p>La boucle <code>case</code> adopte la syntaxe suivante&nbsp;:</p>
<pre>
case <em>chaîne</em> in
<em>motif</em>) <em>commandes</em> ;;
<em>motif</em>) <em>commandes</em> ;;
esac
</pre>
<p>
La boucle <code>case</code> commence par évaluer la
<em>chaîne</em>. Ensuite, elle va lire chaque <em>motif</em>. Enfin, et
dès qu'une <em>chaîne</em> correspond au <em>motif</em>, elle exécute
les <em>commandes</em> appropriées. En anglais, <em>case</em> signifie
«&nbsp;cas&nbsp;»&nbsp;; cette structure de contrôle permet en effet de
réagir adéquatement aux différents «&nbsp;cas de figure&nbsp;»
possibles.</p>
<p>
Vous observerez que&nbsp;:
</p>
<ul>
<li> le <em>motif</em> est séparé des commandes par une parenthèse
fermante (qui n'est ouverte nulle part&nbsp;!)&nbsp;;</li>
<li> la série des <em>commandes</em> est close par deux points et
virgules successifs&nbsp;(«&nbsp;;;&nbsp;»)&nbsp;; </li>
<li> la boucle <code>case</code> est close par l'instruction
<code>esac</code>, qui n'est autre que le mot <code>case</code> à
l'envers (de même que la boucle <code>if</code> est terminée par
l'instruction&nbsp;<code>fi</code>).</li>
</ul>
<p>
Un <em>motif</em> (<em>pattern</em>) est un mot contenant éventuellement
les constructions <code>*</code>, <code>?</code>, <code>[a-d]</code>,
avec la même signification que pour les raccourcis dans les noms de
fichiers (les <em>jokers</em>). Exemple&nbsp;:
</p>
<pre>
case $var in
[0-9]*) echo &quot;$var est un nombre.&quot;;;
[a-zA-Z]*) echo &quot;$var est un mot.&quot;;;
*) echo &quot;$var n'est ni un nombre ni un mot.&quot;;;
esac</pre>
<h4>L'aiguillage commun à plusieurs motifs</h4>
<p>Que faire si je veux aiguiller plusieurs motifs différents de la même
façon&nbsp;? Le premier réflexe est de répéter, presque à l'identique,
des lignes, de la façon suivante&nbsp;:</p>
<pre>
#!/bin/sh
# Fichier &quot;canards&quot;
echo &quot;Quel est ton nom&nbsp;?&quot;
read nom
case $nom in <strong>
&quot;Riri&quot;) echo &quot;Ton oncle s'appelle Donald, Riri.&quot;;;
&quot;Fifi&quot;) echo &quot;Ton oncle s'appelle Donald, Fifi.&quot;;;
&quot;Loulou&quot;) echo &quot;Ton oncle s'appelle Donald, Loulou.&quot;;;
</strong>&quot;*&quot;) echo &quot;Tu n'es pas un neveu de Donald.&quot;;;
esac
</pre>
<p>
Mais <strong>il n'est jamais bon de répéter manuellement des lignes de
programmation</strong>, car c'est générateur de fautes. Il faut toujours
chercher des raccourcis, et ne se répéter qu'en dernier recours. On
utilisera donc le caractère «&nbsp;|&nbsp;», qui permet de mettre sur le
même plan différents motifs. On pourra donc écrire&nbsp;:
</p>
<pre>
#!/bin/sh
# Fichier &quot;canards&quot;
echo &quot;Quel est ton nom&nbsp;?&quot;
read nom
case $nom in
<strong>&quot;Riri&quot;|&quot;Fifi&quot;|&quot;Loulou&quot;</strong>) echo &quot;Ton oncle s'appelle Donald, <strong>$nom</strong>.&quot;;;
&quot;*&quot;) echo &quot;Tu n'es pas un neveu de Donald.&quot;;;
esac
</pre>
<p class="continue">On gagne ainsi en concision, et on évite bien des
fautes d'inattention, qui sont particulièrement coriaces à débuguer.
</p>
<div class="attention">
<p>La boucle <code>case</code> exécute les commandes correspondant au
<strong>premier</strong> motif correct, et ne parcourt pas les motifs
suivants si elle en a trouvé un. Par conséquent, <strong>si le dernier
motif est «&nbsp;*&nbsp;», cela revient à l'instruction
<code>else</code> dans la boucle <code>if</code></strong>.</p>
</div>
<h2>Répéter une procédure&nbsp;: <code>while</code>, <code>until</code>
et&nbsp;<code>for</code></h2>
<p>Les structures <code>if</code> et <code>case</code> sont des
structures d'aiguillage&nbsp;; les structures <code>while</code>,
<code>until</code> et <code>for</code> sont proprement des
boucles. C'est-à-dire qu'elles soumettent à une condition déterminée la
répétition (ou non) d'une suite de commandes.</p>
<h3>La boucle <code>while</code></h3>
<p>
La boucle <code>while</code> exécute les <em>commandes</em> de manière
répétée tant que la première <em>commande</em> réussit&nbsp;; en
anglais, <em>while</em> signifie «&nbsp;tant que&nbsp;».</p>
<h4>Syntaxe de <code>while</code></h4>
<p>La boucle <code>while</code> adopte la syntaxe suivante&nbsp;:</p>
<pre>while <em>condition</em>
do <em>commandes</em>
done</pre>
<p><strong>Après l'instruction <code>do</code>, vous pouvez mettre
autant de commandes que vous le désirez</strong>, suivies de retours
chariot ou bien de points et virgules («&nbsp;;&nbsp;»). Le shell
exécutera tout ce qu'il trouve, jusqu'à ce qu'il tombe sur l'instruction
<code>done</code>. </p>
<h4>Exemple</h4>
<pre>
echo &quot;Tapez le mot de passe&nbsp;:&quot;
read password
while test &quot;$password&quot; != <em>mot de passe correct</em>
do echo &quot;Mot de passe incorrect.&quot;
echo &quot;Tapez le mot de passe&quot;
read password
done
echo &quot;Mot de passe correct.&quot;
echo &quot;Bienvenue sur les ordinateurs secrets du Pentagone.&quot;
</pre>
<p class="continue"> Les lignes qui suivent la boucle <code>while</code>
seront exécutées si, et seulement si, la condition est remplie,
c'est-à-dire si l'utilisateur tape le bon mot de passe. (En pratique, il
est très fortement déconseillé de taper un mot de passe en clair dans un
script, car rien n'est plus facile que de l'éditer&nbsp;!...).
</p>
<h4>Un usage concret de <code>while</code></h4>
<p>Supposons que vous vouliez créer cinquante fichiers sous la forme
<code>fichier1</code>, <code>fichier2</code>, <code>fichier3</code>,
etc. Un petit script vous fera ça plus vite que cinquante lignes de
commande. </p>
<pre>
#!/bin/sh
# Fichier &quot;50fichiers&quot;
numero=1
while test $numero != 51
do
# la commande &quot;touch&quot; permet de créer un fichier vide :
touch fichier&quot;$numero&quot;
# cette commande ajoute 1 à la variable &quot;numero&quot; :
numero=$(($numero + 1))
done
</pre>
<p class="continue">Avec ce script, la variable <code>numero</code> est
d'abord créée avec la valeur&nbsp;1. Ensuite, le shell examine si la
valeur de <code>numero</code> est différente de&nbsp;51&nbsp;; c'est le
cas. Par conséquent, le shell exécute les commandes suivantes&nbsp;: il
crée un fichier <code>fichier1</code>, puis ajoute&nbsp;1 à la variable
<code>numero</code>, qui vaut donc&nbsp;2. Et c'est reparti pour un
tour&nbsp;! Le shell examine si la valeur de <code>numero</code>,
c'est-à-dire&nbsp;2, est différente de&nbsp;51, etc.
</p>
<p>Petit conseil pour faire les choses le plus proprement
possible&nbsp;: dans le script qui précède,
remplacez&nbsp;«&nbsp;51&nbsp;» par une variable&nbsp;: le script sera
beaucoup plus lisible. Exemple&nbsp;:</p>
<pre>
#!/bin/sh
# Fichier &quot;50fichiers&quot;
numero=1
<strong>limite=51</strong>
while test $numero != <strong>$limite</strong>
do
# la commande &quot;touch&quot; permet de créer un fichier vide :
touch fichier&quot;$numero&quot;
# cette commande ajoute 1 à la variable &quot;numero&quot; :
numero=$(($numero + 1))
done
</pre>
<h3>La boucle <code>until</code></h3>
<p>
<em>Until</em> signifie «&nbsp;jusqu'à ce que&nbsp;» en anglais. Cette
boucle est le pendant de la boucle <code>while</code>, à ceci près que
<strong>la <em>condition</em> ne détermine pas la cause de la répétition
de la boucle, mais celle de son interruption</strong>.</p>
<h4>Syntaxe de <code>until</code></h4>
<pre>
until <em>condition</em>
do <em>commandes</em>
done
</pre>
<h4>Exemple</h4>
<p> On aura ainsi&nbsp;: </p>
<pre>
echo &quot;Tapez le mot de passe&nbsp;:&quot;
read password
<strong>until test $password = <em>mot de passe correct</em></strong>
do echo &quot;Mot de passe incorrect.&quot;
echo &quot;Tapez le mot de passe&quot;
read password
done
echo &quot;Mot de passe correct.&quot;
echo &quot;Bienvenue sur les ordinateurs secrets de la NASA.&quot;
</pre>
<p> Il revient en effet au même de répéter une suite de commandes
«&nbsp;<em>tant que</em> le mot de passe est
<strong>incorrect</strong>&nbsp;» ou bien «&nbsp;<em>jusqu'à ce que</em>
le mot de passe soit <strong>correct</strong>&nbsp;», car un mot de
passe déterminé ne peut être que correct ou incorrect.
</p>
<h4><code>while</code> ou <code>until</code>&nbsp;?</h4>
<p>Vous allez donc me demander que préférer entre <code>while</code> et
<code>until</code>, si les deux sont si souvent équivalentes&nbsp;? Eh
bien, celle dont la condition sera la plus facile à écrire, à lire et à
débuguer. Si, comme dans notre exemple, elles sont aussi simples
l'une que l'autre, choisissez votre préférée ou prenez-en une au hasard.
</p>
<h3>La boucle <code>for</code></h3>
<p>
La boucle <code>for</code> affecte successivement une variable chaque
chaîne de caractères trouvée dans une <em>liste de chaînes</em>, et
exécute les <em>commandes</em> une fois pour chaque chaîne.
</p>
<h4>Syntaxe de <code>for</code></h4>
<pre>for <em>var</em> in <em>liste de chaînes</em>
do <em>commandes</em>
done</pre>
<h4>Exemple</h4>
<pre>
#!/bin/sh
# Fichier &quot;liste&quot;
for element in *
do echo &quot;$element&quot;
done
</pre>
<p class="continue">
affiche les noms de tous les fichiers du répertoire courant, un par ligne.
</p>
<p>Concrètement, le shell va prendre la liste des chaînes, en
l'occurrence «&nbsp;*&nbsp;», c'est-à-dire tous les fichiers et
répertoires du répertoire courant. À chacun de ces éléments, il va
attribuer la variable <code>element</code>, puis exécuter les commandes
spécifiées, en l'occurrence <code>echo
&quot;$element&quot;</code>. Ainsi, si dans mon répertoire j'ai les
fichiers <code>a</code>, <code>b</code> et <code>c</code>, le shell va
exécuter successivement <code>echo &quot;a&quot;</code>, <code>echo
&quot;b&quot;</code> et <code>echo &quot;c&quot;</code>. </p>
<h4>Usage de <code>for</code></h4>
<p>
Comme <code>while</code> et <code>until</code>, <code>for</code> est une
boucle au sens propre. Mais il y a une différence notable entre ces deux
groupes de boucle&nbsp;: <code>while</code> et <code>until</code> sont
plus appropriés à la répétition <em>à l'identique</em> d'une séquence de
commandes, tandis que <strong><code>for</code> convient particulièrement
aux répétitions soumises à de légères variations</strong>.
</p>
<p>
Prenons par exemple le script qui permettait, avec <code>while</code>,
de créer 50 fichiers rapidement. La numération des passages par la
boucle était assez pénible, et éclatée en trois endroits&nbsp;:
</p>
<ol>
<li> avant la boucle&nbsp;: l'affectation de la valeur&nbsp;1 à la
variable <code>numero</code> (et, éventuellement, de la valeur&nbsp;51 à
la variable <code>limite</code>)&nbsp;;</li>
<li> eu début de la boucle, la condition
<code>test&nbsp;$numero&nbsp;!=&nbsp;51</code> ou
<code>test&nbsp;$numero&nbsp;!=&nbsp;$limite</code>&nbsp;;</li>
<li> à la fin de la boucle, l'incrémentation (c'est-à-dire
l'augmentation régulière) de la variable <code>numero</code>, avec la
ligne <code>numero=$(($numero&nbsp;+&nbsp;1))</code>.</li>
</ol>
<p>
Pourquoi dire en trois fois ce qui, dans les langages naturels
(c'est-à-dire humains), s'énonce en une seule&nbsp;: «&nbsp;répéter
<em>n</em> fois cette séquence de commandes&nbsp;»&nbsp;? La boucle
<code>for</code> se révèle alors indispensable&nbsp;:
</p>
<pre>
#!/bin/sh
# Fichier &quot;50fichiers-for&quot;
# la commande &quot;seq <em>a</em> <em>b</em>&quot; compte de <em>a</em> à <em>b</em>
for numero in `seq 1 50`
do touch fichier&quot;$numero&quot;
done
</pre>
<p class="continue">Avouez que le script est beaucoup plus simple de
cette manière&nbsp;!</p>
<h2><a name="retour">Code de retour</a></h2>
<p>
On remarque que la condition des commandes <code>if</code> et
<code>while</code> est une commande. Chaque commande renvoie un code de
retour (qui est ignoré en utilisation normale). Si le code est 0, la
commande a réussi&nbsp;; sinon, la commande a échoué. Par exemple, le
compilateur <code>cc</code> renvoie un code d'erreur non nul si le
fichier compilé contient des erreurs, ou s'il n'existe pas.
</p>
<p>
Les commandes <code>if</code> et <code>while</code> considèrent donc le
code de retour 0 comme «&nbsp;vrai&nbsp;», et tout autre code comme
«&nbsp;faux&nbsp;».
</p>
<p>
Il existe une commande <code>test</code>, qui évalue des expressions
booléennes (c'est-à-dire dont la valeur ne peut être que vraie ou
fausse, 1 ou 0) passées en argument, et renvoie un code de retour en
fonction du résultat. Elle est bien utile pour les scripts.
Exemple&nbsp;:
</p>
<pre>
if test $var = foo
then echo 'La variable vaut foo'
else echo 'La variable ne vaut pas foo'
fi</pre>
<div class="metainformation">
Auteur : Baptiste Mélès.
Dernière modification le <date value="$Date: 2005-06-03 13:56:21 $" />.
</div>
</body>
</html>

View file

@ -66,9 +66,8 @@ Quand le shell est inactif, il affiche une <em>invite</em>
(<em>prompt</em> en anglais), qui ressemble à cela&nbsp;:
</p>
<pre>
<span class="prompt">chaland ~ $</span>
</pre>
<pre><span class="prompt">chaland ~ $</span></pre>
<p>
Un curseur, parfois clignotant, indique que le shell attend que vous lui
@ -212,7 +211,7 @@ l'inverse.
Basé sur un polycopié de Roberto Di Cosmo, Xavier Leroy et Damien
Doligez.
Modifications&nbsp;: Nicolas George, Baptiste Mélès.
Dernière modification le <date value="$Date: 2005-05-31 11:33:38 $"
Dernière modification le <date value="$Date: 2005-06-03 13:56:22 $"
/>.
</div>

View file

@ -240,212 +240,226 @@ un programme, c'est-
</p>
<h2><a name="structures">Structures de contrôle</a></h2>
<h2>Un script qui parle&nbsp;: la commande <code>echo</code></h2>
<p>
Maintenant que vous savez comment on peut exécuter un script, il s'agit
de le remplir... Commençons par ce qu'il y a de plus simple&nbsp;:
afficher du texte.
</p>
<h3>Hello World&nbsp;!</h3>
<p>
C'est avec les structures de contrôle qu'un programme peut commencer à
devenir sérieux. Le principe général de ces structures est le
suivant&nbsp;: adapter le comportement du programme selon les réponses
apportées à certaines questions.
Traditionnellement, on commence par faire un programme qui affiche
simplement la ligne «&nbsp;Hello world&nbsp;» (ce qui signifie en
anglais&nbsp;: bonjour au monde entier). Faites donc un fichier
<code>helloworld</code> contenant les lignes suivantes&nbsp;:
</p>
<pre>
#!/bin/sh
# Fichier &quot;helloworld&quot;
echo &quot;Hello world&quot;
</pre>
<p>
Exécutez ensuite ce programme, par exemple en tapant, dans le répertoire
où il se trouve&nbsp;:
</p>
<pre><span class="prompt">clipper ~ $</span> ./helloworld
Hello world</pre>
<p>
Ça y est, vous avez créé votre premier programme&nbsp;! Lancez-le autant
de vous que vous voulez, vous avez bien mérité d'être fier de vous.
</p>
<p>
Nous avons déjà vu une application possible des structures de contrôle
en parlant des variables. Le programme <code>hachoir</code> conçu par
Robert le jardinier pose la question suivante&nbsp;: est-ce que
<code>$USER</code> vaut «&nbsp;Robert&nbsp;»&nbsp;?
Exercice&nbsp;: francisez ce script.
</p>
<ul>
<li> si oui, alors le hachoir doit se mettre en marche&nbsp;;</li>
<li> si non, alors le hachoir doit refuser de se mettre en
marche.</li>
</ul>
<h3>La boucle <code>if</code></h3>
<h3>La commande <code>echo</code></h3>
<p>
La structure de contrôle <code>if</code> est la plus courante de toutes,
et la plus élémentaire. Elle est constituée de quatre termes&nbsp;:</p>
<ol>
<li> <code>if</code> (si), qui marque la condition à remplir&nbsp;;
</li>
<li> <code>then</code> (alors), qui marque les conséquences si la
condition est remplie&nbsp;; </li>
<li> <code>else</code> (sinon), qui est facultatif et marque le
comportement à adopter si la condition n'est pas remplie&nbsp;; </li>
<li> <code>fi</code> (<code>if</code> à l'envers), qui marque la fin de
la boucle.</li>
</ol>
<pre>if <em>commande</em> ; then <em>commandes</em> ; else <em>commandes</em> ; fi</pre>
<p class="continue"> ou bien (car le point et virgule équivaut à un saut
de ligne)&nbsp;:</p>
<pre>if <em>commande</em>
then <em>commandes</em>
else <em>commandes</em>
fi</pre>
<p>
Par exemple, pour Robert le jardinier, on aura&nbsp;:
La commande <code>echo</code> sert à afficher du texte sur la sortie
standard (pour savoir ce qu'est la sortie standard, consultez la page
sur les <a href="entreesortie.html">entrées et sorties</a>). Chaque
ligne de texte est écrite sur une ligne à part. Exemple&nbsp;:
</p>
<pre>if test $USER = Robert
then hacher_menu_comme_chair_à_pâté $*
else echo "Quand tu seras grand, $USER."
echo "Et fais bien attention en traversant la rue."
fi</pre>
<pre>
#!/bin/sh
# Fichier &quot;bonjour&quot;
echo &quot;Bonjour... &quot;
echo &quot;Comment allez-vous ?&quot;
</pre>
<p class="continue">affiche les lignes suivantes&nbsp;:</p>
<pre>
Bonjour...
Comment allez-vous ?
</pre>
<p class="continue">et non&nbsp;:</p>
<pre>
Bonjour... Comment allez-vous ?
</pre>
<h3>La boucle <code>for</code></h3>
<h4>Annuler le retour chariot</h4>
<p>
La boucle <code>for</code> affecte successivement une variable chaque
chaîne de caractères trouvée dans une <em>liste de chaînes</em>, et
exécute les <em>commandes</em> une fois pour chaque chaîne.
Si vous voulez annuler le retour chariot qui a lieu par défaut à la fin
de toute commande <code>echo</code>, il faut utiliser l'option
<code>-n</code>. Le programme sera alors&nbsp;:
</p>
<pre>for <em>var</em> in <em>liste de chaînes</em> ; do <em>commandes</em> ; done</pre>
<pre>
#!/bin/sh
<p class="continue"> ou bien&nbsp;:</p>
<pre>for <em>var</em> in <em>liste de chaînes</em>
do <em>commandes</em>
done</pre>
<p>
Rappel : <code>$<em>var</em></code> accède à la valeur courante de
<em>var</em>. La partie <em>commandes</em> est une suite de commandes,
séparées par des points et virgules (<code>;</code>) ou des retours à la
ligne.
</p>
<p>
Exemple&nbsp;:
</p>
<pre>for i in *; do echo &quot;$i&quot;; done</pre>
echo -n &quot;Bonjour...&quot;
echo &quot;Comment allez-vous ?&quot;
</pre>
<p class="continue">
affiche les noms de tous les fichiers du répertoire courant, un par ligne.
</p>
<p>
Remarque&nbsp;: plus un programme grossit, plus il importe que son
contenu soit lisible. <strong>Les noms de variable doivent donc être le
plus lisibles possible</strong>, pour permettre à d'autres gens, ou bien
à vous-même dans quelques mois, de comprendre rapidement votre script.
Ainsi, il peut être plus explicite d'écrire, au lieu de «&nbsp;<code>for
i in *; do echo &quot;$i&quot;; done</code>&nbsp;»&nbsp;:
</p>
<pre>for fichier in *; do echo &quot;$fichier&quot;; done</pre>
<p>
En outre, pour des raisons de lisibilité, <strong>n'hésitez pas à
gaspiller des lignes de code</strong> en sautant des lignes&nbsp;; les
lignes de code ne sont pas une ressource rare&nbsp;:
</p>
<pre>for fichier in *
do echo &quot;$fichier&quot;
done</pre>
<h3>La boucle <code>while</code></h3>
<pre>while <em>commande</em>
do <em>commande</em>
done</pre>
<p>
La boucle <code>while</code> exécute les <em>commandes</em> de manière
répétée tant que la première <em>commande</em> réussit.</p>
<p> Par exemple&nbsp;: <em>tant que</em> le mot de passe correct n'a pas
été donné, refuser l'accès et redemander le mot de passe.
</p>
<h3>La boucle <code>case</code></h3>
<pre>case <em>chaîne</em> in
<em>pattern</em>) <em>commande</em> ;;
<em>pattern</em>) <em>commande</em> ;;
esac</pre>
<p>
La boucle <code>case</code> exécute la première <em>commande</em> telle
que la <em>chaîne</em> est de la forme <em>pattern</em>. Un
<em>pattern</em> (motif) est un mot contenant éventuellement les
constructions <code>*</code>, <code>?</code>, <code>[a-d]</code>, avec
la même signification que pour les raccourcis dans les noms de
fichiers. Exemple&nbsp;:
Alors seulement, vous pourrez avoir&nbsp;:
</p>
<pre>
case $var in
[0-9]*) echo 'Nombre';;
[a-zA-Z]*) echo 'Mot';;
*) echo 'Autre chose';;
esac</pre>
Bonjour... Comment allez-vous ?
</pre>
<h4>Citer des variables</h4>
<p>Faisons mieux encore&nbsp;: votre script va citer des variables. Pour
savoir ce que sont des variables, allez voir la page sur les <a
href="variable.html">variables</a>.</p>
<p>La variable <code>USER</code> contient le login de
l'utilisateur&nbsp;; la variable <code>PWD</code> (pour <em>print
working directory</em>) affiche le répertoire courant. Faisons donc le
script suivant&nbsp;:</p>
<pre>
#/bin/sh
# Fichier &quot;mon-pwd&quot;
echo &quot;Bonjour $USER...&quot;
echo &quot;Tu es actuellement dans le répertoire $PWD.&quot;
</pre>
<p>Comme vous pouvez le remarquer, pour citer le contenu d'une variable,
on ajoute le signe dollar ($) devant son nom.</p>
<h2>Un script qui écoute&nbsp;: la commande <code>read</code></h2>
<p>Parler c'est bien, écouter c'est mieux. Jusqu'ici, votre programme
est capable de parler, de dire bonjour, mais il n'est même pas capable
de vous appeler par votre nom, tout juste par votre login, ce qui n'est
pas très intime...</p>
<p>
Vous observerez que l'on clôt la boucle <code>case</code> avec
<code>esac</code>, qui est le mot <code>case</code> à l'envers, de même
que l'on clôt <code>if</code> avec <code>fi</code>.
</p>
<h2><a name="retour">Code de retour</a></h2>
<p>
On remarque que la condition des commandes <code>if</code> et
<code>while</code> est une commande. Chaque commande renvoie un code de
retour (qui est ignoré en utilisation normale). Si le code est 0, la
commande a réussi&nbsp;; sinon, la commande a échoué. Par exemple, le
compilateur <code>cc</code> renvoie un code d'erreur non nul si le
fichier compilé contient des erreurs, ou s'il n'existe pas.
</p>
<p>
Les commandes <code>if</code> et <code>while</code> considèrent donc le
code de retour 0 comme «&nbsp;vrai&nbsp;», et tout autre code comme
«&nbsp;faux&nbsp;».
</p>
<p>
Il existe une commande <code>test</code>, qui évalue des expressions
booléennes (c'est-à-dire dont la valeur ne peut être que vraie ou
fausse, 1 ou 0) passées en argument, et renvoie un code de retour en
fonction du résultat. Elle est bien utile pour les scripts.
Exemple&nbsp;:
Nous allons donc lui donner la faculté d'écouter, grâce à la commande
<code>read</code>. Prenons le script suivant&nbsp;:
</p>
<pre>
if test $var = foo
then echo 'La variable vaut foo'
else echo 'La variable ne vaut pas foo'
fi</pre>
#!/bin/sh
# Fichier &quot;mon-nom&quot;
echo &quot;Bonjour... Comment vous-appelez-vous ?&quot;
read nom
echo &quot;Je vous souhaite, $nom, de passer une bonne journée.&quot;
</pre>
<p>Vous connaissez déjà la commande <code>echo</code>. La commande
<code>read</code> permet de lire des variables. Si vous exécutez ce
script, après avoir affiché la ligne </p>
<pre>
Bonjour... Comment vous-appelez-vous ?
</pre>
<p class="continue">le shell va attendre que vous tapiez votre
nom. Tapez par exemple <code>Toto</code>, puis appuyez sur Entrée, et
vous verrez&nbsp;:</p>
<pre>
Bonjour... Comment vous-appelez-vous ?
<em>Toto</em>
Je vous souhaite, Toto, de passer une bonne journée.</pre>
<div class="attention">
La commande <code>read</code> doit être suivie du seul nom de la
variable, <strong>non précédé du signe dollar</strong>. <em>Le signe
dollar ne doit précéder le nom de la variable que lorsque l'on cite son
contenu.</em>
</div>
<h3>Lire plusieurs variables</h3>
<p>
La commande <code>read</code> permet également de lire plusieurs
variables. Il suffit pour cela d'indiquer à la suite les noms des
différentes variables. Exemple&nbsp;:
</p>
<pre>
#!/bin/sh
# Fichier &quot;administration&quot;
echo &quot;Écrivez votre nom puis votre prénom :&quot;
read nom prenom
echo &quot;Nom : $nom&quot;
echo &quot;Prénom : $prenom&quot;
</pre>
<p>Vous aurez&nbsp;:</p>
<pre>
Écrivez votre nom puis votre prénom :
<em>Hugo Victor</em>
Nom : Hugo
Prénom : Victor
</pre>
<p>Vous êtes maintenant en mesure de faire ces <a
href="&url.tuteurs;unix/exercices/">exercices</a> pour vous
entraîner.</p>
<h3>Appuyer sur Entrée pour continuer</h3>
<p>Nous avons vu comment utiliser <code>read</code> avec un seul
argument et avec plusieurs arguments&nbsp;; il reste à voir l'usage de
<code>read</code> <em>sans</em> argument. Oui, c'est possible&nbsp;!
Cela équivaut simplement à attendre un réaction de l'utilisateur, mais
sans mémoriser ce qu'il tape.</p>
<p> Concrètement, cela est très utile après un message «&nbsp;Appuyez
sur Entrée pour continuer.&nbsp;» Exemple&nbsp;:</p>
<pre>
#!/bin/sh
# Fichier &quot;continuer&quot;
echo &quot;Quelle est la différence entre un canard ?&quot;
echo &quot;(Appuyez sur Entrée pour avoir la réponse)&quot;
read
echo &quot;Les pattes, surtout la gauche.&quot;
</pre>
<div class="metainformation">
Basé sur un polycopié de Roberto Di Cosmo, Xavier Leroy et Damien Doligez.
Modifications&nbsp;: Nicolas George, Baptiste Mélès.
Dernière modification le <date value="$Date: 2005-05-31 11:33:38 $" />.
Auteur&nbsp;: Baptiste Mélès.
Dernière modification le <date value="$Date: 2005-06-03 13:56:22 $" />.
</div>
</body>