29

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?

2

7 Answers 7

20

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"

It can be installed directly from the project site or via Homebrew.

For 2022, simply % brew install entr to install.

0
8

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.

1
  • This solution direction is well explained in a tutorial at mayeu.me/post/…
    – August
    Commented Sep 28, 2023 at 17:33
5

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/FileWatcher

  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
}
0
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.

1
  • 6
    Thanks, but it appears that "Folder Actions" are only triggered when a file is added to the folder, not when an existing file is checked.
    – Louis B.
    Commented Feb 17, 2012 at 15:06
1

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."

2
  • 4
    Thanks, but it appears that "Folder Actions" are only triggered when a file is added to the folder, not when an existing file is checked.
    – Louis B.
    Commented Feb 17, 2012 at 15:06
  • Oh, okay. Sorry.
    – Tuesday
    Commented Feb 17, 2012 at 21:05
1

I've found fswatch which claims to be "a cross-platform file change monitor", and seems to support all the proper platform solutions like Filesystem Events on macOS.

# Monitor all files in a directory, execute script once when something changes
fswatch -o -r path/to/directory | xargs -n1 -I{} your-command-here
-1

You might want to try something like this:

touch /tmp/dirb.tmp
while true do 
  ls /thedirtocheck > /tmp/dira.tmp 
  diff /tmp/dira.tmp /tmp/dirb.tmp || echo "something changed" 
  cp /tmp/dira.tmp /tmp/dirb.tmp 
  sleep 100 
done
2
  • You need at least ls -l to catch file changes as well.
    – nohillside
    Commented Feb 17, 2012 at 12:15
  • right.... missed that Commented Feb 17, 2012 at 17:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .