<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//ENS/Tuteurs//DTD TML 1//EN" "tuteurs://DTD/tml.dtd"> <html> <head> <title>Fonctions</title> </head> <body> <h1>Les fonctions en shell</h1> <p> Vous savez qu'<strong>un script shell n'est rien d'autre qu'une série de commandes</strong> (si vous ne le savez pas, lisez la page d'<a href="script.html">initiation à la programmation en shell</a>). Mais parfois cela pose des problèmes, car lorsqu'un script devient un peu long et surtout lorsqu'il est obligé de se répéter, les risques de bogues (dysfonctionnements) croissent. </p> <p> L'usage des <strong>fonctions</strong> permet de : </p> <ol> <li> éviter ces répétitions ;</li> <li> diminuer les risques de bogues ;</li> <li> augmenter la lisibilité du script pour un humain.</li> </ol> <h2>Pourquoi des fonctions ?</h2> <h3>Un programme sans fonction</h3> <p> Utilisateur de TeX ou <a href="&url.tuteurs;logiciels/latex/">LaTeX</a> (ou non), vous voulez effacer régulièrement tous les fichiers <code>.aux</code> et <code>.log</code> qui polluent vos répertoires. Pour ceux qui ne connaissent pas TeX, sachez que ce sont des fichiers construits automatiquement, et que l'on peut recréer facilement à partir du fichier <code>.tex</code> : les supprimer permet donc de gagner de l'espace disque sans dommages. </p> <p> Votre script ressemble donc à ceci : </p> <pre> #!/bin/sh # fichier "texcleaner" : efface les fichiers aux et log # Je prends chaque fichier .aux du répertoire courant for fichier in *.aux do # J'affiche son nom et demande confirmation pour l'effacer echo "$fichier" echo "Voulez-vous vraiment l'effacer ? (o/n)" # Je lis la réponse de l'utilisateur read reponse # Et s'il dit oui, j'efface if [[ $reponse == "o" ]] then rm -f $fichier fi done # Je prends chaque fichier .log du répertoire courant for fichier in *.log do # J'affiche son nom et demande confirmation pour l'effacer echo "$fichier" echo "Voulez-vous vraiment l'effacer ? (o/n)" # Je lis la réponse de l'utilisateur read reponse # Et s'il dit oui, j'efface if [[ $reponse == "o" ]] then rm -f $fichier fi done </pre> <p> Vous venez de terminer ce programme, et vous êtes content, car il fonctionne comme vous le voulez. </p> <div class="encadre"> Soyons honnêtes : cet exemple est un peu tiré par les cheveux, car il existe une commande très simple pour faire cela : <pre> rm -i *.aux *.log </pre> Mais ici, cet exemple <em>à la limite</em> est là pour illustrer d'une façon simple l'intérêt des fonctions... Disons alors que cet exemple permet de donner une version francisée de la commande <code>rm -i *.aux *.log</code> ! </div> <h3>Problèmes des programmes sans fonction</h3> <p> Ce programme présente certains aspects déplaisants : </p> <ol> <li> il n'est pas très lisible ;</li> <li> il comporte des répétitions relativement longues (une dizaine de lignes) ;</li> <li> si vous voulez changer le moindre détail, vous devrez rechercher <i>à la main</i> toutes les occurrences de ce que vous voulez changer, ce qui : <ol> <li> est <strong>fatigant</strong> ;</li> <li> est <strong>fastidieux</strong> ;</li> <li> est, surtout, <strong>très peu fiable</strong> : si vous oubliez une occurrence ou que vous modifiez un endroit alors qu'il ne le fallait pas, les conséquences peuvent être graves.</li> </ol> </li> </ol> <p> Il faudrait, pour pallier ces inconvénients, trouver un moyen de <strong>centraliser les informations destinées à être répétées, afin que l'on puisse s'y référer à chaque endroit où cela est nécessaire</strong>. C'est pour cela que les fonctions existent. </p> <h2>Définition et appel d'une fonction</h2> <p> L'utilisation des fonctions se fait en deux moments : </p> <ol> <li> d'abord, il faut <strong>définir la fonction</strong> : vous décrivez quelle série de commandes il faudra exécuter lorsque l'on appellera la fonction ;</li> <li> ensuite, il faut <strong>appeler la fonction</strong> à chaque endroit que vous voulez.</li> </ol> <h3>Analogies avec le monde humain</h3> <p> Dans le monde naturel, on peut comparer cela à l'horloge parlante : celle-ci commence par dire « au quatrième top, il sera neuf heures cinquante-deux », puis « top... top... top... top. » </p> <p> On pourrait dire que dans un premier temps, l'horloge parlante <em>définit une fonction</em>, en assignant un signal (le quatrième top) à ce qu'il signale (il est neuf heures cinquante-deux) ; une fois cela clairement défini, l'horloge <em>égrène les quatre « top »</em>, et le quatrième renvoie à l'énoncé « il est neuf heures cinquante-deux ». </p> <p> En langage naturel, définir une fonction équivaut à dire : <em>quand je dirai le nom de la fonction, vous l'exécuterez</em>. C'est un acte de langage, comme quand un arbitre dit aux athlètes : « Go ! » ; car tous les athlètes savent que le signifiant « Go ! » (ou le coup de pistolet tiré en l'air) signifie qu'ils doivent partir (la fonction a été définie dans le règlement officiel de leur sport). </p> <h3>Définir une fonction</h3> <h4>Comment définir les fonctions ?</h4> <p> Définir une fonction est extrêmement simple : </p> <pre> nom () { instruction1 instruction2 ... } </pre> <ol> <li> On commence par <strong>trouver un nom à la fonction</strong>. Vous pouvez choisir ce nom à votre guise, par exemple <code>liste_des_fichiers</code>, <code>effacer_fichier</code>, etc. Il est toutefois fortement recommandé : <ul> <li> de <strong>ne pas donner à ses fonctions des noms de commandes existant déjà</strong>, par exemple <code>ls</code>, <code>mutt</code>, etc. Cela pourrait en effet poser de graves problèmes, car les fonctions définies dans un programme sont prioritaires sur les commandes integrées du shell (<code>cd</code>, <code>alias</code>, etc.) et les commandes externes (<code>mutt</code>, <code>mozilla</code>, etc.). Le comportement devient difficilement prévisible, et surtout, le script sera très difficile à débuguer... </li> <li> de <strong>ne pas utiliser de signes de ponctuation, d'espaces ni de caractères accentués</strong> dans les noms de fonction ;</li> </ul> </li> <li> une fois que vous avez donné un nom à la fonction, notez des <strong>parenthèses ouvrante et fermante</strong> : ce sont elles qui indiquent à l'interpréteur du script qu'il s'agit d'une définition de fonction ; </li> <li> enfin, <strong>entre accolades</strong>, notez la <strong>série des instructions</strong> qu'il faudra exécuter à chaque appel de la fonction. </li> </ol> <h4>Où définir les fonctions ?</h4> <p> Comme l'interpréteur de scripts shell lit des scripts ligne à ligne, <strong>il faut que la fonction soit définie avant d'être appelée</strong>. Sinon, vous recevez un message de type : « Command not found » (commande introuvable). Par convention, il est préférable de <strong>placer <em>toutes</em> les définitions de fonction vers le début du programme</strong>, avant toutes les instructions. </p> <h3>Appeler une fonction</h3> <p> Notre programme sera donc considérablement allégé : </p> <pre> #!/bin/sh # fichier "texcleaner" : efface les fichiers aux et log # Je définis ma fonction effacer_fichier effacer_fichier () { # J'affiche son nom et demande confirmation pour l'effacer echo "$1" echo "Voulez-vous vraiment l'effacer ? (o/n)" # Je lis la réponse de l'utilisateur read reponse # Et s'il dit oui, j'efface if [[ $reponse == "o" ]] then rm -f $1 fi } # Je prends chaque fichier .aux du répertoire courant for fichier in *.aux do # J'appelle la fonction effacer_fichier pour chaque fichier effacer_fichier $fichier done # Je prends chaque fichier .log du répertoire courant for fichier in *.log do # J'appelle la fonction effacer_fichier pour chaque fichier effacer_fichier $fichier done </pre> <p> On économise une dizaine de lignes, on gagne en lisibilité, et les risques de bogues diminuent considérablement car s'il y a des corrections à apporter, c'est à un seul endroit du script, et non d'une façon disséminée sur l'ensemble du programme. </p> <p> Un détail de la fonction <code>effacer_fichier</code> peut vous étonner : nous utilisons l'argument <code>$1</code>. Comme vous le savez sans doute (sinon, lisez la page sur les <a href="http://www.eleves.ens.fr/home/meles/protect/tuteurs/unix/shell/commande.html">commandes shell et leurs arguments</a>), <code>$1</code> désigne le premier argument passé à une commande ; or <strong>les fonctions peuvent recevoir des arguments</strong>, exactement de la même façon que les commandes. C'est même tout à fait normal, car <strong>les fonctions sont en fait des commandes comme les autres, à ceci près qu'elles ne sont valables qu'à l'échelle d'un script</strong>, et non à celle d'un système tout entier. </p> <h3>Les appels entre fonctions</h3> <p> Sachez enfin que <strong>des fonctions peuvent appeler d'autres fonctions</strong>, ce qui donne une extrême souplesse à leur utilisation. </p> <p> En voici un bref exemple : </p> <pre> #!/bin/sh # Je définis une première fonction ecrire_sur_une_ligne () { echo -n $* } # Je définis une deuxième fonction qui appelle la première saluer_utilisateur () { ecrire_sur_une_ligne "Bonjour " echo $USER } # J'appelle la deuxième fonction saluer_utilisateur </pre> <h2>Abusez des fonctions</h2> <p> De là, passons à un conseil de programmation : <strong>abusez des fonctions !</strong> N'hésitez pas à créer des fonctions pour tout et n'importe quoi. Vous en tirerez : </p> <ol> <li> un gain de lisibilité ;</li> <li> un gain d'efficacité ;</li> <li> un gain de souplesse.</li> </ol> <h3>Gain de lisibilité</h3> <p> <strong>Un programme sans fonction n'est lisible que s'il est très petit</strong> : une vingtaine de lignes tout au plus. Dès qu'un programme dépasse cette taille, il cesse d'être intelligible d'un seul coup d'œil pour un humain. </p> <p> Supposons qu'un programme soit amené à répéter <em>n</em> fois un même fragment de code comportant <em>p</em> lignes ; en utilisant des fonctions on économise (<em>n</em> - 1) x <em>p</em> lignes (toutes les occurrences de la répétition, moins la définition de la fonction). </p> <p> Ce gain de lignes est indissociable d'un gain de lisibilité, car les répétitions fatiguent inutilement le cerveau humain. </p> <h3>Gain d'efficacité</h3> <p> En recopiant « à la main » des fragments identiques de codes, vous risquez toujours la faute de frappe. Or, la moindre coquille peut avoir des conséquences, au mieux, imprévisibles ; au pire, catastrophiques. </p> <p> En isolant les séries d'instructions dans des définitions de fonctions, vous concentrez en un seul endroit la situation possible d'un bogue donné. Vous pouvez <em>circonscrire le bogue</em>. </p> <h3>Gain de souplesse</h3> <p> En utilisant des fonctions, vous donnez à vos programmes une grande souplesse. Si vous voulez apporter une modification d'ensemble à un programme, vous n'avez plus à corriger autant de morceaux du script qu'il y a d'occurrences du même fragment : vous apportez vos modifications de manière centralisée. </p> <p> Pour décrire ce phénomène, on parle souvent de <strong>modularité</strong>, ou encore d'<strong>encapsulation</strong>. Il s'agit en effet de privilégier l'assemblage simple d'éléments simples à l'assemblage complexe d'éléments complexes. Ainsi, chaque fonction <em>cache</em> aux fonctions qui l'appellent la complexité de son travail, pour leur offrir une interface simple : le travail est divisé en autant de petites tâches que nécessaires, au lieu d'une seule tâche gigantesque et labyrinthesque. </p> <p> Pour toutes ces raisons, n'hésitez surtout pas à créer des fonctions et à les emboîter entre elles. La programmation vous paraîtra de plus en plus facile, à mesure que vous réaliserez des tâches de plus en plus complexes. </p> <p> Vous pouvez revenir à la <a href="index.html">page centrale sur le shell</a>, d'où vous pourrez vous orienter vers d'autres parties du cours. </p> <div class="metainformation"> Auteur : Baptiste Mélès. Dernière modification le <date value="$Date: 2005-09-07 23:00:04 $" />. </div> </body> </html>