watchfile: Execute a command when something changes.

This scribble covers a utility I've been gradually extending, which is handy for solving a common automization problem of "as soon as something happens, do something else".

This can be things like:

  • .. interactively execute python/perl/bash/octave/etc scripts while coding.
  • .. automatically convert assets to a usable format when they change.
  • .. automatically recompile a project when a source file changes.
  • .. monitor a website and alert you when it has changed.

Demo:
Click to play

The script is provided in its entirety below, which I'll leave to the especially interested to go through (70% of it is just parsing parameters).

You can check it out as a github gist here.

1. Examples

Example 1: With python, perl, bash, octave

Whenever I write scripts in an of these languages, I don't have the syntax at my fingertips, so I stumble a bit.

Assuming the file is an executable and has a shabang, here is how you could monitor and automatically execute such a file using the watchfile utility: watchfile foo.py

If foo.py isn't an executable: watchfile foo.py python foo.py

The default is checking the modified timestamp. However, you can request to look at the content itself using the --check-content option, which is fine for smaller files: watchfile --check-content foo.py

Example 2: Preparing assets

Say you are monitoring a Wavefront OBJ file, and want to convert it to some binary representation using a utility called obj2cobj, you could do: watchfile foo.obj obj2cobj foo.obj

Example 3: Compiling and executing code (e.g c++)

Monitoring a file and executing a different command allows for automatic recompiling. watchfile main.cpp g++ -Wall main.cpp -o main

Automatic execution when compilation succeeds (note that you have to escape the ampersands): watchfile main.cpp g++ -Wall main.cpp -o main \&\& ./main

Monitoring multiple files, using the -i flag, to start listing files to monitor, and the -e flag to start listing the command to execute:

watchfile -i main.cpp widget.h widget.cpp \
          -e g++ -Wall main.cpp -o main

Monitoring multiple files, and building with make watchfile -i main.cpp widget.cpp widget.h -e make

Finding and monitoring all source files, and building with make (note that this doesn't handle files created or deleted after monitoring starts, see example 5 on how to do this) watchfile -i `find . -name "*.cpp" -or -name "*.h"` -e make

Example 4: Monitoring a website

The watchfile utility also supports monitoring arbitrary commands, and monitors change in the output as if it were a file. This is done using the -s flag, followed by a single parameter which is the command to monitor (using quotes, if need be).

Using curl and grep, you can easily extract relevant parts of a webpage. By monitoring this output, you can set up an alert for when the website changes.

For instance, say you want to monitor the forecast for northern lights in Europe. Here is how to get the Aurora Borealis intensity level forecast, from the Geophysical Institute at UoAF:

curl -s http://www.gi.alaska.edu/AuroraForecast/Europe/ | \
    grep -oP 'Europe_\d+' | grep -oP '\d+$'

Let's use the watchfile to check this value once every 15 minutes (900 seconds), and show a pop-up on the screen when it changes, using the notify-send utility.

watchfile -d 900 \
    -s "curl -s http://www.gi.alaska.edu/AuroraForecast/Europe/ | \
        grep -oP 'Europe_\d+' | grep -oP '\d+$' \
    -e notify-send "Northern lights intensity changed"

Let's now modify it to store the intensity value to a temporary file, and then display this value in the pop-up:

watchfile -d 900 \
    -s "curl -s http://www.gi.alaska.edu/AuroraForecast/Europe/ | \
        grep -oP 'Europe_\d+' | grep -oP '\d+$' | tee /tmp/auroravalue" \
    -e cat /tmp/auroravalue | xargs notify-send \
       "Northern lights intensity changed to: "

Example 5: Monitor dynamic list of files

In example 3, I mentioned that if you list files to monitor using the -i flag, this list is static cannot change dynamically while the watchfile script is running.

To circumvent this, we use the -s flag to pass a command that lists the timestamps of all the relevant files.

watchfile -s \
    "find . -name '*.cpp' -or -name '*.h' | xargs stat -c %Y" \
     -e make \&\& ./main

Alternatively, to output the content of all relevant files (similar to the --check-content option):

watchfile -s \
    "find . -name '*.cpp' -or -name '*.h' | xargs cat" \
     -e make \&\& ./main

2. The watchfile script