inotifywait one-liner
This uses inotifywait while avoiding the use of while read -r
:
inotifywait -q --format '%f' -e close_write,moved_to -m . |
grep --line-buffered -F -x 'myfile.py' |
xargs -l -i './myfile.py'
Explanation: inotifywait
outputs a line with the filename when it detected a change, grep
filters the targeted filename, and xargs
executes a command for each filename.
inotifywait
parameters:
-q
: Remove stderr messages.
--format '%f'
Only output the filename, we don't filter on events anyway.
-e close_write,moved_to
Detect only close_write (file has been written to), and moved_to (most editors use swap files, and move the buffer to the file when saving).
-m
Keep listening indefinitely (press CTRL+C to interrupt).
.
Target directory that contains the targeted file.
grep
parameters:
--line-buffered
: Flush lines immediately, treat as stream (like sed
).
-F
: Literal filename, don't parse regular expression (otherwise we'd need to escape the dot: myfile\.py
).
-x
: Match the whole line, not just a substring of the filename.
xargs
parameters:
-l
: Execute for each input line, don't gather lines up.
-i
: Prevent the filename being added as an argument to the command, it replaces {}
in the command with the input line.
Generic function to execute command on file change
For a more generic case, you may use this function:
exec-onchange()
{
local file="$1"
shift
# strip path
local filename="${file##*/}"
# strip filename
local path="${file%/*}"
if [ -z "$path" ]; then path="."; fi
# catch a custom command
local cmd="$@"
local literalFlag=""
if [ -z "$cmd" ]; then cmd="$path/$filename"; literalFlag="-Fx"; fi
exec inotifywait -q --format '%f' -e close_write,moved_to -m "$path" |
grep --line-buffered $literalFlag "$filename" |
xargs -l -i /bin/bash -c "$cmd"
}
Usage:
exec-onchange [file-to-watch] [command-to-execute]
Example usage:
exec-onchange myfile.py
Note that the argument is literal, unless a custom command is added, in which case the first argument becomes a regular expression. Although in this case, regular expressions are only allowed for matching the filename, and not the path.
This function allows for more complex usage. Such as automatically compiling, while running the compiled executable as soon as compilation is complete (by using a separate exec-onchange, compilation can be done while still running build/main):
exec-onchange 'src/.*\.cpp' 'echo "{} changed"; gcc src/*.cpp -o build/main' &
exec-onchange build/main
watch -g
terminates when output of the command changes, e.g.:while :; do watch -gn .1 stat -c %Z file.tex && pdflatex -interaction batchmode file.tex && pkill -HUP mupdf; done
. Where:
evaluates totrue
and-n .1
runs the command (stat
) every 0.1 seconds.stat -c %Z
gives time file was last accessed. Downside is this can only be stopped with^Z
,kill %1
while :; do …; done
, I usually usewhile sleep 1; do …; done
. This way, I can easily^C
during thesleep
call. Plus, it avoids hogging the CPU by limiting the loop to run at most at 1Hz (adjustable by changing thesleep
parameters).