<chapter id="chap_11">
<title>Fonctions</title>
<abstract>
<para>Dans ce chapitre nous aborderons&nbsp;:</para>
<para>

<itemizedlist>
<listitem><para>Qu'est-ce qu'une fonction</para></listitem>
<listitem><para>Création et affichage de fonctions depuis la ligne de commande</para></listitem>
<listitem><para>Fonctions dans les scripts</para></listitem>
<listitem><para>Passer des arguments à une fonction</para></listitem>
<listitem><para>Quand utiliser une fonction</para></listitem>
</itemizedlist>
</para>
</abstract>

<sect1 id="sect_11_01"><title>Introduction</title>
<sect2 id="sect_11_01_01"><title>Qu'est-ce qu'une fonction&nbsp;?</title>
<para>La fonction Shell est le moyen de grouper des commandes en vue d'une exécution ultérieure, par l'appel d'un nom donné à ce groupe, ou <emphasis>routine</emphasis>.  Le nom de la routine doit être unique dans le Shell ou le script.  Toutes les commandes qui constituent une fonction sont exécutées comme des commandes régulières.  Quand une fonction est appelée comme le nom d'une simple commande, la liste des commandes associées à ce nom de fonction est traitée.  Une fonction est exécutée à l'intérieur du Shell dans lequel elle a été déclarée&nbsp;: aucun processus nouveau n'est créé pour interpréter les commandes.</para>
<para>Les commandes intégrées spéciales sont détectées avant les fonctions Shell pendant l'analyse des commandes.  Les intégrées spéciales sont&nbsp;: <command>break</command>, <command>:</command>, <command>.</command>, <command>continue</command>, <command>eval</command>, <command>exec</command>, <command>exit</command>, <command>export</command>, <command>readonly</command>, <command>return</command>, <command>set</command>, <command>shift</command>, <command>trap</command> et <command>unset</command>.</para>
</sect2>
<sect2 id="sect_11_01_02"><title>La syntaxe des fonctions</title>
<para>Les fonctions emploient plutôt la syntaxe</para>
<cmdsynopsis><command>function FONCTION { COMMANDES; }</command></cmdsynopsis>
<para>ou l'option</para>
<cmdsynopsis><command>FONCTION () { COMMANDES; }</command></cmdsynopsis>
<para>Chacune définit une fonction Shell <command>FONCTION</command>.  L'emploi de la commande intégrée <command>function</command> est optionnel&nbsp;; cependant, si elle n'est pas employée, les parenthèses sont nécessaires.</para>
<para>Les commandes listées entre les accolades forment le corps de la fonction.  Ces commandes sont exécutées du moment que <command>FONCTION</command> est spécifié en tant que nom de commande.  Le statut d'exécution est celui de la dernière commande exécutée dans le corps de la fonction.</para>
<note><title>Erreurs communes</title>
<para>Les accolades doivent être séparées du corps de la fonction par un espace, sinon elles sont interprétées d'une mauvaise façon.</para>
<para>Le corps d'une fonction doit se terminer par un point-virgule ou un saut de ligne.</para>
</note>
</sect2>
<sect2 id="sect_11_01_03"><title>Les paramètres positionnels dans les fonctions</title>
<para>Les fonctions sont comme des mini-scripts&nbsp;: elles peuvent accepter des paramètres, elles peuvent utiliser des variables connues seulement dans la fonction (avec l'intégrée Shell <command>local</command>) et elles peuvent renvoyer des valeurs au Shell appelant.</para>
<para>Une fonction a aussi un système pour interpréter des paramètres positionnels.  Cependant, les paramètres positionnels passés à une fonction n'ont pas nécessairement les mêmes valeurs que ceux passés à une commande ou un script.</para>
<para>Quand une fonction est exécutée, les arguments<indexterm><primary>arguments</primary><secondary>functions</secondary></indexterm> de la fonction<indexterm><primary>function</primary><secondary>arguments</secondary></indexterm> deviennent les paramètres positionnels le temps de l'exécution.  Le paramètre spécial <varname>#</varname> qui est remplacé par le nombre de paramètres positionnels est modifié en conséquence. Le paramètre positionnel <varname>0</varname> est inchangé.  La variable Bash <varname>FUNCNAME</varname> est valorisé avec le nom de la fonction, tandis qu'elle s'exécute.</para>
<para>Si l'intégrée <command>return</command> est exécutée dans une fonction, la fonction s'interrompt et l'exécution reprend avec la commande qui suit la fonction appelée.  Quand une fonction s'achève, les valeurs des paramètres positionnels et le paramètre spécial <varname>#</varname> sont restaurés à la valeur qu'ils avaient avant l'exécution de la fonction.  Si un argument numérique est donné à <command>return</command>, c'est ce statut qui est retourné.  Un exemple<indexterm><primary>function</primary><secondary>example arguments</secondary></indexterm> simple<indexterm><primary>arguments</primary><secondary>function example</secondary></indexterm>&nbsp;:</para>
<screen>
<prompt>[lydia@cointreau ~/test]</prompt> <command>cat <filename>showparams.sh</filename></command>
#!/bin/bash
                                                                                
echo "Ce script montre l'emploi d'arguments de fonction."
echo
                                                                                
echo "Le paramètre positionnel 1 pour le script est $1."
echo
                                                                                
test ()
{
echo "Le paramètre positionnel 1 pour la fonction est $1."
RETURN_VALUE=$?
echo "Le code retour de cette fonction est $RETURN_VALUE."
}
                                                                                
test other_param

<prompt>[lydia@cointreau ~/test]</prompt> <command>./showparams.sh <parameter>parameter1</parameter></command>
Ce script montre l'emploi d'arguments de fonction.
 
Le paramètre positionnel 1 pour le script est 1.
 
Le paramètre positionnel 1 pour la fonction est other_param.
Le code retour de cette fonction est 0.

<prompt>[lydia@cointreau ~/test]</prompt>
</screen>
<para>Notez que la valeur retournée ou code retour de la fonction est souvent stockée dans une variable, afin qu'elle puisse être testée ultérieurement.  Les scripts d'initialisation de votre système souvent emploient la technique de tester la variable <varname>RETVAL</varname>, comme ceci&nbsp;:</para>
<screen>
<command>if <parameter>[ $RETVAL -eq 0 ]</parameter>; then
        &lt;lancer le démon&gt;</command>
</screen>
<para>Ou comme cet exemple tiré du script <filename>/etc/init.d/amd</filename>, où l'optimisation de Bash est mis en oeuvre.</para>
<screen>
<command><parameter>[ $RETVAL = 0 ]</parameter> &amp;&amp; touch <filename>/var/lock/subsys/amd</filename></command>
</screen>
<para>Les commandes après <command>&amp;&amp;</command> ne sont exécutées que si le test rend vrai&nbsp;; c'est une façon plus rapide de représenter une structure <command>if/then/fi</command>.</para>
<para>Le code retour de la fonction est souvent utilisé comme statut d'exécution de tout le script.  Vous verrez beaucoup de scripts d'initialisation finissant avec quelque chose comme ça <command>exit <varname>$RETVAL</varname></command>.</para>
</sect2>

<sect2 id="sect_11_01_04"><title>Afficher une fonction</title>
<para>Toutes les fonctions connues du Shell courant peuvent être affichées avec l'intégrée <command>set</command> sans options.  Une fonction est conservée après avoir été appelée, à moins qu'elle soit  <command>unset</command> après son exécution.  La commande <command>which</command> affiche aussi les fonctions&nbsp;:</para>
<screen>
<prompt>[lydia@cointreau ~]</prompt> <command>which <filename>zless</filename></command>
zless is a function
zless ()
{
    zcat "$@" | "$PAGER"
}

<prompt>[lydia@cointreau ~]</prompt> <command>echo <varname>$PAGER</varname></command>
less
</screen>
<para>Ceci est le type de fonctions qui sont typiquement configurées dans un fichier de configuration des ressources Shell de l'utilisateur.  Les fonctions sont plus flexibles que les alias et fournissent un moyen simple et facile d'adapter l'environnement utilisateur.</para>
<para>En voici un pour les utilisateurs DOS&nbsp;:</para>
<screen>
dir ()
{
    ls -F --color=auto -lF --color=always "$@" | less -r
}
</screen>
</sect2>

</sect1>
<sect1 id="sect_11_02"><title>Exemples de fonctions dans des scripts</title>
<sect2 id="sect_11_02_01"><title>Recyclage</title>
<para>Il y a plein de scripts sur votre système qui utilisent des fonctions comme un moyen structuré de passer une série de commandes.  Sur certains systèmes Linux, par exemple, vous trouverez le fichier de définition <filename>/etc/rc.d/init.d/functions</filename>, qui est invoqué dans tous les scripts d'initialisation.  Avec cette méthode, les tâches communes comme contrôler qu'un processus s'exécute, démarrer ou arrêter un démon etc., n'ont qu'à être écrites qu'une seule fois, d'une manière générique.  Si la même tâche est nécessaire de nouveau, le code est recyclé.</para> 
<para>Vous pourriez faire votre propre fichier <filename>/etc/functions</filename> qui contiendrait toutes les fonctions que vous utilisez régulièrement, dans différents scripts.  Entrez simplement la ligne </para>
<cmdsynopsis><command>. <filename>/etc/functions</filename></command></cmdsynopsis> 
<para>quelque part au début du script et vous pouvez recycler les fonctions.</para>
</sect2>
<sect2 id="sect_11_02_02"><title>Définir le chemin</title>
<para>Le code ci-dessous peut être trouvé dans le fichier <filename>/etc/profile</filename>.  La fonction <command>pathmunge</command> est définie, puis utilisée pour définir les chemins de <emphasis>root</emphasis> et des autres utilisateurs&nbsp;:</para>
<screen>
pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

# Path manipulation
if [ `id -u` = 0 ]; then
        pathmunge /sbin
        pathmunge /usr/sbin
        pathmunge /usr/local/sbin
fi

pathmunge /usr/X11R6/bin after

unset pathmunge
</screen>
<para>La fonction considère son premier argument comme étant un nom de chemin.  Si ce nom de chemin n'est pas encore dans le chemin courant, il y est ajouté.  Le second argument de cette fonction définit si le chemin sera ajouté au début ou à la fin de la définition actuelle du <varname>PATH</varname>.</para>
<para>L'utilisateur standard se voit ajouter seulement <filename>/usr/X11R6/bin</filename>  dans leurs chemins, alors que <emphasis>root</emphasis> se voit adjoindre quelques répertoires supplémentaires contenant les commandes systèmes.  Après avoir été utilisée, la fonction est supprimée par unset.</para>
</sect2>
<sect2 id="sect_11_02_03"><title>Sauvegarde à distance</title>
<para>L'exemple suivant est l'un de ceux que j'utilise pour faire mes sauvegardes des fichiers de mes livres.  Il emploie des clés SSH pour effectuer la connection à distance.  Deux fonctions sont définies, <command>buplinux</command> et <command>bupbash</command>, qui produisent chacune un fichier <filename>.tar</filename>, qui est alors compressé et envoyé vers le serveur distant.  Ensuite, la copie locale est supprimée.</para>
<para>Le dimanche, seul <command>bupbash</command> est exécuté.</para>
<screen>
#/bin/bash

LOGFILE="/nethome/tille/log/backupscript.log"
echo "Starting backups for `date`" &gt;&gt; "$LOGFILE"

buplinux()
{
DIR="/nethome/tille/xml/db/linux-basics/"
TAR="Linux.tar"
BZIP="$TAR.bz2"
SERVER="rincewind"
RDIR="/var/www/intra/tille/html/training/"

cd "$DIR"
tar cf "$TAR" src/*.xml src/images/*.png src/images/*.eps
echo "Compressing $TAR..." &gt;&gt; "$LOGFILE"
bzip2 "$TAR"
echo "...done." &gt;&gt; "$LOGFILE"
echo "Copying to $SERVER..." &gt;&gt; "$LOGFILE"
scp "$BZIP" "$SERVER:$RDIR" &gt; /dev/null 2&gt;&amp;1
echo "...done." &gt;&gt; "$LOGFILE"
echo -e "Done backing up Linux course:\nSource files, PNG and EPS images.\nRubbish removed." &gt;&gt; "$LOGFILE"
rm "$BZIP"
}

bupbash()
{
DIR="/nethome/tille/xml/db/"
TAR="Bash.tar"
BZIP="$TAR.bz2"
FILES="bash-programming/"
SERVER="rincewind"
RDIR="/var/www/intra/tille/html/training/"

cd "$DIR"
tar cf "$TAR" "$FILES"
echo "Compressing $TAR..." &gt;&gt; "$LOGFILE"
bzip2 "$TAR"
echo "...done." &gt;&gt; "$LOGFILE"
echo "Copying to $SERVER..." &gt;&gt; "$LOGFILE"
scp "$BZIP" "$SERVER:$RDIR" &gt; /dev/null 2&gt;&amp;1
echo "...done." &gt;&gt; "$LOGFILE"

echo -e "Done backing up Bash course:\n$FILES\nRubbish removed." &gt;&gt; "$LOGFILE"
rm "$BZIP"
}

DAY=`date +%w`

if [ "$DAY" -lt "2" ]; then
  echo "It is `date +%A`, only backing up Bash course." &gt;&gt; "$LOGFILE"
  bupbash
else
  buplinux
  bupbash
fi


echo -e "Remote backup `date` SUCCESS\n----------" &gt;&gt; "$LOGFILE"
</screen>
<para>Ce script est lancé par cron, c'est à dire sans intervention de l'utilisateur, c'est pour ça que le standard d'erreurs de la commande <command>scp</command> est redirigé sur <filename>/dev/null</filename>.</para>
<para>Il pourrait être observé que toutes les étapes peuvent être combinées en une commande telle que</para>
<cmdsynopsis><command>tar <option>c</option> <filename>dir_to_backup/</filename> | bzip2 | ssh <option>server</option> "cat &gt; <filename>backup.tar.bz2</filename>"</command></cmdsynopsis>
<para>Cependant, si vous êtes intéressé par les résultats intermédiaires, qui pourrait être récupérés en cas d'échec du script, ce n'est pas ce qu'il faut écrire.</para>
<para>L'expression</para>
<cmdsynopsis><command>command &amp;&gt; <filename>file</filename></command></cmdsynopsis>
<para>est équivalent à</para>
<cmdsynopsis><command>command &gt; <filename>file</filename> 2&gt;&amp;1</command></cmdsynopsis>
</sect2>

</sect1>
<sect1 id="sect_11_03"><title>Résumé</title>
<para>Les fonctions fournissent un moyen facile de grouper des commandes que vous avez besoin d'exécuter régulièrement.  Quand une fonction tourne, les paramètres positionnels sont ceux de la fonction.  Quand elles s'arrêtent, on retrouve ceux du programme appelant. Les fonctions sont comme des mini-scripts, et comme un script, elles génèrent un code retour.</para>
<para>Bien que ce chapitre soit court, il contient des connaissances importantes nécessaires pour atteindre le stade suprême de la paresse, ce qui est le but recherché de tout administrateur système.</para>

</sect1>

<sect1 id="sect_11_04"><title>Exercices</title>
<para>Voici quelques tâches utiles que vous pouvez réalisez avec des fonctions.</para>
<orderedlist>
<listitem><para>Ajoutez une fonction à votre fichier de configuration <filename>~/.bashrc</filename> qui automatise l'impression des pages man.  L'effet devrait être que quand vous taper <command>printman &lt;commande&gt;</command> les pages man de la commande sortent de l'imprimante.  Contrôler le fonctionnement avec une pseudo imprimante.</para>
<para>En plus, imaginez la possibilité pour l'usager de demander un numéro de section des pages man.</para>
</listitem>
<listitem><para>Créer un sous-répertoire dans votre répertoire racine dans lequel vous pouvez stocker des définitions de fonctions.  Y mettre quelques fonctions.  Des fonctions utiles peuvent être, parmi d'autres, celles qui permettent les mêmes commandes que DOS ou un UNIX commercial, ou vice versa.  Ces fonctions devraient être importées dans votre environnment Shell quand <filename>~/.bashrc</filename> est lu.</para>
</listitem>
</orderedlist>

</sect1>
</chapter>