7 releases (breaking)

0.6.0 Sep 9, 2019
0.5.0 Aug 8, 2019
0.4.1 May 12, 2019
0.3.0 May 12, 2019
0.1.0 Jan 31, 2019

#3 in #inotify

Download history 15/week @ 2019-10-01 21/week @ 2019-10-15 73/week @ 2019-10-22 8/week @ 2019-10-29 9/week @ 2019-11-05 8/week @ 2019-11-12 15/week @ 2019-11-19 22/week @ 2019-11-26 7/week @ 2019-12-03 91/week @ 2019-12-10 1/week @ 2019-12-17 7/week @ 2019-12-24 100/week @ 2020-01-07

98 downloads per month

MIT license

734 lines

IF Filesystem-event Then (IFFT) Latest Version Build Status

IF a filesystem event (create, write, remove, chmod) occurs in a watched folder that is not filtered out by an exclusion rule THEN execute a shell command.

Use this to watch for code changes to trigger: process restart; code compilation; or test run.


If you have rust installed on your machine:

cargo install ifft

Otherwise, check releases for downloads.


Hello, world.

Create a config file (ifft.toml) in a directory (let's say ~/ifft-test):

# Matches everything including sub-folders
if = "**/*"
then = "echo hello, world."

Run ifft with the directory containing your config as the argument: ifft ~/ifft-test.

You'll see the following output, which indicates that ifft found your config file:

Found config: "~/ifft-test/ifft.toml"

In later examples, we'll see that multiple config files can be embedded throughout the directory tree.

Now let's create a file that will trigger ifft: touch ~/ifft-test/test1 You'll see the following output:

[2019-05-12 14:55:57Z] Event: Create("~/ifft-test/test1")
  Match from config in: "~/ifft-test"
  Matched if-cond: "**/*"
[2019-05-12 14:55:57Z] Execute: "echo hello, world." from "~/ifft-test"
  Exit code: 0
    hello, world.

As you can see, triggers report the match condition and the exit code, stdout, and stderr of the triggered command.

That's it. ifft simply listens for file changes and takes action.


Here's a more complex ifft config that would be in a folder such as ~/src with sub-folders my-c-prog and my-rust-prog:

# Never trigger on a backup or swap file. (VIM specific)
not = [

# If any .c or .h files change, recompile.
if = "my-c-prog/**/*.{c,h}"
then = "make"
working_dir = "my-c-prog"

if = "my-rust-prog/**/*.{rs,toml}"
# Ignore changes in the target folder to avoid recursive triggering.
not = ["my-rust-prog/target/*"]
then = "cargo build"
working_dir = "my-rust-prog"

# Contrived example to demonstrate other features.
if = "*"
# {{}} is substituted with the absolute path to the triggering file.
then = "cp -R {{}} ."
# working_dir can be an absolute path. If omitted, the working_dir is set to
# root.
working_dir = "/tmp"

The second ifft condition could be moved into a new ifft.toml in the my-rust-prog folder. For equivalent functionality, the contents would be:

if = "**/*.{rs,toml}"
not = ["target/*"]
then = "cargo build"

This allows you to distribute config files all over, which has the advantage of keeping them small and relevant to the folder they're in.

On start

If you want to automatically trigger iffts on start without any file event, use the -r flag. The argument will trigger any iffts with matching names. For example, running ifft ~/ifft-test -r build will match:

name = "build"
if = "**/*.{rs,toml}"
not = ["target/*"]
then = "cargo build"

This is useful to ensure that projects are built on boot without having to wait for a file event.

You can also use the -q flag to quit after the -r flag triggers have completed. This can be used to initiate a one-time build or clean without listening for changes afterwards.

Another strategy is to omit the if condition which means it can only be triggered on start with the -r flag.


  • Configure with a toml file.
  • Config files can be distributed throughout a directory tree.
  • Use glob patterns for if and not conditions.
  • Global not filtering and per-trigger not filtering.
  • If multiple events trigger the same if, then is only executed if an event was triggered after the last time then was executed.
  • On start, iffts with a matching name can be triggered without any file event.
  • Events on paths with symlink components will also have their absolute-path equivalent tested against triggers.


Tested on Linux and OS X. Untested elsewhere.

Usage with VirtualBox Shared Folders

On the guest OS, VirtualBox Shared Folders do not generate filesystem event notifications. You'll need to use a separate filesystem event forwarder such as notify-forwarder.


  • bazel for a serious incremental build system.
  • watchexec is a more full-featured program.
  • entr has a clean Unixy interface.


  • [] Add .gitignore parsing support.
  • [] Flag to ignore hidden files.
  • [] Flag to control verbosity of prints.
  • [] Group events in quick succession together and trigger only once.
  • [] Allow customization of type of FS events that trigger.
  • [] Low priority: Compute the optimal path prefix for watching.
  • [] Performance: Do not compile glob before each use. Current hack to make it easy to access the glob pattern string if an error occurs.


~127K SLoC