From b04c0acaedb30eabc27388c179338f96aea4900a Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch>
Date: Fri, 17 Jan 2025 13:50:07 +0100
Subject: [PATCH] handle multiple files with spaces; long param options; update
 help

---
 onfilechange.sh | 181 ++++++++++++++++++++++++------------------------
 1 file changed, 89 insertions(+), 92 deletions(-)

diff --git a/onfilechange.sh b/onfilechange.sh
index 0d6f8f6..80ec501 100755
--- a/onfilechange.sh
+++ b/onfilechange.sh
@@ -23,20 +23,22 @@
 # 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
+# 2025-01-17  v1.06  <axel.hahn@unibe.ch>      handle multiple files with spaces; long param options; update help
 # ======================================================================
 
 # ----------------------------------------------------------------------
 # CONFIG
 # ----------------------------------------------------------------------
 
-bDebug=0
-iSleep=5
+FLAG_DEBUG=0
+typeset -i iSleep=5
 sCommand=
-sWatchFile=
 sMode=
 
+aWatchFiles=()
+
 # ---- below are some internal variables 
-_version=1.05
+_version=1.06
 
 
 # ----------------------------------------------------------------------
@@ -47,38 +49,55 @@ _version=1.05
 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
+
+${_self} checks the change of a given fileobjects and triggers
+a command on file changes.
+It can use stat or inotifywait to watch a file change. preferred is stat
+because it can be used for not yet existing files.
+
+SYNTAX:
+
+  ${_self} OPTIONS -c COMAND [FILE] [[... FILE N]]
 
 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
+  -c|--command COMMAND
+    A command to execute on a file change
+
+  -f|--file FILENAME
+    Filenames or directories to watch; separate multiple files with 
+    space and put all in quotes
+    DEPRECATED: add all files to watch as parameters
 
-        $_self -f "/home/me/touchfile.txt home/me/touchfile2.txt" -c "ls -l" 
-            watch touchfile.txt and touchfile2.txt
+  -h|--help
+    show this help
 
-        $_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
+  -i|--inotifywait
+    force inotifywait command
+
+  -s|--stat
+    force stat command (default mode)
+ 
+  -v
+    verbose mode; enable showing debug output; it should be the first option
+    to see handling of other options
+  
+  -w|--wait SLEEPTIME
+    for stat mode: wait time in seconds betweeen each test or on 
+    missing file; default: 5 sec
+
+EXAMPLES:
+
+  $_self -c "ls -l" /home/me/touchfile.txt
+    watch touchfile.txt and make a file listing on change
+  
+  $_self -c "ls -l" "/home/me/touchfile.txt" "home/me/touchfile2.txt"
+    watch touchfile.txt and touchfile2.txt
+  
+  $_self -s -w 10 -c "echo hello" /home/me/touchfile.txt
+    watch touchfile.txt every 10 sec with stat and show "hello" on a change
 
 ENDOFHELP
 
@@ -86,28 +105,28 @@ ENDOFHELP
 
 # write debug output ... if debug is enabled only
 #
-# global (bool) $bDebug
+# global (bool) $FLAG_DEBUG
 # param  string  text message to show
 function wd(){
-	if [ $bDebug -ne 0 ]; then
-		echo "[$(date)] DEBUG |" $*
+	if [ $FLAG_DEBUG -ne 0 ]; then
+		echo "$*" | while read -r l; do echo "[$(date)] DEBUG | ${l}"; done
 	fi
 }
 
 # list watched files
 #
-# global (string) filename(s) to watch
+# global (string) filesAsString  list of all files to watch
 function listFiles(){
 	echo
 	echo ">>>>> watched files"
-	ls -ld ${sWatchFile} 2>&1
+	eval "ls -ld $filesAsString"
 }
 
 # for stat: helper to get current file status
 #
-# global (string) $sWatchFile
+# global (array) $aWatchFiles  array of watched files
 function getFilestatus(){
-	for myfile in ${sWatchFile}
+	for myfile in "${aWatchFiles[@]}"
 	do
 		stat -c "%F %n | perms: %A; user %u (%U) group %g (%G) | size: %s byte | last modification %y" "${myfile}" 2>&1
 	done
@@ -129,9 +148,9 @@ function initFilestatus(){
 # global (string) $sTmpFile2  current file status
 # global (string) $sCommand   command to execute
 function compareFilestatus(){
-	getFilestatus >${sTmpFile2}
-	wd "$(cat ${sTmpFile2})"
-	if diff ${sTmpFile} ${sTmpFile2}; then
+	getFilestatus >"${sTmpFile2}"
+	wd "$(cat "${sTmpFile2}")"
+	if diff "${sTmpFile}" "${sTmpFile2}"; then
 		wd "No Change"
 	else
 		wd "Change detected."
@@ -141,7 +160,7 @@ function compareFilestatus(){
 			echo rc=$? FAILED.
 		fi
 		wd "Re-Init File status"
-		mv ${sTmpFile2} ${sTmpFile}
+		mv "${sTmpFile2}" "${sTmpFile}"
 		echo
 		echo ">>>>> waiting for the next change ..."
 	fi
@@ -168,15 +187,12 @@ _______________________________________________________________________| v${_ver
 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
 
@@ -184,52 +200,29 @@ 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
+while [[ "$#" -gt 0 ]]; do case $1 in
+    -h|--help)         showHelp; exit 0;;
+    -c|--command)      sCommand="$2";            wd "command is now ${sCommand}"; shift; shift;;
+    -f|--file)         aWatchFiles+=("$2");      echo "DEPPRECATED: Add all files as parameter instead of using -f option."; wd "watch file $2 was added" ;shift;shift;;
+    -i|--inotifywait)  sMode=inotifywait;        wd "forcing mode with inotifywait command"; shift;;
+    -s|--stat)         sMode=stat;               wd "forcing mode with inotifywait command"; shift;;
+    -w|--wait)         iSleep=$OPTARG;           wd "sleep $iSleep sec"; shift;shift;;	
+    -v|--verbose)      FLAG_DEBUG=1; shift;;
+    *) if grep "^-" <<< "$1" >/dev/null ; then
+        echo; echo "ERROR: Unknown parameter: $1"; echo; _showHelp; exit 2
+       fi
+       # break;
+	   aWatchFiles+=("$1");      wd "watch file $1 was added" ;shift;;
+
+esac; done
 
 cat <<ENDOFINFO
 
 --- summary
-checking file [${sWatchFile}]
+checking file(s): ${aWatchFiles[@]}
 with command [${sMode}] 
 with a sleep time of [${iSleep}] seconds 
-and on change I start the command [${sCommand}]
+on change execute command [${sCommand}]
 
 ...............................................................................
 
@@ -240,19 +233,23 @@ ENDOFINFO
 # CHECKS
 # ----------------------------------------------------------------------
 
+filesAsString=
+for myfile in "${aWatchFiles[@]}"
+do
+	filesAsString+="'${myfile}' "
+done
+
 wd "--- checks"
-if [ -z "${sWatchFile}" ]; then
-	echo ERROR: set a check file with param -f
+if [ -z "${filesAsString}" ]; then
+	echo "ERROR: Add one or more files / directories to watch as parameter"
 	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
+	echo "INFO: file ${filesAsString} (or one of them) does not exist yet"
 fi
 
 if [ -z "${sCommand}" ]; then
-	echo ERROR: set a ${sCommand} with param -s
+	echo ERROR: set a command with option -c first.
 	exit 1
 fi
 echo
@@ -262,7 +259,7 @@ echo
 # ----------------------------------------------------------------------
 
 echo ">>>>> start"
-myset=$(echo "${sWatchFile}" | sha1sum | cut -f 1 -d " ")
+myset=$(echo "${aWatchFiles[*]}" | sha1sum | cut -f 1 -d " ")
 sTmpFile="/tmp/$(basename $0)-${myset}-last.tmp"
 sTmpFile2="/tmp/$(basename $0)-${myset}-current.tmp"
 
@@ -270,7 +267,7 @@ case $sMode in
 	"inotifywait")
 		while true; do
 			if listFiles >/dev/null 2>&1; then
-				inotifywait -e attrib -e modify "${sWatchFile}" && execCommand
+				eval inotifywait -e attrib -e modify "${filesAsString}" && execCommand || exit 1
 			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."
-- 
GitLab