Batch Filesystem Events

inotifywait is a handy utility that listens for filesystem events you specify and prints them to standard out. This is particularly useful if, say, you want to automatically redeploy a website whenever something changes.

(Jekyll comes with this functionality out-of-the-box, but if you have more than one site, or other non-Jekyll content that changes, it may be desirable to deploy everything together.)

The naive approach to this problem might look something like:

# Filesystem events specific to file creation or modification
events="modify,attrib,close_write,move,create,delete"

# -m = monitor (don’t quit after the first event)
# -r = recursive
inotifywait -m -r -e "$events" $site_dir | while read ; do deploy_site ; done

But the problem with filesystem events is that lots of them can fire simultaneously. A single file save produces multiple events, and a project-wide find/replace opens the floodgates. Since the site will deploy on every individual event, even minor changes kick off 3-4 rebuilds in a row. That’s pretty silly.

I found a few patterns out on the ’net to batch inotifywait messages, but most of them were pretty complicated. I wondered if it might be possible to do the same thing in a more straightforward fashion, and came up with this:

echo "Watching source directory for changes."

events="modify,attrib,close_write,move,create,delete"

while true
do

    count=0

    while read -t 1
    do
        (( count++ ))
    done

    if [[ $count -gt 0 ]]
    then
        echo "$count changes detected."
        deploy_site
    fi

done < <(inotifywait -m -r -e "$events" --exclude '/\..+' $dir 2>/dev/null)

As it turns out, read accepts a timeout value! Now any inotify events less than a second apart will be counted as part of the same batch and will result in only a single deploy_site call.

A word of warning: failure to exclude .git folders will result in a ton of filesystem events. In the above code, I ignore all dotfiles (and folders) to avoid this problem.