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 fonctionexecute()
- 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 bouclewhile 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
- Ce billet sâinspire de ce post : Execute a Command Whenever File or Directory Changes
- Execute a Command Whenever File or Directory Changes
኿