How to execute a command whenever a file changes?
Simple, using inotifywait (install your distribution's inotify-tools
package):
while inotifywait -e close_write myfile.py; do ./myfile.py; done
or
inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
./myfile.py # or "./$filename"
done
The first snippet is simpler, but it has a significant downside: it will miss changes performed while inotifywait
isn't running (in particular while myfile
is running). The second snippet doesn't have this defect. However, beware that it assumes that the file name doesn't contain whitespace. If that's a problem, use the --format
option to change the output to not include the file name:
inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
./myfile.py
done
Either way, there is a limitation: if some program replaces myfile.py
with a different file, rather than writing to the existing myfile
, inotifywait
will die. Many editors work that way.
To overcome this limitation, use inotifywait
on the directory:
inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
if [ "$filename" = "myfile.py" ]; then
./myfile.py
fi
done
Alternatively, use another tool that uses the same underlying functionality, such as incron (lets you register events when a file is modified) or fswatch (a tool that also works on many other Unix variants, using each variant's analog of Linux's inotify).
entr (http://entrproject.org/) provides a more friendly interface to inotify (and also supports *BSD & Mac OS X).
It makes it very easy to specify multiple files to watch (limited only by ulimit -n
), takes the hassle out of dealing with files being replaced, and requires less bash syntax:
$ find . -name '*.py' | entr ./myfile.py
I've been using it on my entire project source tree to run the unit tests for the code I'm currently modifying, and it's been a huge boost to my workflow already.
Flags like -c
(clear the screen between runs) and -d
(exit when a new file is added to a monitored directory) add even more flexibility, for example you can do:
$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done
As of early 2018 it's still in active development and it can be found in Debian & Ubuntu (apt install entr
); building from the author's repo was pain-free in any case.
I wrote a Python program to do exactly this called when-changed.
Usage is simple:
when-changed FILE COMMAND...
Or to watch multiple files:
when-changed FILE [FILE ...] -c COMMAND
FILE
can be a directory. Watch recursively with -r
. Use %f
to pass the filename to the command.
How about this script? It uses the stat
command to get the access time of a file and runs a command whenever there is a change in the access time (whenever file is accessed).
#!/bin/bash
### Set initial time of file
LTIME=`stat -c %Z /path/to/the/file.txt`
while true
do
ATIME=`stat -c %Z /path/to/the/file.txt`
if [[ "$ATIME" != "$LTIME" ]]
then
echo "RUN COMMAND"
LTIME=$ATIME
fi
sleep 5
done