À travers un exemple concret, l’article décrit comment surveiller des fichiers avec l’utilitaire inotifywait sous Linux avec Bash
inotifywait
est un utilitaire en ligne de commande qui se trouve dans le paquet inotify-tools
sur Debian et Ubuntu.
Pour l’installer, il suffit de faire :
sudo apt install inotify-tools
Il est également utile d’avoir l’utilitaire lsof
:
sudo apt install lsof
Voici le code source, les explications viennent après.
#!/bin/bash
function step {
local message="$1"
local date=$(date "+%Y-%m-%d %T%z")
shift
printf '[%s] %s: %s\n' "$date" "$message" "$*"
"$@"
}
has_extension() {
local file_extension="${1##*.}"
shift
for extension in $*
do
[ "$file_extension" == "$extension" ] && return 0
done
return 1
}
clean_files() {
local filename="$1"
[ -f "$filename.gz" ] && step "REMOVE" rm "$filename.gz"
[ -f "$filename.br" ] && step "REMOVE" rm "$filename.br"
[ -f "$filename.webp" ] && step "REMOVE" rm "$filename.webp"
}
jpg_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.webp" && return
step "JPEG-WEBP" \
cwebp -quiet \
-pass 10 \
-af \
-sharp_yuv \
"${filename}" \
-o "${filename}.webp"
}
png_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.webp" && return
step "PNG-WEBP" \
cwebp -quiet \
-near_lossless 50 \
-q 100 \
-alpha_q 50 \
-mt \
"${filename}" \
-o "${filename}.webp"
}
text_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.gz" && return
step "GZIP" zopfli --i127 "$filename"
step "BROTLI" brotli --best --force --no-copy-stat "$filename"
step "BROTLI" chmod 644 "$filename.br"
}
cd /var/www/html
inotifywait \
--quiet \
--monitor \
--recursive \
--event modify,create,delete \
--exclude '\.(gz|br|webp)$' \
--format '%w%f' \
. \
| while read filename
do
has_extension "$filename" html css js svg xml json xsl xsd txt ps png jpg ico \
|| continue
(test -f "$filename" && lsof -- "$filename") && continue
if [ ! -f "$filename" ]
then
clean_files "$filename"
elif has_extension "$filename" html css js svg xml json xsl xsd txt ps ico
then
text_precompress "$filename"
elif has_extension "$filename" jpeg jpg
then
jpg_precompress "$filename"
elif has_extension "$filename" png
then
png_precompress "$filename"
fi
done
function step {
local message="$1"
local date=$(date "+%Y-%m-%d %T%z")
shift
printf '[%s] %s: %s\n' "$date" "$message" "$*"
"$@"
}
La fonction step permet de garder une trace des actions effectuées par le script.
Elle affiche ces informations sous la forme suivante :
[2020-03-24 21:31:29+0000] GZIP: zopfli --i127 ./shell/surveiller-fichiers-inotifywait/index.xml
[2020-03-24 21:31:30+0000] BROTLI: brotli --best --force --no-copy-stat ./shell/surveiller-fichiers-inotifywait/index.xml
[2020-03-24 21:31:30+0000] BROTLI: chmod 644 ./shell/surveiller-fichiers-inotifywait/index.xml.br
D’abord la date et l’heure, suivies du nom de l’action pour enfin terminer par la commande exécutée.
Notez la subtile différence entre $* et $@ ! "$*"
permet de placer tous les paramètres existants dans un seul paramètre. A contrario "$@"
est un cas particulier puisqu’il reproduit tous les paramètres à leur exact position.
has_extension() {
local file_extension="${1##*.}"
shift
for extension in $*
do
[ "$file_extension" == "$extension" ] && return 0
done
return 1
}
La fonction has_extension permet de tester si le nom de fichier passé en premier paramètre se termine par l’une des extensions qui suivent.
clean_files() {
local filename="$1"
[ -f "$filename.gz" ] && step "REMOVE" rm "$filename.gz"
[ -f "$filename.br" ] && step "REMOVE" rm "$filename.br"
[ -f "$filename.webp" ] && step "REMOVE" rm "$filename.webp"
}
Cette fonction sert simplement à supprimer des fichiers générés par le script au cas ou le fichier d’origine aurait été supprimé.
jpg_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.webp" && return
step "JPEG-WEBP" \
cwebp -quiet \
-pass 10 \
-af \
-sharp_yuv \
"${filename}" \
-o "${filename}.webp"
}
La fonction jpg_precompress se charge de convertir un jpeg en webp.
Elle commence tout d’abord par vérifier s’il est nécessaire de convertir le jpeg en comparant sa date de dernière modification avec celle d’un éventuel webp correspondant. Le test est nécessaire car une opération sur un fichier peut générer plusieurs types d’événements. Il faut éviter de faire plusieurs fois le travail car il s’agit d’un traitement lourd.
Les paramètres sont poussés à l’extrême afin de profiter du meilleur niveau de qualité et de la meilleure compression (l’opération n’est effectuée qu’une seule fois).
La fonction utilise les paramètres de compression destructive de webp.
png_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.webp" && return
step "PNG-WEBP" \
cwebp -quiet \
-near_lossless 50 \
-q 100 \
-alpha_q 50 \
-mt \
"${filename}" \
-o "${filename}.webp"
}
Ce qui a été dit pour jpg_precompress s’applique également à png_precompress.
La différence réside dans les paramètres qui tentent de faire une compression sans perte (ou à la rigueur avec des pertes non visibles).
text_precompress() {
local filename="$1"
test "${filename}" -ot "${filename}.gz" && return
step "GZIP" zopfli --i127 "$filename"
step "BROTLI" brotli --best --force --no-copy-stat "$filename"
step "BROTLI" chmod 644 "$filename.br"
}
La fonction text_precompress s’occupe de précompresser les fichiers texte (HTML, XML, JS, CSS etc.) selon 2 formats.
Pour générer le format gzip (le plus largement supporté par les navigateurs), elle utilise l’utilitaire zopfli. Ce dernier permet des taux de compression plus élevés que gzip tout en générant des fichiers parfaitement compatibles avec lui. L’opération prend en revanche beaucoup plus de temps qu’avec gzip mais cette opération étant faite une fois pour toute, ce n’est pas gênant.
Pour générer le format brotli (supporté par Chrome et Firefox mais uniquement sur des connexions https) offre des taux de compression plus élevés qu’avec zopfli. On l’utilise également avec les paramètres les plus poussés.
inotifywait \
--quiet \
--monitor \
--recursive \
--event modify,create,delete \
--exclude '\.(gz|br|webp)$' \
--format '%w%f' \
. \
| while read filename
do
# ...
done
Le script utilise inotifywait en mode monitor. inotifywait va alors surveiller en permanence les changements survenant dans l’arborescence et les indiquer sur la sortie standard.
Explication des paramètres :
La sortie d’inotifywait est redirigée dans une boucle while read
. C’est cette boucle qui va effectuer les traitements pour chaque modification ou suppression d’un fichier.
has_extension "$filename" html css js svg xml json xsl xsd txt ps png jpg ico \
|| continue
La boucle principale commence par vérifier que l’extension du fichier qui a généré l’événement fait bien partie de celles sur lesquelles nous opérons des traitements.
(test -f "$filename" && lsof -- "$filename") && continue
Elle vérifie ensuite, si le fichier existe, qu’il n’est plus ouvert par une autre application. Si tel était le cas, cela voudrait dire que l’application opération des modifications sur le fichier n’a pas encore terminé son travail et qu’il n’est pas encore opportun de traiter ce fichier.
if [ ! -f "$filename" ]
then
clean_files "$filename"
Quand un fichier est supprimé, inotifywait génère un événement. Le script reçoit donc le nom d’un fichier qui n’existe plus. Dans ce cas-là, il faut faire le ménage et retirer les éventuels fichiers gzip, brotli et webp qui ont été créés pour ce fichier.
elif has_extension "$filename" html css js svg xml json xsl xsd txt ps ico
then
text_precompress "$filename"
Teste si le fichier porte l’extension d’un type de fichier texte (ou compressible par gzip et brotli) et effectue le traitement approprié.
Note : oui, les fichiers ico sont compressibles.
elif has_extension "$filename" jpg
then
jpg_precompress "$filename"
Effectue le traitement approprié s’il s’agit d’un jpeg.
elif has_extension "$filename" png
then
png_precompress "$filename"
fi
Effectue le traitement approprié s’il s’agit d’un png.