Is there a command like "watch" or "inotifywait" on the Mac?
Asked Answered
P

16

358

I want to watch a folder on my Mac and then execute a bash script, passing it the name of whatever file/folder was just moved into or created in the watched directory.

Playroom answered 4/10, 2009 at 6:9 Comment(3)
You should ask how DropBox does it since presumably they tried all the available options.Portecochere
@JeffBurdges I'm not so sure that'd be an easy undertaking. However I would say after skimming over Apple's FSEvents Reference it would be really silly if Dropbox wasn't making use of this. The fswatch util presented as an answer below does in fact use this method.Andreas
@JeffBurdges: it's not funny.Typehigh
L
537

fswatch

fswatch is a small program using the Mac OS X FSEvents API to monitor a directory. When an event about any change to that directory is received, the specified shell command is executed by /bin/bash

If you're on GNU/Linux, inotifywatch (part of the inotify-tools package on most distributions) provides similar functionality.

Update: fswatch can now be used across many platforms including BSD, Debian, and Windows.

Syntax / A Simple Example

The new way that can watch multiple paths - for versions 1.x and higher:

fswatch -o ~/path/to/watch | xargs -n1 -I{} ~/script/to/run/when/files/change.sh

Note: The number output by -o will get added to the end of the xargs command if not for the -I{}. If you do choose to use that number, place {} anywhere in your command.

The older way for versions 0.x:

fswatch ~/path/to/watch ~/script/to/run/when/files/change.sh

Installation with Homebrew

As of 9/12/13 it was added back in to homebrew - yay! So, update your formula list (brew update) and then all you need to do is:

brew install fswatch

Installation without Homebrew

Type these commands in Terminal.app

cd /tmp
git clone https://github.com/alandipert/fswatch
cd fswatch/
make
cp fswatch /usr/local/bin/fswatch

If you don't have a c compiler on your system you may need to install Xcode or Xcode command line tools - both free. However, if that is the case, you should probably just check out homebrew.

Additional Options for fswatch version 1.x

Usage:
fswatch [OPTION] ... path ...

Options:
 -0, --print0          Use the ASCII NUL character (0) as line separator.
 -1, --one-event       Exit fsw after the first set of events is received.
 -e, --exclude=REGEX   Exclude paths matching REGEX.
 -E, --extended        Use exended regular expressions.
 -f, --format-time     Print the event time using the specified format.
 -h, --help            Show this message.
 -i, --insensitive     Use case insensitive regular expressions.
 -k, --kqueue          Use the kqueue monitor.
 -l, --latency=DOUBLE  Set the latency.
 -L, --follow-links    Follow symbolic links.
 -n, --numeric         Print a numeric event mask.
 -o, --one-per-batch   Print a single message with the number of change events.
                       in the current batch.
 -p, --poll            Use the poll monitor.
 -r, --recursive       Recurse subdirectories.
 -t, --timestamp       Print the event timestamp.
 -u, --utc-time        Print the event time as UTC time.
 -v, --verbose         Print verbose output.
 -x, --event-flags     Print the event flags.

See the man page for more information.
Liturgist answered 10/12, 2012 at 19:37 Comment(8)
Your instructions or installing without homebrew don't seem to work any more. make throws an error about not finding a makefile. Fortunately, fswatch is now on MacPorts, so sudo port install fswatch works, for those of us using MacPorts instead of Homebrew.Paleolith
If you are using a command with static arguments (for example, I was running my unit tests on src and test changes) this might not work for you. This question (#25690089) was the the second piece I needed.Checkerboard
I hear you out there: fswatch ./ | xargs -I{} cp {} ~/Dropbox/backup/latest/Gounod
fswatch is better than tools like entr or when_changed, which have some bugs when dealing with .git directory. fswatch is much more professional. Simply, you just fswatch . will monitor current directory.Lorsung
I would suggest fswatch -0 -v -o /path/to/watched/files | xargs -0 -n 1 -I {} [your command] with -0 for NULLs. This one works for me for less compilationPolyphemus
Thanks for this. This answer has a lot of historical information that could be deleted, maybe? I'm just trying to watch a file and I need to do this while true; do fswatch --one-event eraseme-001.swift && swift eraseme-001.swift; done... is there a way to do it without the while?Insolation
@DanRosenstark You are having to use while because you are choosing to use --one-event. The equivalent is fswatch eraseme-001.swift | xargs -n1 -I{} swift {}Kilbride
can fswatch watch for additions / deletions of files?Hetaera
M
108

You can use launchd for that purpose. Launchd can be configured to automatically launch a program when a file path is modified.

For example the following launchd config plist will launch the program /usr/bin/logger when the desktop folder of my user account is modified:

<?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>path modified</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Users/sakra/Desktop/</string>
    </array>
</dict>
</plist>

To activate the config plist save it to the LaunchAgents folder in your Library folder as "logger.plist".

From the shell you can then use the command launchctl to activate the logger.plist by running:

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

The desktop folder is now being monitored. Every time it is changed you should see an output in the system.log (use Console.app). To deactivate the logger.plist, run:

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

The configuration file above uses the WatchPaths option. Alternatively you can also use the QueueDirectories option. See the launchd man page for more information.

Mansized answered 4/10, 2009 at 9:40 Comment(5)
is there a way to have it monitor change in file content as well as the file path?Drub
I don't think so. You can use opensnoop for that purpose.Mansized
I got this to work, but then when I switched /usr/bin/logger with my Bash script and removed the "<string>path modified</strong>" entry, I couldn't find any way for my bash script to know which file on my desktop was modified -- just that the event occurred. I tried looking at $0, $1 and only got the script name itself, noting passed to it.Convertite
Also, does this thing go into a loop once it detects a change, rather than only launching (and stopping) when it detects a change? I'd rather it work on demand, not keep running in a loop telling me that, for instance, 2 days ago, something changed on my desktop, writing that in the log over and over again. So, do we need to use some kind of interval option in this plist?Convertite
@Convertite If by "thing" you mean launchd, it's a system daemon and is always running. If by "thing" you mean the program in the plist, launchd runs the command in response to each watch event; it's up to your program/script to terminate itself instead of loop.Northman
D
50

Facebook's watchman, available via Homebrew, also looks nice. It supports also filtering:

These two lines establish a watch on a source directory and then set up a trigger named "buildme" that will run a tool named "minify-css" whenever a CSS file is changed. The tool will be passed a list of the changed filenames.

$ watchman watch ~/src

$ watchman -- trigger ~/src buildme '*.css' -- minify-css

Notice that the path must be absolute.

Deannadeanne answered 7/4, 2014 at 8:33 Comment(4)
This is by far the best tool.Tannie
This is a very nice little program: The docs are great, it doesn't open an infinite loop (which is what fswatch seems to do) and it remembers the watches you've created after reboot and auto starts them up again.Topminnow
@Tannie Could you elaborate how you came to that conclusion?Tenaculum
I did a large amount of research comparing various solutions. The functionality is very robust, and works consistently.Tannie
C
24

You might want to take a look at (and maybe expand) my little tool kqwait. Currently it just sits around and waits for a write event on a single file, but the kqueue architecture allows for hierarchical event stacking...

Chaldron answered 24/1, 2012 at 22:49 Comment(4)
Works so far on mountain lion. Very nice!Lock
So this is actually really awesome. I ran into a little issue with fswatch (which is fine too, since its such a light wrapper it could also be modified to do the proper thing I'm sure) where it would fire when running things like git status (which is actually run every time zsh prompt is rendered on my machine...) which actually kinda screws up this elaborate script i have by creating an endless feedback loop. kqwait, however, provides the info about the changed file, and is not tripped by git status. I do need it to fire on write though.Andreas
$ brew install kqwait && while true; do kqwait doc/my_file.md; make; doneCompassion
Simple, works like a charm on Mojave (and probably on any future Mac OS).Sob
P
17

watchdog is a cross-platform python API for watching files / directories, and it has builtin "tricks" tool that allows you to trigger actions (including shell commands) when events occur (including new added file, removed file and changed file).

Pickford answered 11/5, 2012 at 13:0 Comment(0)
O
15

This is just to mention entr as an alternative on OSX to run arbitrary commands when files change. I find it simple and useful.

  • brew install entr on macos
  • apt install entr on Debian/Ubuntu
Overtake answered 18/10, 2016 at 9:13 Comment(0)
C
6

Here's a one-liner using sschober's tool.

$ while true; do kqwait ./file-to-watch.js; script-to-execute.sh; done
Compassion answered 24/4, 2016 at 18:51 Comment(1)
$ brew install kqwaitCompassion
C
4

Apple OSX Folder Actions allow you to automate tasks based on actions taken on a folder.

Cristicristian answered 4/10, 2009 at 6:16 Comment(2)
Yeah I know, i'v tried to use that several times, never successfully gotten it to work, could you give me an example?Playroom
That's a link, not an answerInsolation
S
3

Edit: fsw has been merged into fswatch. In this answer, any reference to fsw should now read fswatch.

I wrote an fswatch replacement in C++ called fsw which features several improvements:

  • It's a GNU Build System project which builds on any supported platform (OS X v. >= 10.6) with

    ./configure && make && sudo make install
    
  • Multiple paths can be passed as different arguments:

    fsw file-0 ... file-n 
    
  • It dumps a detailed record with all the event information such as:

    Sat Feb 15 00:53:45 2014 - /path/to/file:inodeMetaMod modified isFile 
    
  • Its output is easy to parse so that fsw output can be piped to another process.

  • Latency can be customised with -l, --latency.
  • Numeric event flags can be written instead of textual ones with -n, --numeric.
  • The time format can be customised using strftime format strings with -t, --time-format.
  • The time can be the local time of the machine (by default) or UTC time with -u, --utc-time.

Getting fsw:

fsw is hosted on GitHub and can be obtained cloning its repository:

    git clone https://github.com/emcrisostomo/fsw

Installing fsw:

fsw can be installed using the following commands:

    ./configure && make && sudo make install

Further information:

I also wrote an introductory blog post where you can find a couple of examples about how fsw works.

Sodality answered 15/2, 2014 at 0:30 Comment(0)
A
2

My fork of fswatch provides the functionality of inotifywait -m with slightly less (no wait, more! I have a lot more troubles on Linux with inotifywait...) parse-friendly output.

It is an improvement upon the original fswatch because it sends out the actual path of the changed file over STDOUT rather than requiring you to provide a program that it forks.

It's been rock solid as the foundation of a series of scary bash scripts I use to automate stuff.

(this is off-topic) inotifywait on Linux, on the other hand, requires a lot of kludges on top of it and I still haven't figured out a good way to manage it, though I think something based on node.js might be the ticket.

Andreas answered 22/5, 2013 at 3:24 Comment(2)
Right, fork of fswatch. Is that on Homebrew?Casie
The answer is no; but they're working on it. To install it really quickly just brew install https://raw.github.com/mlevin2/homebrew/116b43eaef08d89054c2f43579113b37b4a2abd3/Library/Formula/fswatch.rbCasie
Y
2

I have a GIST for this and the usage is pretty simple

watchfiles <cmd> <paths...>

To illustrate, the following command will echo Hello World every time that file1 OR file2 change; and the default interval check is 1 second

watchfiles 'echo Hello World' /path/to/file1 /path/to/file2 

If I want to check every 5 seconds I can use the -t flag

watchfiles -t 'echo Hello World' /path/to/file1 /path/to/file2 
  • -v enables the verbose mode which shows debug information
  • -q makes watchfiles execute quietly (# will be shown so the user can see the program is executing)
  • -qq makes watchfiles execute completely quietly
  • -h shows the help and usage

https://gist.github.com/thiagoh/5d8f53bfb64985b94e5bc8b3844dba55

Yettayetti answered 5/4, 2017 at 7:45 Comment(0)
I
1

I ended up doing this for macOS. I'm sure this is terrible in many ways:

#!/bin/sh
# watchAndRun
if [ $# -ne 2 ]; then
    echo "Use like this:"
    echo "   $0 filename-to-watch command-to-run"
    exit 1
fi
if which fswatch >/dev/null; then
    echo "Watching $1 and will run $2"
    while true; do fswatch --one-event $1 >/dev/null && $2; done
else
    echo "You might need to run: brew install fswatch"
fi
Insolation answered 2/2, 2019 at 17:48 Comment(1)
I couldn't figure out fswatch syntax without the while. Which is annoying and makes the script hard to stop.Insolation
C
1

sudo fs_usage -f filesys | grep "interesting thing" ?

Canberra answered 16/9, 2021 at 14:28 Comment(3)
Is this a question or an answer?Ambsace
Welcome to Stack Overflow! While this code may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply.Forfar
Thanks for posting the only option which runs directly on the terminal without installing third party software.Quadrat
C
0

If you want to use NodeJS, you can use a package called chokidar (or chokidar-cli actually) for the watching and then use rsync (included with Mac):

Rsync command:

$ rsync -avz --exclude 'some-file' --exclude 'some-dir' './' '/my/destination'

Chokidar cli (installed globally via npm):

chokidar \"**/*\" -c \"your-rsync-command-above\"

Colvin answered 8/10, 2019 at 0:0 Comment(0)
M
0

I can wholeheartedly recommend using watchexec. Built in Rust and It Just Works™ no matter which platform you're on! Straightforward CLI options as well.

Minton answered 2/3, 2022 at 11:13 Comment(0)
L
-3

Here's a simple single line alternative for users who don't have the watch command who want to execute a command every 3 seconds:

while :; do your-command; sleep 3; done

It's an infinite loop that is basically the same as doing the following:

watch -n3 your-command

Largess answered 11/11, 2013 at 8:39 Comment(2)
This is NOT the same as using a library that listens for events from the filesystem. If in your example, your-command does disk I/O, then that's a guaranteed disk read/write every 3 seconds — or 10,800 times every hour. By using filesystem events you would be guaranteed that I/O (and other expensive operations) only happen when you change a file (which generally is only a couple of times an hour.)Rogerson
True, and good to consider. I usually need to watch things for temporary amounts of time to see what something is doing. I don't use my mentioned technique for production apps or anything like that. For example, I'd like to see the progress of dd, and my technique makes it happen (replacing your-command as appropriate to send the appropriate signal to dd).Largess

© 2022 - 2024 — McMap. All rights reserved.