Monitor a folder for changes, and run a command when a change is detected

Is there a simple way to automatically run a (Terminal) command every time a file is changed in a specific folder?

This should be possible via the command line, or a system built-in application, no third-party applications.

Any ideas?


Solution 1:

entr(1) is a utility for running commands when files change. It reads a list of file on STDIN and uses kqueue(2) to avoid polling.

Example:

ls my_project/*.html | entr echo "file changed"

Solution 2:

Save a property list like this as ~/Library/LaunchAgents/test.plist:

<?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>test</string>
    <key>ProgramArguments</key>
    <array>
        <string>say</string>
        <string>yy</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>~/Desktop/</string>
    </array>
</dict>
</plist>

The agent can be loaded with launchctl load ~/Library/LaunchAgents/test.plist or by logging out and back in. Applying changes requires unloading and loading the plist.

Tilde expansion (~/) works in WatchPaths by default. EnableGlobbing adds wildcard and tilde expansion for ProgramArguments, but it doesn't affect Program or WatchPaths.

If a watched file is modified within ten seconds from the last invocation, a message like this is added to system.log:

com.apple.launchd.peruser.501[146]: (test) Throttling respawn: Will start in 7 seconds

One way to get rid of the messages is to add something like sleep 10 to the end of scripts. Setting ThrottleInterval to 10 doesn't help.

Changes in subfolders of watched folders aren't detected.

See man launchd and man launchd.plist for more information.

Solution 3:

You could use Folder Actions, which lets you execute (Automator) scripts whenever contents in a folder change. As far as I know, Automator has a template that lets you easily create a new folder action and attach it to the desired folder. And by adding a "Run Shell Script" action, you should just get the desired effect.

Solution 4:

Folder actions are fine for triggering when a file is added or modified.

However, if your definition of changed includes deleting a file, OSX Folder actions does not detect if a file has been removed.

To answer the question:

  1. Download the FileWatcher dependencies from here: https://github.com/eonist/swift-utils

  2. Familiarize your self with running swift in the comandline: http://krakendev.io/blog/scripting-in-swift

  3. Use this code to watch a folder.

Code:

var fileWatcher = FileWatcher(["~/Desktop/test/".tildePath])/*<---the fileWatcher instance must be scoped to your class*/

fileWatcher!.event = { event in
    Swift.print(self?.someVariable)//Outputs: a variable in your current class
    Swift.print(event.description)//Outputs: a description of the file change
}

Solution 5:

Automator has a type of workflow called a "Folder Action" which automatically runs when something is added to a folder. Create one, then use the action "Run Shell Script."