<chapter id="chap_12">
<title>Trapper les signaux</title>
<abstract>
<para>Dans ce chapitre nous traiterons les sujets suivants&nbsp;:</para>
<para>
<itemizedlist>
<listitem><para>Signaux disponibles</para></listitem>
<listitem><para>Intérêt des signaux</para></listitem>
<listitem><para>Emploi de l'instruction <command>trap</command></para></listitem>
<listitem><para>Comment éviter que les usagers interrompent votre programme</para></listitem>
</itemizedlist>
</para>
</abstract>
<sect1 id="sect_12_01"><title>Signaux</title>
<sect2 id="sect_12_01_01"><title>Introduction</title>
<sect3 id="sect_12_01_01_01"><title>Trouver la page man de signal</title>
<para>Votre système contient une page man qui liste tous les signaux disponibles, mais selon votre système, elle peut être affichée de diverses façons.  Sur la plupart des Linux, ce sera avec <command>man <option>7</option> signal</command>.  Dans le doute, localisez la page man exacte ainsi que la section avec une commande comme</para>
<cmdsynopsis><command>man <option>-k</option> signal | grep <option>list</option></command></cmdsynopsis>
<para>ou l'option</para>
<cmdsynopsis><command>apropos signal | grep <option>list</option></command></cmdsynopsis>
<para>Les noms de signaux peuvent être trouvés avec <command>kill -l</command>.</para>
</sect3>
<sect3 id="sect_12_01_01_02"><title>Les signaux en direction de votre Shell Bash</title>
<para>En l'absence de toute trappe, un Shell Bash interactif ignore  <emphasis>SIGTERM</emphasis> et <emphasis>SIGQUIT</emphasis>.  <emphasis>SIGINT</emphasis> est récupéré et considéré, et si le contrôle de travaux est actif, <emphasis>SIGTTIN</emphasis>, <emphasis>SIGTTOU</emphasis> et <emphasis>SIGTSTP</emphasis> sont aussi ignorés.  Les commandes qui sont lancées en tant que résultat de substitution de commandes ignorent aussi ces signaux quand le clavier les a générés.</para>
<para><emphasis>SIGHUP</emphasis> par défaut fait quitter le Shell.  Un Shell interactif enverra un <emphasis>SIGHUP</emphasis> à tous les travaux, en exécution ou pas&nbsp;; voir la documentation sur l'intégrée <command>disown</command> si vous souhaitez désactiver ce comportement pour un processus particulier.  Utilisez l'option <option>huponexit</option> pour tuer tous les travaux à la réception du signal <emphasis>SIGHUP</emphasis> avec l'intégrée <command>shopt</command>.</para>
</sect3>

<sect3 id="sect_12_01_01_03"><title>Envoyer des signaux avec le Shell</title>
<para>Les signaux suivants peuvent être envoyés en utilisant le Shell Bash&nbsp;:</para>

<table id="tab_12_01" frame="all"><title>Les signaux de contrôle dans Bash</title>
<tgroup cols="2" align="left" colsep="1" rowsep="1">
<thead>
<row><entry>Combinaison standard de touches</entry><entry>sens</entry></row>
</thead>
<tbody>
<row><entry><keycap>Ctrl</keycap>+<keycap>C</keycap></entry><entry>Le signal d'interruption, envoie SIGINT à tous les travaux s'exécutant dans le Shell courant.</entry></row>
<row><entry><keycap>Ctrl</keycap>+<keycap>Y</keycap></entry><entry>Le caractère <emphasis>delayed suspend</emphasis>.  Provoque la suspension d'un processus actif quand il tente de lire son entrée depuis le terminal.  Le contrôle est rendu au Shell, l'utilisateur peut renvoyer en tâche de fond, réactiver ou tuer le processus.  'Delayed suspend' n'est pas une fonctionnalité connue de tous les systèmes.</entry></row>
<row><entry><keycap>Ctrl</keycap>+<keycap>Z</keycap></entry><entry>Le signal <emphasis>suspend</emphasis> envoie <emphasis>SIGTSTP</emphasis> à un programme en train de s'exécuter, donc il s'arrête et redonne le contrôle au Shell.</entry></row>
</tbody>
</tgroup>
</table>
<note><title>Paramètres du terminal</title>
<para>Vérifiez les paramètres <command>stty</command>.  La suspension et la reprise d'affichage sont généralement désactivées si vous utilisez des émulations <quote>modernes</quote> de terminaux.  Le <command>xterm</command> standard reconnaît <keycap>Ctrl</keycap>+<keycap>S</keycap> et <keycap>Ctrl</keycap>+<keycap>Q</keycap> par défaut.</para>
</note>
</sect3>

</sect2>

<sect2 id="sect_12_01_02"><title>Utilisation de signaux avec kill</title>
<para>La plupart des Shell récents, Bash inclus, ont une fonction intégrée  <command>kill</command>.  Dans Bash, à la fois les noms de signaux et leur numéro sont acceptés en tant qu'option, et les arguments peuvent être des identifiants de travaux ou de processus.  Un statut d'exécution peut être renvoyé avec l'option <option>-l</option>&nbsp;: zéro si au moins un signal a été envoyé correctement, différent de zéro si une erreur s'est produite.</para>
<para>Avec la commande <command>kill</command> de <filename>/usr/bin</filename>, votre système peut activer des options supplémentaires, telles que la capacité de tuer des processus provenant d'autres identifiants utilisateurs que le votre, et celle de spécifier les processus par leur nom, comme avec <command>pgrep</command> et <command>pkill</command>.</para>
<para>Les 2 commandes <command>kill</command> envoient le signal <emphasis>TERM</emphasis> si aucun n'est donné.</para>
<para>Voici une liste des principaux signaux&nbsp;:</para>

<table id="tab_12_02" frame="all"><title>Signaux courants de kill</title>
<tgroup cols="2" align="left" colsep="1" rowsep="1">
<thead>
<row><entry>Nom du signal</entry><entry>Valeur du signal</entry><entry>Effet</entry></row>
</thead>
<tbody>
<row><entry>SIGHUP</entry><entry>1</entry><entry>Suspend</entry></row>
<row><entry>SIGINT</entry><entry>2</entry><entry>Interruption depuis le clavier</entry></row>
<row><entry>SIGKILL</entry><entry>9</entry><entry>signal kill</entry></row>
<row><entry>SIGTERM</entry><entry>15</entry><entry>signal d'arrêt d'exécution</entry></row>
<row><entry>SIGSTOP</entry><entry>17,19,23</entry><entry>Stoppe le processus</entry></row>
</tbody>
</tgroup>
</table>
<note><title>SIGKILL et SIGSTOP</title>
<para><emphasis>SIGKILL</emphasis> et <emphasis>SIGSTOP</emphasis> ne peuvent pas être trappés, bloqués ou ignorés.</para>
</note>
<para>Pour tuer un processus ou une série de processus, il est de bon sens de commencer par essayer avec le signal le moins dangereux, <emphasis>SIGTERM</emphasis>.  De cette façon, les programmes qui se soucient d'un arrêt correct ont une chance de suivre les procédures qui leur ont été demandé d'exécuter à la réception du signal <emphasis>SIGTERM</emphasis>, tel que purger et fermer les fichiers ouverts.  Si vous envoyez un <emphasis>SIGKILL</emphasis> à un processus, vous retirez toute chance au processus d'effectuer un arrêt soigné, ce qui peut avoir des conséquences néfastes.</para>
<para>Mais si l'arrêt soigné ne fonctionne pas, le signal <emphasis>INT</emphasis> ou <emphasis>KILL</emphasis> peut être le seul moyen.  Par exemple,quand un processus ne meurt pas avec  <keycap>Ctrl</keycap>+<keycap>C</keycap>, c'est mieux d'utiliser <command>kill <option>-9</option></command> sur cet ID de processus&nbsp;:</para>
<screen>
<prompt>maud: ~&gt;</prompt> <command>ps <option>-ef</option> | grep <parameter>stuck_process</parameter></command>
maud    5607   2214  0 20:05 pts/5    00:00:02 stuck_process

<prompt>maud: ~&gt;</prompt> <command>kill <option>-9</option> <parameter>5607</parameter></command>

<prompt>maud: ~&gt;</prompt> <command>ps <option>-ef</option> | grep <parameter>stuck_process</parameter></command>
maud    5614    2214 0 20:15 pts/5    00:00:00 grep stuck_process
[1]+ Killed             stuck_process
</screen>
<para>Quand un processus démarre plusieurs instances, <command>killall</command> peut être plus facile.  Elle prend la même option que la commande <command>kill</command>, mais s'applique à toutes les instances d'un processus donné.  Tester cette commande avant de l'employer dans un environnement de production, parce qu'elle pourrait ne pas fonctionner comme attendu sur certains UNIX commerciaux.</para> 
</sect2>

</sect1>
<sect1 id="sect_12_02"><title>Piéger les signaux</title>
<sect2 id="sect_12_02_01"><title>Généralité</title>
<para>Il peut y avoir des situations ou vous ne souhaitez pas que les usagers de vos scripts quittent abruptement par une séquence de touches du clavier, par exemple parce qu'une entrée est en attente ou une purge est à faire.  L'instruction <command>trap</command> trappe ces séquences et peut être programmée pour exécuter une liste de commandes à la récupération de ces signaux.</para>
<para>La syntaxe de l'instruction <command>trap</command> est directe&nbsp;:</para>
<cmdsynopsis><command>trap [COMMANDS] [SIGNALS]</command></cmdsynopsis>
<para>Ceci indique à la commande <command>trap</command> de récupérer les  <emphasis>SIGNAUX</emphasis> listés, qui peuvent être des noms de signaux avec ou sans le préfixe  <emphasis>SIG</emphasis>, ou des numéros de signaux.  Si un signal est <emphasis>0</emphasis> ou <emphasis>EXIT</emphasis>, les  <command>COMMANDES</command> sont exécutées quand le Shell se finit.  Si l'un des signaux est <emphasis>DEBUG</emphasis>, la liste des  <command>COMMANDES</command> est exécutée après chaque commande simple.  Un signal peut être aussi spécifié pour <emphasis>ERR</emphasis>&nbsp;; dans ce cas <command>COMMANDES</command> sont exécutées chaque fois qu'une commande simple s'achève avec un statut différent de zéro.  Notez que ces commandes ne seront pas exécutées quand le statut d'exécution différent de zéro vient d'une instruction <command>if</command>, ou d'une boucle   <command>while</command> ou <command>until</command>.  Aucune ne sera exécutée si un <emphasis>AND</emphasis> (&amp;&amp;) ou un  <emphasis>OR</emphasis> (||) logique donne un statut d'exécution différent de zéro, ou quand le code retour d'une commande est inversé par l'opérateur <emphasis>!</emphasis>.</para>
<para>Le statut renvoyé par la commande <command>trap</command> elle-même est zéro à moins qu'un signal invalide ait été spécifié.  La commande <command>trap</command> admet des options qui sont documentées dans les pages info de Bash.</para>
<para>Voici un exemple très simple, récupérant  <keycap>Ctrl</keycap>+<keycap>C</keycap> frappé par l'usager, ce qui déclenche l'affichage d'un message.  Quand vous essayez de tuer ce programme sans spécifier le signal <emphasis>KILL</emphasis>, rien ne se produit&nbsp;:</para>
<screen>
#!/bin/bash
# traptest.sh

trap "echo Booh!" SIGINT SIGTERM
echo "pid is $$"

while :                 # Ceci est équivalent à « while true ».
do
        sleep 60        # Ce script ne fait pas vraiment quelque chose.
done
</screen>

</sect2>
<sect2 id="sect_12_02_02"><title>Comment Bash interprète trap</title>
<para>Quand Bash reçoit un signal pour lequel un piège a été défini durant l'exécution d'une commande, le piège ne sera mis en oeuvre que une fois que la commande aura terminé.  Quand Bash attend après une commande asynchrone via l'intégrée <command>wait</command>, la réception d'un signal pour lequel un piège a été défini fera que l'intégrée  <command>wait</command> redonnera la main immédiatement avec un statut d'exécution supérieur à 128, immédiatement après quoi le piège est exécuté.</para>
</sect2>
<sect2 id="sect_12_02_03"><title>Plus d'exemples</title>
<sect3 id="sect_12_02_03_01"><title>Détecter quand une variable est utilisée</title>
<para>Quand vous débuggez de longs scripts, vous pouvez vouloir donner à une variable l'attribut <emphasis>trace</emphasis> et trapper les messages <emphasis>DEBUG</emphasis> pour cette variable.  Normalement vous déclarez juste une variable avec une affectation du genre <command><varname>VARIABLE</varname>=valeur</command>.  En remplaçant la déclaration de la variable avec les lignes suivantes vous pouvez obtenir des informations intéressantes sur ce que fait votre script&nbsp;:</para>
<screen>
declare -t VARIABLE=valeur

trap "echo VARIABLE est utilisée ici." DEBUG

# suite du script
</screen>
</sect3>
<sect3 id="sect_12_02_03_02"><title>Purger les déchets de traitements avant de quitter</title>
<para>La commande <command>whatis</command> repose sur une base de données qui est régulièrement reconstruite avec le script <filename>makewhatis.cron</filename> lancé par cron&nbsp;:</para>
<screen>
#!/bin/bash

LOCKFILE=/var/lock/makewhatis.lock

# Le makewhatis précédent devrait s'être exécuté avec succès&nbsp;:

[ -f $LOCKFILE ] &amp;&amp; exit 0

# Avant de quitter, éliminer les fichiers verrous.

trap "{ rm -f $LOCKFILE ; exit 255; }" EXIT

touch $LOCKFILE
makewhatis -u -w
exit 0
</screen>
</sect3>
</sect2>

</sect1>
<sect1 id="sect_12_03"><title>Résumé</title>
<para>Des signaux peuvent être envoyés à votre programme avec la commande <command>kill</command> ou des raccourcis clavier.  Ces signaux peuvent être récupérés, sur quoi une action peut être effectuée, avec l'instruction <command>trap</command>.</para>
<para>Certains programmes ignorent les signaux.  Le seul signal qu'aucun programme ne peut ignorer est <emphasis>KILL</emphasis>.</para>

</sect1>
<sect1 id="sect_12_04"><title>Exercices</title>
<para>Quelques exemples&nbsp;:</para>
<orderedlist>
<listitem><para>Ecrire un script qui écrit une image de démarrage système sur une disquette avec l'outil <command>dd</command>.  Si l'utilisateur essaye d'interrompre avec <keycap>Ctrl</keycap>+<keycap>C</keycap>, afficher un message disant que cette action rendra la disquette inutilisable.</para></listitem>
<listitem><para>Ecrire un script qui automatise l'installation d'un paquetage tiers de votre choix.  Le paquetage doit être téléchargé par INTERNET.  Il doit être décompressé, désarchivé et compilé si ces actions sont appropriées.  Seule l'opération d'installation du paquetage ne devrait pas être interruptible.</para>
</listitem>
</orderedlist>

</sect1>
</chapter>

Site hébergé sur un Cloud Public IKOULA Ikoula