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