How to watch for file changes and trigger a shell command on any modification in OS X and Linux

These days our files are everywhere, you need an advanced IDE just to track where all the code for a small software project is, and you have many background processes running as dependencies on your main application. It's not always possible to know when a particular file will be updated since it's done via a chain of automatic processes. Have you ever been waiting for a process to run and not notice that it had finished? As developers, we need ways to hook into these chains and be aware of files which change when they change in order to trigger new actions off of those "filesystem events".

On Unix/Linux, there are some commands we can use on the shell to monitor changes in a file. But Linux has one way and OS X has a different way.

The idea of having userland interfaces to have such granular event notification of directory or file changes is relatively new in operating system history. And thus there isn't an old, traditional UNIX system call interface for this, and so Linux has one way of doing it, and Mac OS X has its own FSEvents API which is not based on BSD or FreeBSD.

Linux: inotify and inotifywait

Linux has a unique kernel subsystem to allow userland applications to subscribe to "inode notifications". An inode is used for anything in the filesystem from normal files to directories. And so the inotify interface provides a developer what they need to asynchronously update app state when a file is changed, added, deleted, etc.

How can we use Linux's inotify to execute a command whenever a file or directory changes?

While inotify itself isn't directly accessible from the command line, inotify-tools is a C library and collection of command line programs on top of the library and inotify which includes inotifywait which you can use in your shell scripts to wait for a file change, or inotifywatch for collecting statistics. To wait for a file to change and then do something based on that event, inotifywait is what you want. [Note: inotify replaces dnotify]

Mac OS X: FSEvents

On OSX, we have an Apple api called FSEvents, similar to inotify, which an application can use to register for notification of events when the filesystem changes, e.g. when a file is modified or created. (Events at the level of files was only added since OS X 10.7) This api is for developers to use but can't be used directly from the command line with anything which comes with OSX. There is a way to create a plist which is run by launchctl/launchd to run a command which is watching a directory

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>logger</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/logger</string>
        <string>Desktop modified</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Users/wusername/Desktop/</string>
    </array>
</dict>
</plist>

$ launchctl load ~/Library/LaunchAgents/logger.plist

For a raw stream of all filesystem changes on OSX you can run sudo fs_usage. Warning: it is quite verbose.

Neither method is really conducive to composing Unix command line commands for OS X. Instead, you should install 3rd party software such as the next suggestion.

[P.S. For OSX, there used to be a great app called FSeventer which could graphically monitor and display changes to any file on your computer, useful for seeing what a running app is doing. Unfortunately, it does not work on recent releases of Mac OS X (Yosemite, El Capitan).]

Cross platform: Facebook's watchman, et al

Facebook has a tool for developers called Watchman, a file watching service. It's both a daemon and a command to trigger build commands from the daemon. It can watch directories recursively. It's smart in other ways as well. And it runs on Linux with inotify, OS X with FSEvents, and can even run on Windows. So you can use watchman anywhere to monitor a folder (recursively) and trigger any command-line action when a file is edited and saved or created.