cClaude.rocks ☕ Le blog

[Nouvelles technologies du libre, sciences et coups de gueule…]

Menu

Le but de ce billet est de pouvoir exĂ©cuter une tĂąche (un script bash par exemple) lorsque le contenu d’un rĂ©pertoire a Ă©tĂ© modifiĂ©.

Pour cela on s’appuiera sur le paquet inotify-tools.


ඏ

Une solution basée sur inotify

Dans la plupart des cas, inotify est la solution la plus efficace et la plus raisonnable pour suivre les changements de fichiers dans les répertoires que l'on souhaite surveiller. Il a été intégré au code commun du noyau Linux en 2005, ce qui en fait un standard dans toutes les distributions Linux.

Notez que inotify a quelques limitations. Le principal problĂšme est qu’il nĂ©cessite que le noyau soit au courant de tous les Ă©vĂ©nements pertinents du systĂšme de fichiers, ce qui n’est pas toujours possible par exemple pour NFS, et de maniĂšre gĂ©nĂ©rale pour les rĂ©pertoires partagĂ©s. Vous devez tenir compte de ces limitations.

inotifywait et inotifywatch sont des commandes du paquet inotify-tools qui permettent d’utiliser le sous-systĂšme inotify. inotifywait attend les Ă©vĂ©nements du systĂšme de fichiers et agit dĂšs qu’il en reçoit un. inotifywatch collecte les statistiques d’utilisation du systĂšme de fichiers et donne le nombre de chaque Ă©vĂ©nement configurĂ©. Dans la suite, nous ne nous intĂ©resserons qu’à inotifywait.


ඏ

Exemple de script Bash utilisant inotify

Voyons tout de suite notre script :

#!/bin/bash

if [ -z "$(which inotifywait)" ]; then
    echo "inotifywait not installed."
    echo "In most distros, it is available in the inotify-tools package."
    exit 1
fi

counter=0;

function execute() {
    counter=$((counter+1))
    echo "Detected change n. $counter"
    eval "$@"
}

inotifywait --recursive --monitor --format "%e %w%f" \
--event modify,move,create,delete ./ \
| while read changed; do
    echo $changed
    execute "$@"
done

Nous n’allons pas nous lancer dans une explication de chaque ligne de notre script. Voici une prĂ©sentation des parties les plus importantes :

  • Tout ce qui suit le nom du script est interprĂ©tĂ© comme la commande Ă  exĂ©cuter, grĂące Ă  la variable spĂ©ciale $@ entre guillemets passĂ©e Ă  la fonction execute()
  • Chaque ligne de sortie de inotifywait (formatĂ©e comme spĂ©cifiĂ© par le drapeau --format) est temporairement stockĂ©e dans la variable $changed, grĂące au tuyau entre la commande inotifywait et la boucle while read.
  • Au lieu de se terminer aprĂšs avoir reçu un seul Ă©vĂ©nement (ce qui est le cas par dĂ©faut), inotifywait s’exĂ©cute indĂ©finiment grĂące Ă  l’option --monitor. C’est un gain de performance impressionnant comparĂ© au redĂ©marrage d'inotifywait aprĂšs chaque Ă©vĂ©nement.
  • L’option --event spĂ©cifie les Ă©vĂ©nements Ă  surveiller dans le rĂ©pertoire courant et les sous-rĂ©pertoires, en surveillant de maniĂšre rĂ©cursive jusqu’à une profondeur illimitĂ©e comme demandĂ© par l’option --recursive.
  • Les sous-rĂ©pertoires nouvellement crĂ©Ă©s seront Ă©galement surveillĂ©s.

Sauvegardons notre script sous le nom de inotifyTest.sh dans le répertoire à surveiller, puis ouvrons deux terminaux. Nous utiliserons le premier pour voir comment notre script se comporte et le second pour effectuer des opérations dans le répertoire surveillé.

Démarrons le script dans le premier terminal. Dans ce cas, la commande à exécuter est un simple echo :

./inotifyTest.sh echo "Running our command..."
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.

Essayons ensuite d’effectuer quelques opĂ©rations sur les fichiers et les rĂ©pertoires dans le second terminal :

touch newFile.txt

echo "Some content" >> newFile.txt

rm newFile.txt

mkdir testDir

cd testDir
touch anotherFile.txt
cd ..

rm -fR testDir

Pendant ce temps, le premier terminal a enregistré toutes les opérations correctement.

Incidemment, nous notons que la derniÚre commande rm -fR testDir a en fait effectuée deux opérations :

CREATE ./newFile.txt
Detected change n. 1
Running our command...
MODIFY ./newFile.txt
Detected change n. 2
Running our command...
DELETE ./newFile.txt
Detected change n. 3
Running our command...
CREATE,ISDIR ./testDir
Detected change n. 4
Running our command...
CREATE ./testDir/anotherFile.txt
Detected change n. 5
Running our command...
DELETE ./testDir/anotherFile.txt
Detected change n. 6
Running our command...
DELETE,ISDIR ./testDir
Detected change n. 7
Running our command...

Tout fonctionne donc comme prĂ©vu. Cependant, nous devons faire attention Ă  nos cas d’utilisation rĂ©els, comme nous le verrons dans la suite.


ඏ

Affinage du script

Le script peut dĂ©tecter beaucoup plus d’évĂ©nements que nous ne le souhaiterions. Par exemple, ouvrons un fichier texte prĂ©existant test.txt avec xed, apportons-y une modification et enregistrons-le. Nous nous attendions Ă  un seul Ă©vĂ©nement, mais notre script en dĂ©tecte quatre :

./inotifyTest.sh echo "Running our command..."
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
CREATE ./.goutputstream-7FUFN1
Detected change n. 1
Running our command...
MODIFY ./.goutputstream-7FUFN1
Detected change n. 2
Running our command...
MOVED_FROM ./.goutputstream-7FUFN1
Detected change n. 3
Running our command...
MOVED_TO ./test.txt
Detected change n. 4
Running our command...

Ce comportement inattendu est dĂ» Ă  l’utilisation de fichiers temporaires dont nous ne sommes gĂ©nĂ©ralement pas conscients. Le mĂȘme problĂšme se pose avec d’autres Ă©diteurs de terminaux largement utilisĂ©s, tels que nano.

Fondamentalement, il existe deux approches pour rĂ©soudre ce problĂšme. La premiĂšre consiste Ă  restreindre le type d’évĂ©nements surveillĂ©s, Ă  savoir ceux indiquĂ©s par l’indicateur --event. La seconde consiste Ă  exclure les fichiers ou rĂ©pertoires non pertinents en utilisant l’option --exclude ou --excludei.

Par exemple, refaisons le mĂȘme test avec xed, mais excluons tous les fichiers et rĂ©pertoires cachĂ©s en ajoutant --exclude '/\.' aux paramĂštres de inotifywait. Ce drapeau accepte une expression rĂ©guliĂšre Ă©tendue POSIX, nous devons donc Ă©chapper les points. Voici le rĂ©sultat :

./inotifyTest.sh echo "Running our command..."
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
MOVED_TO ./test.txt
Detected change n. 1
Running our command...

Des quatre Ă©vĂ©nements prĂ©cĂ©demment dĂ©tectĂ©s, notre script n’a dĂ©tectĂ© cette fois que le dernier. C’est ce que nous voulions. En gĂ©nĂ©ral, nous devons analyser nos cas d’utilisation pour trouver les expressions rĂ©guliĂšres d’exclusion les plus appropriĂ©es.


ඏ

Nombre maximum de surveillances « inotify »

Dans la plupart des cas, notre script fonctionnera correctement. Cependant, il peut atteindre la limite du systĂšme pour le nombre d’observateurs de fichiers si le nombre de fichiers est considĂ©rable.

Essayons tail -f sur n’importe quel fichier ancien pour vĂ©rifier si notre systĂšme d’exploitation a dĂ©passĂ© la limite maximale de surveillance fixĂ©e par inotify :

tail -f /var/log/dmesg

L’implĂ©mentation interne de tail -f utilise le mĂ©canisme inotify pour surveiller les changements de fichiers. Si tout va bien, il affichera les dix derniĂšres lignes et fera une pause ; alors, abandonnons avec CTRL + c. Au lieu de cela, si nous n’avons plus de surveillance inotify, nous obtiendrons probablement cette erreur :

tail: inotify cannot be used, reverting to polling: Too many open files

sysctl permet de consulter la configuration actuelle :

sysctl fs.inotify
fs.inotify.max_queued_events = 16384
fs.inotify.max_user_instances = 128
fs.inotify.max_user_watches = 65536

Voyons ce que signifient ces valeurs :

  • max_queued_events : le nombre maximum d’évĂ©nements dans la file d’attente du noyau.
  • max_user_instances : le nombre maximum d’instances de surveillance, Ă©gal au nombre de rĂ©pertoires racines Ă  surveiller.
  • max_user_watches : le nombre maximum de rĂ©pertoires dans toutes les instances de surveillance.

Il est possible de modifier max_user_instances et max_user_watches mais il est gĂ©nĂ©ralement conseillĂ© de conserver la valeur par dĂ©faut de max_queued_events. L’augmentation de ces valeurs doit se faire avec prudence, en effet chaque observateur occupe 1 ko de mĂ©moire noyau sur les systĂšmes 64 bits, hors ce type de mĂ©moire n’est pas « swappable ».

Pour modifier la configuration de façon permanente, il faut Ă©diter le fichier /etc/sysctl.conf avec les permissions de root (sur les dĂ©rivĂ©s de Debian/RedHat), en modifiant les lignes suivantes ou en les ajoutant si elles n'existent pas. N’oubliez pas de remplacer n par la valeur souhaitĂ©e (le maximum est 524288) :

fs.inotify.max_queued_events = n
fs.inotify.max_user_instances = n
fs.inotify.max_user_watches = n

Rechargeons ensuite les paramÚtres de sysctl (sur les dérivés de Debian/RedHat) :

sysctl -p

ඏ

Étude de quelques exemples


Surveillance d’un seul fichier

La documentation de inotifywait donne l’exemple suivant :

#!/bin/sh
while ! inotifywait -e modify /var/log/syslog; do
  if tail -n1 /var/log/syslog | grep httpd; then
    kdialog --msgbox "Apache needs love!"
  fi
done

L’exemple utilise en rĂ©alitĂ© /var/log/messages mais depuis l’arrivĂ©e de systemd, ce fichier n’existe plus. Il se peut Ă©galement que le fichier /var/log/syslog puisqu’il n’est en rĂ©alitĂ© pas nĂ©cessaire Ă  systemd, mais qu’il permet un portage simple de certaine applications.

Dans ce cas, puisqu’on ne surveille qu’un seul fichier, il est possible de relacer Ă  chaque changement la commande inotifywait, cette mĂ©thode reste relativement efficace.


Synchronisation d’un rĂ©pertoire

Créer un fichier inotify-rsync.sh dans le dossier à synchroniser et rendez le exécutable :

touch inotify-rsync.sh
chmod +x inotify-rsync.sh

Inspirons-nous du premier script, pour faire la synchronisation du répertoire à chaque changement.

La variable destination est à adapter en fonction de vos besoins


#!/bin/bash
declare -r DESTINATION='user@192.168.2.73:/sdcard/Movies.local/Videos' # A adapter

if ! which inotifywait >/dev/null ; then
  echo >&2 "inotifywait not installed."
  echo >&2 "In most distros, it is available in the inotify-tools package."
  exit 1
fi

function run_action() {
  rsync --delete-excluded --archive --compress --copy-links --delete --progress \
        --recursive --update --verbose -e 'ssh -p 2223' \
        . \
        "${DESTINATION}"
}

inotifywait --recursive --monitor --format "%e %w%f" \
            --event modify,move,create,delete . | while read changed; do
  echo $changed
  run_action
done

Les problĂšmes liĂ©s aux fichiers temporaires n’est pas gĂ©rĂ© ici, mais ce n’est pas le seul problĂšme.

La configuration laisse penser qu’on synchronise un dossier contenant des vidĂ©os. Le script tel qu’il est Ă©crit risque de relancer la synchronisation si des nouvelles vidĂ©os sont ajoutĂ©es alors que la synchronisation n’est pas terminĂ©e. Dans ce genre de situation, il faudra donc prĂ©voir un systĂšme pour ne pas relancer la synchronisation tant que la prĂ©cĂ©dente n’est pas terminĂ©e.

Cela peut se faire par le dĂ©pĂŽt d’un fichier .lock.

Il faudrait Ă©galement s’assurer que la machine de destination soit joignable et si elle ne l’est pas prĂ©voir un mĂ©canisme pour traiter la synchronisation ultĂ©rieurement.


ඏ

Liens

኿


â„č 2006 - 2024 | 🏠 Accueil du domaine | 🏡 Accueil du blog