Catégories

Utilisation de inotifywait dans des scripts Shell

Inotify (intégré au noyau Linux depuis la version 2.6.13 de Juin 2005) est un mécanisme permettant d’être immédiatement averti lors d’événements (création, lecture, écriture, …) sur des fichiers ou répertoires.

Plusieurs programmes exploitent cette fonctionnalité, dont « inotifywait » qui permet facilement de l’intégrer à des scripts Shell. Voici quelques exemples d’utilisation:

Installation

  • Sous Debian GNU/Linux:
# apt-get install inotify-tools

Attention la version 3.12 présente dans Debian Lenny a un bug avec l’affichage des dates/heures.
voir http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=458132

  • Sous RedHat Linux:
# yum install inotify-tools

1ère version

Prenons l’exemple simple de vouloir surveiller les fichiers créés dans /tmp:

#!/bin/sh

# CONFIGURATION
DIR="/tmp"
EVENTS="create"

# MAIN
inotifywait -m -e $EVENTS --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %f' $DIR |
while read date time file
do
    echo "$date $time Fichier créé: $file"
done

Cela affichera:

$ ./inotify1.sh
Setting up watches.
Watches established.
2010-08-05 11:15:01 Fichier créé: rtm.flock
2010-08-05 11:15:10 Fichier créé: #sql_1354_0.MYI
2010-08-05 11:15:10 Fichier créé: #sql_1354_0.MYD
(...)

Cette façon de faire est simple mais a quelques inconvénients, que l’on constate avec les commandes suivantes:

$ nohup ./inotify1.sh > /tmp/inotify.log 2>&1
[1] 22350

$ ps f|grep inotify|grep -v grep
22350 pts/2    S      0:00  _ /bin/sh ./inotify1.sh
22351 pts/2    S      0:00  |   _ inotifywait -m -e create --timefmt %Y-%m-%d %H:%M:%S --format %T %f /tmp
22352 pts/2    S      0:00  |   _ /bin/sh ./inotify1.sh

$ kill 22350
[1]+  Complété              nohup ./inotify1.sh > /tmp/inotify.log 2>&1

$ ps f|grep inotify|grep -v grep
22352 pts/2    S      0:00 /bin/sh ./inotify1.sh
22351 pts/2    S      0:00 inotifywait -m -e create --timefmt %Y-%m-%d %H:%M:%S --format %T %f /tmp

En effet:

  • la syntaxe inotifywait (…) | while read créé un sous-processus shell
  • ce sous-processus, ainsi que la commande inotifywait ne sont pas tués si l’on tue le script shell

Amélioration avec un tube nommé

Afin d’éviter les inconvénient de la première version, il peut être pratique d’utiliser un tube nommé (named pipe ou encore FIFO) pour que le shell en cours reçoive les événements inotify:

Exemple:

#!/bin/sh

# CONFIGURATION
DIR="/tmp"
EVENTS="create"
FIFO="/tmp/inotify2.fifo"

# FUNCTIONS
on_exit() {
    kill $INOTIFY_PID
    rm $FIFO
    exit
}

# MAIN
if [ ! -e "$FIFO" ]
then
    mkfifo "$FIFO"
fi

inotifywait -m -e "$EVENTS" --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %f' "$DIR" > "$FIFO" &
INOTIFY_PID=$!

trap "on_exit" 2 3 15

while read date time file
do
    echo "$date $time Fichier créé: $file"
done < "$FIFO"

on_exit

Le comportement est maintenant le suivant:

$ nohup ./inotify2.sh > /tmp/inotify.log 2>&1 &
[1] 23144

$ ps f|grep inotify|grep -v grep
23144 pts/2    S      0:00  _ /bin/sh ./inotify2.sh
23146 pts/2    S      0:00  |   _ inotifywait -m -e create --timefmt %Y-%m-%d %H:%M:%S --format %T %f /tmp

$ kill 23144
[1]+  Done                    nohup ./inotify2.sh > /tmp/inotify.log 2>&1 &

$ ps f|grep inotify|grep -v grep
$

Détail:

  • L’utilisation du tube nommé $FIFO permet de lancer la commande inotifywait en tâche de fond et de récupérer son PID
  • Il est alors possible d’installer un « trap », en l’occurrence la fonction « on_exit() », qui va tuer la commande inotifywait (et aussi effacer le tube nommé) lorsque le script est tué par un des signaux 2 (INT), 3 (QUIT) ou 15 (TERM).
  • Les événements sont lus via le tube nommé, sans création d’un shell, grâce à la fin du « while »: done < $FIFO

Pour aller un peu plus loin: opérations non bloquantes

Il reste un comportement qui peut-être gênant dans certains cas: les opérations de la boucle de lecture sont bloquantes, chaque événement devra attendre que le précédent est terminé pour être traité.

Voici une manière d’éviter cela:

#!/bin/sh

# CONFIGURATION
DIR="/tmp"
EVENTS="create"
FIFO="/tmp/inotify2.fifo"

# FUNCTIONS
on_exit() {
    kill $INOTIFY_PID
    rm $FIFO
    exit
}

on_event() {
    local date=$1
    local time=$2
    local file=$3

    sleep 5

    echo "$date $time Fichier créé: $file"
}

# MAIN
if [ ! -e "$FIFO" ]
then
    mkfifo "$FIFO"
fi

inotifywait -m -e "$EVENTS" --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %f' "$DIR" > "$FIFO" &
INOTIFY_PID=$!

trap "on_exit" 2 3 15

while read date time file
do
    on_event $date $time $file &
done < "$FIFO"

on_exit

Détail:

  • La fonction on_event est lancée en tâche de fond (un sous-processus shell est en fait créé)
  • les opérations sont maintenant exécutées en parallèle; pratique pour les traitement longs ou pouvant bénéficier de multiples processeurs

Références

http://fr.wikipedia.org/wiki/Inotify
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=458132

12 commentaires pour Utilisation de inotifywait dans des scripts Shell

  • fabitux

    Bonjour,
    Merci pour ces scripts bien utiles.
    J’ai adapté pour que quand un fichier est créé, il le copie vers un autre répertoire.
    Le problème : inotify est tellement réactif qu’il n’attend pas que le fichier soit ENTIEREMENT créé pour agir (si le fichier créé est volumineux, avec un long temps d’enregistrement sur le disque).

    J’ai testé en ajoutant : EVENTS= »create », »modify », mais le problème est le même.

    Voyez-vous une solution à ce problème ?

    à+
    fabitux

    • Dans ce cas, il faut utiliser l’évènement « CLOSE_WRITE ».
      Comme son nom l’indique, celui-ci est déclenché lors de la fermeture d’un fichier ouvert en écriture, ce qui est normalement le cas à la fin d’un copie.
      Je l’utilise pour automatiser des transfert de fichiers et cela fonctionne parfaitement.

      Salutations.

  • fabitux

    erratum :
    Finalement, je n’ai pas un réel besoin d’attendre. Voire, au contraire, ça m’arrange qu’il agisse au fur et à mesure de la création d’un gros fichier.
    à+
    fabitux

  • Nagame

    Bonjour,

    Avant tout, félicitation pour cet article !
    C’est exactement ce que je cherchais.

    Une difficulté supplémentaire dans mon cas : je dois déclencher l’enregistrement une fois la copie de fichiers dans un dossier terminé (1 ou plusieurs). Le but est d’obtenir un enregistrement par « lot » de copie.

    L’event « close_write » est effectivement de mise. Mais une fois dans « on_event », quel est la meilleur solution ? Un wait ? re-tester si un fichier a été copié ?

    Bref, pas évident :/

    • Vous pourriez utiliser un fichier témoin, c’est-à-dire un fichier au nom prédéfini qui sera créé en dernier (une fois tous les fichiers copiés dans le dossier) et qui est le seul à déclencher le traitement (à filtrer avec les arguments d’inotifywait ou dans on_event()).
      Cela implique que la façon dont sont copiés les fichiers permette ce principe, donc plus ou moins automatisé et/ou que vous pouvez contrôler.

      • patrick L

        j’ai le meme probleme pour faire un archivage de photos ou de téléchargements. en quantité importante comme les photos de vacances sur deux semaines…

        alors je charge les photos dans un dossier et alors inotify surveille le dossier et sous dossiers photos. là les events close_write lancent la copie… jusque là pas de probleme.

        mais je voudrais sans me poser de question mettre dans une archive à la date des exifs du fichier.

        le probleme est le meme que le monsieur au dessus… à savoir qu’il y a une grosse quantité de fichiers et aussi en poids total des fichiers…

        et surtout les fichiers tar.gz ne peuvent pas ajouter de fichier et je suis coincé… d’accord je peux les copier simplement dans un dossier du disque de sauvegarde.. et ensuite lancer la sauvegarde et alors supprimer le dossier temporaire pour reprendre la suite.

        ou alors faire une archive tar sans gunzip, et la gzipper à un moment de la journée… dans tous les cas les fichiers sont secourus. mais bon risque est que soit la creation de l’archive se passe mal et on supprime le dossier …

        ou alors le disque de sauvegarde est plein et la creation de l’archive plante le disque de secours ce qui serait un desastre.

        Ou alors que la compression elle aussi se passe mal..

        Ou alors compresser selon une archive tar sans comprimer. Mais ca fait alors une copie de taille identique des photos dans l’archive.

  • nestof

    Bonsoir,

    J’ai remarqué en exécutant le script que la ligne « on_event $date $time $file & » entraine un processus zombie lorsque les commandes de la fonction on_event(..) sont terminées.
    Y-a-t-il un moyen d’éviter cela ?

    • Bonne remarque, je n’avais pas fait attention à ce comportement. Cela est dû au lancement des on_event() en arrière-plan (avec &).

      Pour l’éviter, il faudrait ajouter un traitement des signaux SIGCHLD (par exemple, dans un « trap … SIGCHLD », faire un « wait » sur le PID du sous-processus qui a terminé).

      S’il n’y a pas besoin de lancer les tâches en parallèle, il suffit de désactiver le lancement en arrière-plan (supprimer le & des on_event()) pour ne plus avoir de zombies.

      Sinon, j’ai remarqué que « bash » a l’air de traiter automatiquement les SIGCHLD lorsqu’il est explicitement appelé (« bash script » ou shebang « #!/bin/bash »). Ce n’est sûrement pas le plus portable, mais si cela peut dépanner le temps d’avoir une solution élégante…

      • nestof

        Merci, je vais regarder du côté de « trap … SIGCHLD »

      • patrick L

        merci de vos informations utiles.

        sinon j’ai pas compris le principe de ce sigchild ni d’ailleurs le trap… c’est une sorte de chien de garde des processus qui envoient des signaux en disant je suis là… si j’ai bien compris ce signal alors comment je sais le delai d’envoie des signaux « en vie » ?

        exemple

        je lance une copie de gros fichier, le processsus envoie les messages… et à la fin logiquement plus rien. et alors au bout de N secondes sans message et bien je tue le processus. mais si c’est trop court pour la copie de fichier et bien probleme.

  • patrick L

    j’ai une autre question… je voudrais faire un piege contre des consultation de données importantes… genre des comptes en banque.. en dernier recours si jamais le reste etait cassé.

    j’ai un dossier comptes ou importants à surtout pas lire… je fais alors

    inotifywait -e access -m -r –format « %w %f » ~/comptes/ | while read dossier fichier ; do
    mv $dossier$fichier /media/quelque/part/ ; done

    de facon à ce que lorsque le pirate consulte, le fichier soit déplacé ailleurs.

  • patrick L

    bonsoir

    sur le pirate j’ai pas encore la solution pour déplacer le fichier juste avant la lecture.

    Sur la sauvegarde automatique dans une archive j’ai trouvé une solution viable… Au moment du close-write je lance la sauvegarde dans une archive tar -uvf /media/externe/archive-$(date +%Y-%m-%d).tar.gz ensuite les fichiers s’ajoutent… et donc les fichiers sont sauvés et tous les soirs je fais gzip du fichier tar pour en faire un .tar.gz. que l’on restaure de la maniere classique.

    j’ai trouvé aussi une astuce anti fausse manip. En supprimant un fichier dans un dossier protégé alors on lance un fuser -km pour tuer les processus utilisant le dossier monté donc un disque externe.

    j’ai pas encore fait les codes mais j’ai fait des essais en ligne.

Laisser un commentaire sur patrick L

 

 

 

Vous pouvez utiliser ces tags HTML

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre user="" computer="" escaped="">