Extension du tutorial shell --Bap.
This commit is contained in:
parent
74df6e2ff8
commit
95fdd248b3
3 changed files with 822 additions and 176 deletions
633
unix/shell/boucle.tml
Normal file
633
unix/shell/boucle.tml
Normal 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 ; 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 : 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 : <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 : est-ce que <code>$USER</code> vaut
|
||||
« Robert » ?
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li> Si oui, alors le hachoir doit se mettre en marche ;</li>
|
||||
<li> si non, alors le hachoir doit refuser de se mettre en
|
||||
marche.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
C'est pour les structures de type « si... alors... ou sinon »,
|
||||
la commande <code>if</code> a été faite. Elle est constituée de cinq
|
||||
termes, dont trois sont obligatoires :</p>
|
||||
|
||||
<ol>
|
||||
<li> <code>if</code> (si), qui marque la condition à remplir ;
|
||||
</li>
|
||||
<li> <code>then</code> (alors), qui marque les conséquences si la
|
||||
condition est remplie ; </li>
|
||||
<li> <code>elif</code> (contraction de <em>else if</em>, qui signifie
|
||||
« sinon, si... »), qui est <em>facultatif</em> et marque une
|
||||
condition alternative ;</li>
|
||||
<li> <code>else</code> (sinon), qui est <em>facultatif</em> et marque le
|
||||
comportement à adopter si la condition n'est pas remplie ; </li>
|
||||
<li> <code>fi</code> (<code>if</code> à l'envers), qui marque la fin de
|
||||
la boucle.</li>
|
||||
</ol>
|
||||
|
||||
<p>On peut donc taper :</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) :</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 :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
if test $USER = Robert
|
||||
then echo "Le hachoir va se mettre en marche."
|
||||
<em>mettre en marche le hachoir</em>
|
||||
else echo "Quand tu seras grand, $USER."
|
||||
echo "Et fais bien attention en traversant la rue."
|
||||
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 :</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 :</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "mon-pwd"
|
||||
|
||||
if test $PWD = $HOME
|
||||
then echo "Vous êtes dans votre répertoire d'accueil."
|
||||
elif test $PWD = $HOME/bin
|
||||
then echo "Vous êtes dans votre répertoire d'exécutables."
|
||||
elif test $PWD = $HOME/bin/solaris
|
||||
then echo "Vous êtes dans votre répertoire d'exécutables pour Solaris."
|
||||
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 : <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 « $HOME » 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> ?</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 :</p>
|
||||
|
||||
<pre>
|
||||
if test $PWD = $HOME
|
||||
then echo "Vous êtes dans votre répertoire d'accueil."
|
||||
elif test $PWD = $HOME/bin
|
||||
then echo "Vous êtes dans votre répertoire d'exécutables."
|
||||
elif test $PWD = $HOME/bin/solaris
|
||||
then echo "Vous êtes dans votre répertoire d'exécutables pour Solaris."
|
||||
elif test $PWD = $HOME/prive
|
||||
then echo "Vous êtes dans votre répertoire privé."
|
||||
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>
|
||||
|
||||
<p>Pourquoi ce programme est-il pénible à écrire, à lire et à
|
||||
débuguer ?</p>
|
||||
|
||||
<ol>
|
||||
|
||||
<li> parce qu'on a du mal, en le survolant simplement, à saisir
|
||||
immédiatement son enjeu ; </li>
|
||||
|
||||
<li> parce qu'il y a une redondance de la séquence « <code>test
|
||||
$PWD =</code> » ; et qui dit redondance dit possibilité de
|
||||
faute de frappe – erreur particulièrement difficile à repérer
|
||||
à l'œil nu ;</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> :</p>
|
||||
|
||||
<pre>
|
||||
case "$PWD" in
|
||||
"$HOME") echo "Vous êtes dans votre répertoire d'accueil.";;
|
||||
"$HOME/bin") echo "Vous êtes dans votre répertoire d'exécutables.";;
|
||||
"$HOME/bin/solaris") echo "Vous êtes dans votre répertoire d'exécutables pour Solaris.";;
|
||||
"$HOME/prive") echo "Vous êtes dans votre répertoire privé.";;
|
||||
"*") 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.";;
|
||||
esac
|
||||
</pre>
|
||||
|
||||
<p>Le gain de lisibilité est flagrant (si si, c'est flagrant, je vous
|
||||
assure) :</p>
|
||||
|
||||
<ol>
|
||||
<li> la structure de la boucle et son enjeu sont
|
||||
transparents ;</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> ;</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 :</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
|
||||
« cas » ; cette structure de contrôle permet en effet de
|
||||
réagir adéquatement aux différents « cas de figure »
|
||||
possibles.</p>
|
||||
|
||||
<p>
|
||||
Vous observerez que :
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li> le <em>motif</em> est séparé des commandes par une parenthèse
|
||||
fermante (qui n'est ouverte nulle part !) ;</li>
|
||||
|
||||
<li> la série des <em>commandes</em> est close par deux points et
|
||||
virgules successifs (« ;; ») ; </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 <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 :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
case $var in
|
||||
[0-9]*) echo "$var est un nombre.";;
|
||||
[a-zA-Z]*) echo "$var est un mot.";;
|
||||
*) echo "$var n'est ni un nombre ni un mot.";;
|
||||
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 ? Le premier réflexe est de répéter, presque à l'identique,
|
||||
des lignes, de la façon suivante :</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "canards"
|
||||
|
||||
echo "Quel est ton nom ?"
|
||||
read nom
|
||||
|
||||
case $nom in <strong>
|
||||
"Riri") echo "Ton oncle s'appelle Donald, Riri.";;
|
||||
"Fifi") echo "Ton oncle s'appelle Donald, Fifi.";;
|
||||
"Loulou") echo "Ton oncle s'appelle Donald, Loulou.";;
|
||||
</strong>"*") echo "Tu n'es pas un neveu de Donald.";;
|
||||
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 « | », qui permet de mettre sur le
|
||||
même plan différents motifs. On pourra donc écrire :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "canards"
|
||||
|
||||
echo "Quel est ton nom ?"
|
||||
read nom
|
||||
|
||||
case $nom in
|
||||
<strong>"Riri"|"Fifi"|"Loulou"</strong>) echo "Ton oncle s'appelle Donald, <strong>$nom</strong>.";;
|
||||
"*") echo "Tu n'es pas un neveu de Donald.";;
|
||||
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 « * », cela revient à l'instruction
|
||||
<code>else</code> dans la boucle <code>if</code></strong>.</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h2>Répéter une procédure : <code>while</code>, <code>until</code>
|
||||
et <code>for</code></h2>
|
||||
|
||||
<p>Les structures <code>if</code> et <code>case</code> sont des
|
||||
structures d'aiguillage ; 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 ; en
|
||||
anglais, <em>while</em> signifie « tant que ».</p>
|
||||
|
||||
|
||||
<h4>Syntaxe de <code>while</code></h4>
|
||||
|
||||
<p>La boucle <code>while</code> adopte la syntaxe suivante :</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 (« ; »). 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 "Tapez le mot de passe :"
|
||||
read password
|
||||
|
||||
while test "$password" != <em>mot de passe correct</em>
|
||||
do echo "Mot de passe incorrect."
|
||||
echo "Tapez le mot de passe"
|
||||
read password
|
||||
done
|
||||
|
||||
echo "Mot de passe correct."
|
||||
echo "Bienvenue sur les ordinateurs secrets du Pentagone."
|
||||
</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 !...).
|
||||
</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 "50fichiers"
|
||||
|
||||
numero=1
|
||||
|
||||
while test $numero != 51
|
||||
do
|
||||
# la commande "touch" permet de créer un fichier vide :
|
||||
touch fichier"$numero"
|
||||
|
||||
# cette commande ajoute 1 à la variable "numero" :
|
||||
numero=$(($numero + 1))
|
||||
done
|
||||
</pre>
|
||||
|
||||
<p class="continue">Avec ce script, la variable <code>numero</code> est
|
||||
d'abord créée avec la valeur 1. Ensuite, le shell examine si la
|
||||
valeur de <code>numero</code> est différente de 51 ; c'est le
|
||||
cas. Par conséquent, le shell exécute les commandes suivantes : il
|
||||
crée un fichier <code>fichier1</code>, puis ajoute 1 à la variable
|
||||
<code>numero</code>, qui vaut donc 2. Et c'est reparti pour un
|
||||
tour ! Le shell examine si la valeur de <code>numero</code>,
|
||||
c'est-à-dire 2, est différente de 51, etc.
|
||||
</p>
|
||||
|
||||
<p>Petit conseil pour faire les choses le plus proprement
|
||||
possible : dans le script qui précède,
|
||||
remplacez « 51 » par une variable : le script sera
|
||||
beaucoup plus lisible. Exemple :</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "50fichiers"
|
||||
|
||||
numero=1
|
||||
<strong>limite=51</strong>
|
||||
|
||||
while test $numero != <strong>$limite</strong>
|
||||
do
|
||||
# la commande "touch" permet de créer un fichier vide :
|
||||
touch fichier"$numero"
|
||||
|
||||
# cette commande ajoute 1 à la variable "numero" :
|
||||
numero=$(($numero + 1))
|
||||
done
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<h3>La boucle <code>until</code></h3>
|
||||
|
||||
<p>
|
||||
<em>Until</em> signifie « jusqu'à ce que » 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 : </p>
|
||||
|
||||
<pre>
|
||||
echo "Tapez le mot de passe :"
|
||||
read password
|
||||
|
||||
<strong>until test $password = <em>mot de passe correct</em></strong>
|
||||
do echo "Mot de passe incorrect."
|
||||
echo "Tapez le mot de passe"
|
||||
read password
|
||||
done
|
||||
|
||||
echo "Mot de passe correct."
|
||||
echo "Bienvenue sur les ordinateurs secrets de la NASA."
|
||||
</pre>
|
||||
|
||||
|
||||
<p> Il revient en effet au même de répéter une suite de commandes
|
||||
« <em>tant que</em> le mot de passe est
|
||||
<strong>incorrect</strong> » ou bien « <em>jusqu'à ce que</em>
|
||||
le mot de passe soit <strong>correct</strong> », car un mot de
|
||||
passe déterminé ne peut être que correct ou incorrect.
|
||||
</p>
|
||||
|
||||
|
||||
<h4><code>while</code> ou <code>until</code> ?</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 ? 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 "liste"
|
||||
|
||||
for element in *
|
||||
do echo "$element"
|
||||
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 « * », 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
|
||||
"$element"</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 "a"</code>, <code>echo
|
||||
"b"</code> et <code>echo "c"</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 : <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 :
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li> avant la boucle : l'affectation de la valeur 1 à la
|
||||
variable <code>numero</code> (et, éventuellement, de la valeur 51 à
|
||||
la variable <code>limite</code>) ;</li>
|
||||
|
||||
<li> eu début de la boucle, la condition
|
||||
<code>test $numero != 51</code> ou
|
||||
<code>test $numero != $limite</code> ;</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 + 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 : « répéter
|
||||
<em>n</em> fois cette séquence de commandes » ? La boucle
|
||||
<code>for</code> se révèle alors indispensable :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "50fichiers-for"
|
||||
|
||||
# la commande "seq <em>a</em> <em>b</em>" compte de <em>a</em> à <em>b</em>
|
||||
for numero in `seq 1 50`
|
||||
do touch fichier"$numero"
|
||||
done
|
||||
</pre>
|
||||
|
||||
<p class="continue">Avouez que le script est beaucoup plus simple de
|
||||
cette manière !</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 ; 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 « vrai », et tout autre code comme
|
||||
« faux ».
|
||||
</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 :
|
||||
</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>
|
|
@ -66,9 +66,8 @@ Quand le shell est inactif, il affiche une <em>invite</em>
|
|||
(<em>prompt</em> en anglais), qui ressemble à cela :
|
||||
</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 : 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>
|
||||
|
||||
|
|
|
@ -240,212 +240,226 @@ un programme, c'est-
|
|||
</p>
|
||||
|
||||
|
||||
<h2><a name="structures">Structures de contrôle</a></h2>
|
||||
<h2>Un script qui parle : 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 :
|
||||
afficher du texte.
|
||||
</p>
|
||||
|
||||
<h3>Hello World !</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 : 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 « Hello world » (ce qui signifie en
|
||||
anglais : bonjour au monde entier). Faites donc un fichier
|
||||
<code>helloworld</code> contenant les lignes suivantes :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
|
||||
# Fichier "helloworld"
|
||||
echo "Hello world"
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Exécutez ensuite ce programme, par exemple en tapant, dans le répertoire
|
||||
où il se trouve :
|
||||
</p>
|
||||
|
||||
<pre><span class="prompt">clipper ~ $</span> ./helloworld
|
||||
Hello world</pre>
|
||||
|
||||
<p>
|
||||
Ça y est, vous avez créé votre premier programme ! 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 : est-ce que
|
||||
<code>$USER</code> vaut « Robert » ?
|
||||
Exercice : francisez ce script.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li> si oui, alors le hachoir doit se mettre en marche ;</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 :</p>
|
||||
|
||||
<ol>
|
||||
<li> <code>if</code> (si), qui marque la condition à remplir ;
|
||||
</li>
|
||||
<li> <code>then</code> (alors), qui marque les conséquences si la
|
||||
condition est remplie ; </li>
|
||||
<li> <code>else</code> (sinon), qui est facultatif et marque le
|
||||
comportement à adopter si la condition n'est pas remplie ; </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) :</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 :
|
||||
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 :
|
||||
</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 "bonjour"
|
||||
|
||||
echo "Bonjour... "
|
||||
echo "Comment allez-vous ?"
|
||||
</pre>
|
||||
|
||||
<p class="continue">affiche les lignes suivantes :</p>
|
||||
|
||||
<pre>
|
||||
Bonjour...
|
||||
Comment allez-vous ?
|
||||
</pre>
|
||||
|
||||
<p class="continue">et non :</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 :
|
||||
</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 :</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 :
|
||||
</p>
|
||||
|
||||
<pre>for i in *; do echo "$i"; done</pre>
|
||||
echo -n "Bonjour..."
|
||||
echo "Comment allez-vous ?"
|
||||
</pre>
|
||||
|
||||
<p class="continue">
|
||||
affiche les noms de tous les fichiers du répertoire courant, un par ligne.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Remarque : 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 « <code>for
|
||||
i in *; do echo "$i"; done</code> » :
|
||||
</p>
|
||||
|
||||
<pre>for fichier in *; do echo "$fichier"; 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 ; les
|
||||
lignes de code ne sont pas une ressource rare :
|
||||
</p>
|
||||
|
||||
<pre>for fichier in *
|
||||
do echo "$fichier"
|
||||
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 : <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 :
|
||||
Alors seulement, vous pourrez avoir :
|
||||
</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 : 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 ; la variable <code>PWD</code> (pour <em>print
|
||||
working directory</em>) affiche le répertoire courant. Faisons donc le
|
||||
script suivant :</p>
|
||||
|
||||
<pre>
|
||||
#/bin/sh
|
||||
# Fichier "mon-pwd"
|
||||
|
||||
echo "Bonjour $USER..."
|
||||
echo "Tu es actuellement dans le répertoire $PWD."
|
||||
</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 : 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 ; 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 « vrai », et tout autre code comme
|
||||
« faux ».
|
||||
</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 :
|
||||
Nous allons donc lui donner la faculté d'écouter, grâce à la commande
|
||||
<code>read</code>. Prenons le script suivant :
|
||||
</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 "mon-nom"
|
||||
|
||||
echo "Bonjour... Comment vous-appelez-vous ?"
|
||||
read nom
|
||||
echo "Je vous souhaite, $nom, de passer une bonne journée."
|
||||
</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 :</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 :
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "administration"
|
||||
|
||||
echo "Écrivez votre nom puis votre prénom :"
|
||||
read nom prenom
|
||||
echo "Nom : $nom"
|
||||
echo "Prénom : $prenom"
|
||||
</pre>
|
||||
|
||||
<p>Vous aurez :</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 ; il reste à voir l'usage de
|
||||
<code>read</code> <em>sans</em> argument. Oui, c'est possible !
|
||||
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 « Appuyez
|
||||
sur Entrée pour continuer. » Exemple :</p>
|
||||
|
||||
<pre>
|
||||
#!/bin/sh
|
||||
# Fichier "continuer"
|
||||
|
||||
echo "Quelle est la différence entre un canard ?"
|
||||
echo "(Appuyez sur Entrée pour avoir la réponse)"
|
||||
read
|
||||
echo "Les pattes, surtout la gauche."
|
||||
</pre>
|
||||
|
||||
|
||||
<div class="metainformation">
|
||||
Basé sur un polycopié de Roberto Di Cosmo, Xavier Leroy et Damien Doligez.
|
||||
Modifications : Nicolas George, Baptiste Mélès.
|
||||
Dernière modification le <date value="$Date: 2005-05-31 11:33:38 $" />.
|
||||
Auteur : Baptiste Mélès.
|
||||
Dernière modification le <date value="$Date: 2005-06-03 13:56:22 $" />.
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue