Monitor Folder Contents with launchd - Including Subfolders?

I want to make a shell script that runs any time the contents of a particular folder on my server are changed. Seems easy enough at first, because launchd has a directive called WatchPaths which triggers your script any time the contents of a folder change.

HOWEVER WatchPaths is not triggered by changes in subfolders. The folder I'm trying to watch is a few hundred MB of small web files, nested several levels deep in sub folders. Far too many folders to try to manually list each one.

Is there some other sane way to accomplish this?


Solution 1:

WatchPaths doesn't appear to be able to watch a sub-directory recursively, so the best you can do with it is watch a single directory's activities. You might want to take a look at Folder Actions, they're discussed in detail in this blog post titled: Setup OS X Folder Actions to Know When a File is Added as well.

Another alternative is to use a CLI tool that's available in Homebrew via the brew install fswatch command. fswatch is a cross platform implementation that works on most flavors of *NIX includes macOS.

The FSEvents monitor, available only on OS X, has no known limitations, and scales very well with the number of files being observed.

Example

Here I've created a sub-directory called ~/somedir. I then invoked `fswatch against this directory:

$ mkdir ~/somedir
$ fswatch /some/dir

Now if we start adding files and sub-directories fswatch will notify:

$ touch ~/somedir/afile
fwatch msg>> /Users/joeuser/somedir/afile

$ mkdir ~/somedir/anotherdir
fswatch msg>> /Users/joeuser/somedir/anotherdir

$ touch ~/somedir/anotherdir/afile
fswatch msg>> /Users/joeuser/somedir/anotherdir/afile

The above can be adapted for use in a shell script which can take whatever other actions you require when any changes are detected against the directory tree that fswatch is monitoring.

Other Examples

To act on file system activity you can use one of these 2 patterns:

$ fswatch -0 path | while read -d "" event \
  do \
    // do something with ${event}
  done

Or this:

$ fswatch -o path | xargs -n1 -I{} program

References