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.
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 thexargs
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.
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 fswatch ./ | xargs -I{} cp {} ~/Dropbox/backup/latest/
–
Gounod fswatch .
will monitor current directory. –
Lorsung 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 compilation –
Polyphemus 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 while
because you are choosing to use --one-event
. The equivalent is fswatch eraseme-001.swift | xargs -n1 -I{} swift {}
–
Kilbride 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.
/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 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 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.
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...
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; done
–
Compassion 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).
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 macosapt install entr
on Debian/Ubuntu
Apple OSX Folder Actions allow you to automate tasks based on actions taken on a folder.
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.
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.
brew install https://raw.github.com/mlevin2/homebrew/116b43eaef08d89054c2f43579113b37b4a2abd3/Library/Formula/fswatch.rb
–
Casie 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 theverbose
mode which shows debug information-q
makeswatchfiles
execute quietly (#
will be shown so the user can see the program is executing)-qq
makeswatchfiles
execute completely quietly-h
shows the help and usage
https://gist.github.com/thiagoh/5d8f53bfb64985b94e5bc8b3844dba55
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
sudo fs_usage -f filesys | grep "interesting thing" ?
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\"
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.
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
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 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.
fswatch
util presented as an answer below does in fact use this method. – Andreas