-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
onfilechange.sh 7.43 KiB
#!/bin/bash
# ======================================================================
#
#
# T R I G G E R C O M M A N D O N A F I L E C H A N G E
#
#
# A Shell script that watches a given fileobject or multiple fileobjects
# (=files or directories)
# If the fileobject changes then a given command will be exxecuted.
# It loops permanently; you need to stop it by Ctrl + C and/ or can
# use it as systemd watcher daemon.
#
# It uses stat for wide compatibility but can enable inotifywatch to
# check a file change by an event.
#
# licence: GNU GPL 3.0
# source: https://git-repo.iml.unibe.ch/iml-open-source/onfilechange
# docs: https://os-docs.iml.unibe.ch/onfilechange/
#
# ----------------------------------------------------------------------
# 2019-10-14 v1.0 <axel.hahn@iml.unibe.ch> first basic version
# 2019-10-21 v1.03 <axel.hahn@iml.unibe.ch> use stat as default
# 2022-03-11 v1.04 <axel.hahn@iml.unibe.ch> shell fixes
# 2022-03-11 v1.05 <axel.hahn@iml.unibe.ch> fix: behaviur when trigger command fails; shell fixes; update docs
# ======================================================================
# ----------------------------------------------------------------------
# CONFIG
# ----------------------------------------------------------------------
bDebug=0
iSleep=5
sCommand=
sWatchFile=
sMode=
# ---- below are some internal variables
_version=1.05
# ----------------------------------------------------------------------
# FUNCTIONS
# ----------------------------------------------------------------------
# show help
function showHelp(){
local _self=$( basename $0 )
cat <<ENDOFHELP
HELP:
This script checks the change of a given fileobjects and triggers
a command if it changes
PRAMETERS:
-c [command]
command to execute on a file change
-f [fileobject(s)]
filenames or directories to watch; separate multiple files with
space and put all in quotes
-h
show this help
-i
force inotifywait command
-s
force stat command (default mode)
-v
verbose mode; enable showing debug output
-w [integer]
for stat mode: wait time in seconds betweeen each test or on
missing file; default: 5 sec
EXAMPLES:
$_self -f /home/me/touchfile.txt -c "ls -l"
watch touchfile.txt and make a file listing on change
$_self -f "/home/me/touchfile.txt home/me/touchfile2.txt" -c "ls -l"
watch touchfile.txt and touchfile2.txt
$_self -f /home/me/touchfile.txt -s -w 10 -c "echo hello"
watch touchfile.txt every 10 sec with stat and show "hello" on a
change
ENDOFHELP
}
# write debug output ... if debug is enabled only
#
# global (bool) $bDebug
# param string text message to show
function wd(){
if [ $bDebug -ne 0 ]; then
echo "[$(date)] DEBUG |" $*
fi
}
# list watched files
#
# global (string) filename(s) to watch
function listFiles(){
echo
echo ">>>>> watched files"
ls -ld ${sWatchFile} 2>&1
}
# for stat: helper to get current file status
#
# global (string) $sWatchFile
function getFilestatus(){
for myfile in ${sWatchFile}
do
stat -c "%F %n | perms: %A; user %u (%U) group %g (%G) | size: %s byte | last modification %y" "${myfile}" 2>&1
done
}
# for stat: inititalize file change detection
#
# global (string) $TmpFile last/ initial file status
# global (string) $sTmpFile2 current file status
function initFilestatus(){
getFilestatus >"${sTmpFile}"
wd "$(cat "${sTmpFile}")"
cp -p "${sTmpFile}" "${sTmpFile2}"
}
# for stat: compare file status and execute command on change
#
# global (string) $TmpFile last/ initial file status
# global (string) $sTmpFile2 current file status
# global (string) $sCommand command to execute
function compareFilestatus(){
getFilestatus >${sTmpFile2}
wd "$(cat ${sTmpFile2})"
if diff ${sTmpFile} ${sTmpFile2}; then
wd "No Change"
else
wd "Change detected."
if execCommand; then
echo Command was successful.
else
echo rc=$? FAILED.
fi
wd "Re-Init File status"
mv ${sTmpFile2} ${sTmpFile}
echo
echo ">>>>> waiting for the next change ..."
fi
}
# execute a command; called on a file change
#
# global (string) $sCommand command line to exectute
function execCommand(){
echo ">>>>> $(date) Executing ${sCommand} ..."
${sCommand}
}
# ----------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------
cat <<ENDOFHEAD
______________________________________________________________________________
T R I G G E R C O M M A N D O N A F I L E C H A N G E
_______________________________________________________________________| v${_version}
ENDOFHEAD
if which stat >/dev/null 2>&1; then
echo "INFO: stat command detected"
sMode=stat
else
echo ERROR: the command stat was not found on your system.
if ! which inotifywait >/dev/null 2>&1; then
echo ERROR: the command inotifywait was not found on your system.
exit 2
fi
echo "INFO: enabling inotifywait command"
sMode=inotifywait
fi
if [ $# -eq 0 ]; then
showHelp
exit 0
fi
while getopts ":c: :v :f: :h :i :s :w:" opt
do
case $opt in
h)
showHelp
exit 0
;;
v)
bDebug=1
wd "debug is now ${bDebug}"
;;
c)
sCommand=$OPTARG
wd "command is now ${sCommand}"
;;
f)
sWatchFile=$OPTARG
wd "watch file is now ${sWatchFile}"
;;
i)
sMode=inotifywait
wd "forcing mode with inotifywait command"
;;
s)
sMode=stat
wd "forcing mode with stat command"
;;
w)
typeset -i iSleep=$OPTARG
wd "sleep $iSleep sec"
;;
:)
echo "ERROR: Option -$opt requires an argument." >&2
showHelp
exit 1
esac
done
cat <<ENDOFINFO
--- summary
checking file [${sWatchFile}]
with command [${sMode}]
with a sleep time of [${iSleep}] seconds
and on change I start the command [${sCommand}]
...............................................................................
ENDOFINFO
# ----------------------------------------------------------------------
# CHECKS
# ----------------------------------------------------------------------
wd "--- checks"
if [ -z "${sWatchFile}" ]; then
echo ERROR: set a check file with param -f
exit 1
fi
if ! listFiles; then
echo "INFO: file ${sWatchFile} (or one of them) does not exist yet"
# echo "ERROR: file ${sWatchFile} (or one of them) does not exist yet"
# exit 1
fi
if [ -z "${sCommand}" ]; then
echo ERROR: set a ${sCommand} with param -s
exit 1
fi
echo
# ----------------------------------------------------------------------
# GO
# ----------------------------------------------------------------------
echo ">>>>> start"
myset=$(echo "${sWatchFile}" | sha1sum | cut -f 1 -d " ")
sTmpFile="/tmp/$(basename $0)-${myset}-last.tmp"
sTmpFile2="/tmp/$(basename $0)-${myset}-current.tmp"
case $sMode in
"inotifywait")
while true; do
if listFiles >/dev/null 2>&1; then
inotifywait -e attrib -e modify "${sWatchFile}" && execCommand
else
echo "ERROR: inotifywait only can notify if all watched files exist."
echo "Use parameter -s to use stat for file detection, This mode also allows that a file is deleted."
exit 2
fi
done
;;
"stat")
wd "--- initial read of watched files"
initFilestatus
echo waiting for file changes ...
wd "--- starting loop"
while true; do
wd sleep ${iSleep}
sleep ${iSleep}
compareFilestatus
done
;;
esac
# ----------------------------------------------------------------------