tuteurs.ens.fr/unix/shell/script.tml

453 lines
13 KiB
Text
Raw Normal View History

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html
PUBLIC "-//ENS/Tuteurs//DTD TML 1//EN"
"tuteurs://DTD/tml.dtd">
<html>
<head>
<title>Scripts</title>
</head>
<body>
<h1><a name="programmation">Programmation de scripts en shell</a></h1>
<p>
Un shell, quel qu'il soit, peut ex<65>cuter des commandes prises dans un
fichier. Un fichier contenant des commandes pour le shell est appel<65> un
<em>script</em>. C'est en fait un <em>programme</em> <20>crit <em>dans le
langage du shell</em>. Ce langage comprend non seulement les commandes que
nous avons d<>j<EFBFBD> vues, mais aussi des structures de contr<74>le
(constructions conditionnelles et boucles).
</p>
<p>
Pour la programmation du shell, nous allons utiliser le shell
<code>sh</code>, qui est le plus r<>pandu et standard. Ce que nous avons
vu jusqu'ici s'applique aussi bien <20> <code>sh</code> qu'<27>
<code>zsh</code> et aussi <20> <code>csh</code>, <20> l'exception de
<code>setenv</code> et de certaines redirections signal<61>es. </p>
<h2>Cr<43>er un script</h2>
<h3>Rendre un script ex<65>cutable</h3>
<p> Pour <20>tre un script, un fichier doit commencer par la ligne: </p>
<pre>#!/bin/sh</pre>
<p>
Il doit aussi avoir <20>tre ex<65>cutable (bit <code>x</code>). Le
<code>#!/bin/sh</code> sur la premi<6D>re ligne indique que ce script doit <20>tre
ex<EFBFBD>cut<EFBFBD> par le shell <code>sh</code> dont on indique le chemin
d'acc<63>s. Pour rendre un fichier ex<65>cutable, tapez&nbsp;:
</p>
<pre><span class="prompt">chaland ~</span> chmod u+x fichier
</pre>
<p class="continue">
(pour en savoir plus sur les droits attach<63>s <20> un fichier, consultez la
page sur <a href="droits.html">les droits d'acc<63>s</a>).
</p>
<h3>Le chemin d'une commande</h3>
<p>
Pour comprendre ce qui suit, vous devez savoir ce qu'est le
<code>PATH</code>. Si ce n'est pas le cas, lisez la <a
href="index.html">page principale sur le shell</a>.
</p>
<p>
Quand vous ex<65>cutez un script, vous pouvez vous trouver <20>
n'importe quel endroit de l'arborescence de vos r<>pertoires. Si le
r<EFBFBD>pertoire courant ne se situe pas dans votre <code>PATH</code> et
que vous voulez ex<65>cuter un programme qui s'y trouve, vous ne pouvez
pas taper&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> commande
</pre>
<p class="continue">car si le r<>pertoire courant n'est pas dans le
<code>PATH</code>, le shell n'ira pas y chercher
<code>commande</code>. </p>
<p>
Vous recevrez donc un message comme&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> commande
zsh: command not found: commande</pre>
<h4>Sp<53>cifier le chemin d'une commande</h4>
<p>
Pour que le shell comprenne o<> chercher votre commande, il faut donc
sp<EFBFBD>cifier l'emplacement de la commande en donnant son chemin, qu'il
soit absolu&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> /home/toto/repertoire/courant/commande
</pre>
<p class="continue">ou relatif&nbsp;: </p>
<pre>
<span class="prompt">clipper ~</span> repertoire/courant/commande
</pre>
<p class="continue">ou encore sous la forme&nbsp;: </p>
<pre>
<span class="prompt">clipper ~/repertoire/courant</span> ./commande
</pre>
<h4>Le r<>pertoire <code>~/bin</code></h4>
<p>
Il y a un certain nombre de commandes que l'on peut vouloir utiliser
depuis n'importe quel r<>pertoire. Dans ce cas, il est fastidieux de&nbsp;:
</p>
<ul>
<li> devoir se rappeler dans quel r<>pertoire se trouve chacune
d'entre elles&nbsp;;</li>
<li> devoir taper <20> chaque fois le chemin de la commande.</li>
</ul>
<p>Il suffit donc de mettre tous vos scripts dans un m<>me
r<EFBFBD>pertoire, et de mettre ce r<>pertoire dans le
<code>PATH</code>. Par convention, ce r<>pertoire s'appelle
<code>bin</code> et se place dans votre r<>pertoire personnel. Si
votre r<>pertoire personnel est <code>/home/toto</code>, ce
r<EFBFBD>pertoire sera donc <code>/home/toto/bin</code>. </p>
<p>
Commencez donc par cr<63>er ce r<>pertoire&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> mkdir bin
</pre>
<p>
Ensuite, v<>rifiez qu'il soit bien dans votre <code>PATH</code>&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> echo $PATH
</pre>
<p class="continue">Si vous voyez par exemple <code>$HOME/bin</code>
dans votre <code>PATH</code>, alors c'est bon, tous les fichiers
ex<EFBFBD>cutables situ<74>s dans ce r<>pertoire seront accessibles depuis
n'importe quel r<>pertoire.
</p>
<p>
Si ce n'est pas le cas, il faut ajouter ce r<>pertoire au
<code>PATH</code>. Pour cela, ajoutez dans le fichier de configuration
de votre shell, par exemple le fichier <code>.zshrc</code>, la
ligne&nbsp;:
</p>
<pre>PATH=$PATH:$HOME/bin</pre>
<p class="continue">Cette ligne indique que la prochaine fois que vous
ouvrirez votre shell, le r<>pertoire <code>bin</code> figurera dans
votre <code>PATH</code>.
</p>
<h2>Principes g<>n<EFBFBD>raux des scripts shell</h2>
<h3>Une succession de commandes</h3>
<p> Si vous manipulez d<>j<EFBFBD> le shell en ligne de commande, vous
pouvez commencer vos premiers scripts. Un script shell est en effet
avant tout une <strong>succession de commandes</strong>.
</p>
<p> Par exemple, si vous avez coutume de taper successivement, quand
vous vous loguez <20> l'ENS&nbsp;:
</p>
<pre>
<span class="prompt">clipper ~</span> mozilla &amp;
<span class="prompt">clipper ~</span> mutt
</pre>
<p class="continue">vous pouvez vous cr<63>er le script suivant dans le
fichier <code>~/bin/amorce</code>&nbsp;:</p>
<pre>
#!/bin/sh
mozilla &amp;
mutt
</pre>
<p>Ainsi, d<>s que vous vous connectez, vous pouvez taper
<code>amorce</code> dans le shell, et vos commandes s'ex<65>cuteront
automatiquement. </p>
<h3>Commentaires</h3>
<p>
Presque tous les langages informatiques autorisent d'ins<6E>rer des
commentaires&nbsp;; le shell n'<27>chappe pas <20> la r<>gle. Pour
cela, il suffit de faire pr<70>c<EFBFBD>der chaque ligne de commentaire du
caract<EFBFBD>re <20>&nbsp;#&nbsp;<3B>. Exemple&nbsp;:
</p>
<pre>
#!/bin/sh
# Tout ce que j'<27>cris ici ne sera pas lu.
echo &quot;Ce que je tape ici sera lu.&quot;
</pre>
<p>
Les lignes blanches ne sont pas interpr<70>t<EFBFBD>es non
plus. N'h<>sitez donc surtout pas <20> espacer votre script, les
lignes blanches ne consomment presque rien en termes d'espace disque, ce
n'est donc pas une ressource rare.
</p>
<h3>L'imp<6D>ratif de lisibilit<69></h3>
<p>
Pourquoi espacer son script&nbsp;? Pourquoi ins<6E>rer des
commentaires&nbsp;? Pour une seule et m<>me raison&nbsp;:
<strong>votre script doit <20>tre lisible</strong>. Pourquoi <20>tre
lisible&nbsp;? </p>
<p> D'abord, pour autrui&nbsp;: si d'autres gens lisent votre script, il
doit <20>tre intelligible, et les passages complexes doivent <20>tre
explicit<EFBFBD>s par des commentaires.</p>
<p> Ensuite, pour vous-m<>me&nbsp;; au moment o<> vous <20>crivez un
script, vous comprenez tout, naturellement&nbsp;; mais si vous le
relisez dans quelques mois, voire quelques ann<6E>es, les passages
obscurs risqueront d'<27>tre incompr<70>hensibles, ce qui est
particuli<EFBFBD>rement p<>nible quand on essaye de <em>d<>buguer</em>
un programme, c'est-<2D>-dire d'en corriger les erreurs.
</p>
<h2><a name="structures">Structures de contr<74>le</a></h2>
<p>
C'est avec les structures de contr<74>le qu'un programme peut commencer <20>
devenir s<>rieux. Le principe g<>n<EFBFBD>ral de ces structures est le
suivant&nbsp;: adapter le comportement du programme selon les r<>ponses
apport<EFBFBD>es <20> certaines questions.
</p>
<p>
Nous avons d<>j<EFBFBD> vu une application possible des structures de contr<74>le
en parlant des variables. Le programme <code>hachoir</code> con<6F>u par
Robert le jardinier pose la question suivante&nbsp;: est-ce que
<code>$USER</code> vaut <20>&nbsp;Robert&nbsp;<3B>&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>
<h3>La boucle <code>if</code></h3>
<p>
La structure de contr<74>le <code>if</code> est la plus courante de toutes,
et la plus <20>l<EFBFBD>mentaire. Elle est constitu<74>e de quatre termes&nbsp;:</p>
<ol>
<li> <code>if</code> (si), qui marque la condition <20> remplir&nbsp;;
</li>
<li> <code>then</code> (alors), qui marque les cons<6E>quences si la
condition est remplie&nbsp;; </li>
<li> <code>else</code> (sinon), qui est facultatif et marque le
comportement <20> adopter si la condition n'est pas remplie&nbsp;; </li>
<li> <code>fi</code> (<code>if</code> <20> 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 <20>quivaut <20> 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;:
</p>
<pre>if test $USER = Robert
then hacher_menu_comme_chair_<72>_p<5F>t<EFBFBD> $*
else echo "Quand tu seras grand, $USER."
echo "Et fais bien attention en traversant la rue."
fi</pre>
<h3>La boucle <code>for</code></h3>
<p>
La boucle <code>for</code> affecte successivement une variable chaque
cha<EFBFBD>ne de caract<63>res trouv<75>e dans une <em>liste de cha<68>nes</em>, et
ex<EFBFBD>cute les <em>commandes</em> une fois pour chaque cha<68>ne.
</p>
<pre>for <em>var</em> in <em>liste de cha<68>nes</em> ; do <em>commandes</em> ; done</pre>
<p class="continue"> ou bien&nbsp;:</p>
<pre>for <em>var</em> in <em>liste de cha<68>nes</em>
do <em>commandes</em>
done</pre>
<p>
Rappel<EFBFBD>: <code>$<em>var</em></code> acc<63>de <20> la valeur courante de
<em>var</em>. La partie <em>commandes</em> est une suite de commandes,
s<EFBFBD>par<EFBFBD>es par des points et virgules (<code>;</code>) ou des retours <20> la
ligne.
</p>
<p>
Exemple&nbsp;:
</p>
<pre>for i in *; do echo &quot;$i&quot;; done</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 <20>tre le
plus lisibles possible</strong>, pour permettre <20> d'autres gens, ou bien
<EFBFBD> vous-m<>me dans quelques mois, de comprendre rapidement votre script.
Ainsi, il peut <20>tre plus explicite d'<27>crire, au lieu de <20>&nbsp;<code>for
i in *; do echo &quot;$i&quot;; done</code>&nbsp;<3B>&nbsp;:
</p>
<pre>for fichier in *; do echo &quot;$fichier&quot;; done</pre>
<p>
En outre, pour des raisons de lisibilit<69>, <strong>n'h<>sitez pas <20>
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<65>cute les <em>commandes</em> de mani<6E>re
r<EFBFBD>p<EFBFBD>t<EFBFBD>e tant que la premi<6D>re <em>commande</em> r<>ussit.</p>
<p> Par exemple&nbsp;: <em>tant que</em> le mot de passe correct n'a pas
<EFBFBD>t<EFBFBD> donn<6E>, refuser l'acc<63>s et redemander le mot de passe.
</p>
<h3>La boucle <code>case</code></h3>
<pre>case <em>cha<68>ne</em> in
<em>pattern</em>) <em>commande</em> ;;
<em>pattern</em>) <em>commande</em> ;;
esac</pre>
<p>
La boucle <code>case</code> ex<65>cute la premi<6D>re <em>commande</em> telle
que la <em>cha<68>ne</em> est de la forme <em>pattern</em>. Un
<em>pattern</em> (motif) est un mot contenant <20>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;:
</p>
<pre>
case $var in
[0-9]*) echo 'Nombre';;
[a-zA-Z]*) echo 'Mot';;
*) echo 'Autre chose';;
esac</pre>
<p>
Vous observerez que l'on cl<63>t la boucle <code>case</code> avec
<code>esac</code>, qui est le mot <code>case</code> <20> l'envers, de m<>me
que l'on cl<63>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<6F> en utilisation normale). Si le code est 0, la
commande a r<>ussi&nbsp;; sinon, la commande a <20>chou<6F>. Par exemple, le
compilateur <code>cc</code> renvoie un code d'erreur non nul si le
fichier compil<69> contient des erreurs, ou s'il n'existe pas.
</p>
<p>
Les commandes <code>if</code> et <code>while</code> consid<69>rent donc le
code de retour 0 comme <20>&nbsp;vrai&nbsp;<3B>, et tout autre code comme
<EFBFBD>&nbsp;faux&nbsp;<3B>.
</p>
<p>
Il existe une commande <code>test</code>, qui <20>value des expressions
bool<EFBFBD>ennes (c'est-<2D>-dire dont la valeur ne peut <20>tre que vraie ou
fausse, 1 ou 0) pass<73>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>
<p>Vous <20>tes maintenant en mesure de faire ces <a
href="&url.tuteurs;unix/exercices/">exercices</a> pour vous
entra<EFBFBD>ner.</p>
<div class="metainformation">
Bas<EFBFBD> sur un polycopi<70> de Roberto Di Cosmo, Xavier Leroy et Damien Doligez.
Modifications&nbsp;: Nicolas George, Baptiste M<>l<EFBFBD>s.
Derni<EFBFBD>re modification le <date value="$Date: 2005-05-31 11:33:38 $" />.
</div>
</body>
</html>