642

I want a quick and simple way to execute a command whenever a file changes. I want something very simple, something I will leave running on a terminal and close it whenever I'm finished working with that file.

Currently, I'm using this:

while read; do ./myfile.py ; done

And then I need to go to that terminal and press Enter, whenever I save that file on my editor. What I want is something like this:

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done

Or any other solution as easy as that.

BTW: I'm using Vim, and I know I can add an autocommand to run something on BufWrite, but this is not the kind of solution I want now.

Update: I want something simple, discardable if possible. What's more, I want something to run in a terminal because I want to see the program output (I want to see error messages).

About the answers: Thanks for all your answers! All of them are very good, and each one takes a very different approach from the others. Since I need to accept only one, I'm accepting the one that I've actually used (it was simple, quick and easy-to-remember), even though I know it is not the most elegant.

4
  • Possible cross site duplicate of: stackoverflow.com/questions/2972765/… ( although here it is on topic =) ) Mar 11, 2014 at 17:31
  • 4
    The solution by Jonathan Hartley builds on other solutions here and fixes big problems that the top-voted answers have: missing some modifications and being inefficient. Please change the accepted answer to his, which also is being maintained on github at github.com/tartley/rerun2 (or to some other solution without those flaws)
    – nealmcb
    Nov 23, 2015 at 16:12
  • watch -g terminates when output of the command changes, e.g.: while :; do watch -gn .1 stat -c %Z file.tex && pdflatex -interaction batchmode file.tex && pkill -HUP mupdf; done. Where : evaluates to true and -n .1 runs the command (stat) every 0.1 seconds. stat -c %Z gives time file was last accessed. Downside is this can only be stopped with ^Z, kill %1
    – Zaz
    Sep 24, 2021 at 20:39
  • 1
    @Zaz Instead of while :; do …; done, I usually use while sleep 1; do …; done. This way, I can easily ^C during the sleep call. Plus, it avoids hogging the CPU by limiting the loop to run at most at 1Hz (adjustable by changing the sleep parameters). Sep 27, 2021 at 20:17

41 Answers 41

564

Simple, using inotifywait (install your distribution's inotify-tools package):

while inotifywait -e close_write myfile.py; do ./myfile.py; done

or

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done

The first snippet is simpler, but it has a significant downside: it will miss changes performed while inotifywait isn't running (in particular while myfile is running). The second snippet doesn't have this defect. However, beware that it assumes that the file name doesn't contain whitespace. If that's a problem, use the --format option to change the output to not include the file name:

inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
  ./myfile.py
done

Either way, there is a limitation: if some program replaces myfile.py with a different file, rather than writing to the existing myfile, inotifywait will die. Many editors work that way.

To overcome this limitation, use inotifywait on the directory:

inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
  if [ "$filename" = "myfile.py" ]; then
    ./myfile.py
  fi
done

Alternatively, use another tool that uses the same underlying functionality, such as incron (lets you register events when a file is modified) or fswatch (a tool that also works on many other Unix variants, using each variant's analog of Linux's inotify).

28
  • 54
    I've encapsulated all of this (with quite a few bash tricks) in a simple-to-use sleep_until_modified.sh script, available at: bitbucket.org/denilsonsa/small_scripts/src Aug 30, 2010 at 0:57
  • 15
    while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done is fantastic. Thank you. Dec 15, 2011 at 16:49
  • 6
    inotifywait -e delete_self seems to work well for me.
    – Kos
    Oct 1, 2013 at 11:29
  • 13
    For some reason while inotifywait -e close_write myfile.py; do ./myfile.py; done always exits without running the command (bash and zsh). For this to work I needed to add || true, eg: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done
    – ideasman42
    Aug 3, 2016 at 23:36
  • 4
    Update: the URL mentioned in the first comment has moved: github.com/denilsonsa/small_scripts/blob/master/… Jul 1, 2020 at 20:19
263

entr (https://github.com/eradman/entr) provides a more friendly interface to inotify (and also supports *BSD & Mac OS X).

It makes it very easy to specify multiple files to watch (limited only by ulimit -n), takes the hassle out of dealing with files being replaced, and requires less bash syntax:

$ find . -name '*.py' | entr ./myfile.py

I've been using it on my entire project source tree to run the unit tests for the code I'm currently modifying, and it's been a huge boost to my workflow already.

Flags like -c (clear the screen between runs) and -d (exit when a new file is added to a monitored directory) add even more flexibility, for example you can do:

$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done

As of early 2018 it's still in active development and it can be found in Debian & Ubuntu (apt install entr); building from the author's repo was pain-free in any case.

15
  • 10
    Doesn't handle new files and their modifications.
    – Wernight
    Apr 29, 2014 at 12:58
  • 3
    @Wernight - as of 7th May 2014 entr has the new -d flag; it's slightly more long-winded, but you can do while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done to deal with new files. May 23, 2014 at 8:27
  • 2
    entr is also available in debian repos at least from debian jessie/8.2 on... Oct 18, 2015 at 21:59
  • 8
    It is worth noting that entr is available on Homebrew, so brew install entr will work as expected
    – jmarceli
    Dec 14, 2017 at 15:42
  • 1
    beware that entr doesn't respond to ATTRIB events.
    – jchook
    Jun 22, 2018 at 1:44
149

I wrote a Python program to do exactly this called when-changed.

Usage is simple:

when-changed FILE COMMAND...

Or to watch multiple files:

when-changed FILE [FILE ...] -c COMMAND

FILE can be a directory. Watch recursively with -r. Use %f to pass the filename to the command.

19
  • 1
    @ysangkok yes it does, in the latest version of the code :)
    – joh
    Oct 11, 2012 at 16:35
  • 6
    Now available from "pip install when-changed". Still works nicely. Thanks. Jan 8, 2015 at 18:37
  • 2
    To clear the screen first you can use when-changed FILE 'clear; COMMAND'. Feb 14, 2015 at 9:29
  • 1
    This answer is so much better because I can do it on Windows, too. And this guy actually wrote a program to get the answer. May 13, 2015 at 15:33
  • 5
    Good news everyone! when-changed is now cross-platform! Check out the latest 0.3.0 release :)
    – joh
    Jan 23, 2016 at 2:41
73

How about this script? It uses the stat command to get the access time of a file and runs a command whenever there is a change in the access time (whenever file is accessed).

#!/bin/bash

### Set initial time of file
LTIME=`stat -c %Z /path/to/the/file.txt`

while true    
do
   ATIME=`stat -c %Z /path/to/the/file.txt`

   if [[ "$ATIME" != "$LTIME" ]]
   then    
       echo "RUN COMMAND"
       LTIME=$ATIME
   fi
   sleep 5
done
5
  • 4
    Wouldn't stat-ing the modified time be a better "whenever a file changes" answer?
    – Xen2050
    Feb 27, 2016 at 16:42
  • 1
    Would running stat many times per second cause many reads to the disk? or would the fstat system call automatically make cache these responses somehow? I'm trying to write a sort of 'grunt watch' to compile my c code whenever I make changes Dec 7, 2016 at 18:01
  • This is good if you know the filename to be watched in advance. Better would be to pass the filename to the script. Better still would be if you could pass many filenames (eg. "mywatch *.py"). Better still would be if it could operate recursively on files in subdirs too, which some of the other solutions do. Feb 16, 2017 at 14:18
  • 9
    Just in case anyone is wondering about heavy reads, I tested this script in Ubuntu 17.04 with a sleep of 0.05s and vmstat -d to watch for disk access. It seems linux does a fantastic job at caching this sort of thing :D Aug 2, 2017 at 0:01
  • Looks like stat -f %m /path/to/the/file.txt is the MacOS equivalent. Aug 24, 2022 at 17:39
54

For those who can't install inotify-tools like me, this should be useful:

watch -d -t -g ls -lR

This command will exit when the output changes, ls -lR will list every file and directory with its size and dates, so if a file is changed it should exit the command, as man says:

-g, --chgexit
          Exit when the output of command changes.

I know this answer may not be read by anyone, but I hope someone would reach to it.

Command line example:

~ $ cd /tmp
~ $ watch -d -t -g ls -lR && echo "1,2,3"

Open another terminal:

~ $ echo "testing" > /tmp/test

Now the first terminal will output 1,2,3

Simple script example:

#!/bin/bash
DIR_TO_WATCH=${1}
COMMAND=${2}

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}
10
  • 10
    Nice hack. I tested and it seems to have a problem when the listing is long and the changed file falls outside the screen. A small modification could be something like this: watch -d -t -g "ls -lR tmp | sha1sum"
    – Atle
    Dec 16, 2016 at 14:10
  • 3
    if you watch your solution every second, it works forever and run MY_COMMAND only if some file changes: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND"
    – mnesarco
    Jul 23, 2017 at 2:26
  • My version of watch (On Linux, watch from procps-ng 3.3.10) accepts float seconds for its interval, hence watch -n0.2 ... will poll every fifth of a second. Good for those healthy sub-millisecond unit tests. Apr 19, 2018 at 16:23
  • 1
    @FantomX1 For a one-liner or handy alias, something like this should work: while [[ 1=1 ]] ; do watch -n 5 -g 'ls -lrt --full-time ./Dockerfile' && docker build . ; sleep 6 ; done or while [[ 1=1 ]] ; do watch -n 5 -g 'ls -lrt --full-time *.c *.h ' && make clean && make ; sleep 6 ; done
    – Justin
    Jan 11, 2021 at 21:44
  • 3
    The command line I end finding useful is: watch -n1 "watch -t -g ls --full-time >/dev/null && MY_CMD" Note the --full-time otherwise the timestamp is only to the minute, and the redirect of the watch-ls output to dev null.
    – TvE
    Dec 25, 2021 at 19:18
35

Solution using Vim:

:au BufWritePost myfile.py :silent !./myfile.py

But I don't want this solution because it's kinda annoying to type, it's a bit hard to remember what to type, exactly, and it's a bit difficult to undo its effects (need to run :au! BufWritePost myfile.py). In addition, this solution blocks Vim until the command has finished executing.

I've added this solution here just for completeness, as it might help other people.

To display the program output (and completely disrupt your editting flow, as the output will write over your editor for a few seconds, until you press Enter), remove the :silent command.

2
  • 1
    This can be quite nice when combined with entr (see below) - just make vim touch a dummy file that entr is watching, and let entr do the rest in the background... or tmux send-keys if you happen to be in such an environment :) May 23, 2014 at 8:42
  • nice! you can make a macro for your .vimrc file Mar 19, 2018 at 1:49
33

rerun2 (on github) is a 10-line Bash script of the form:

#!/usr/bin/env bash

function execute() {
    clear
    echo "$@"
    eval "$@"
}

execute "$@"

inotifywait --quiet --recursive --monitor --event modify --format "%w%f" . \
| while read change; do
    execute "$@"
done

Save the github version as 'rerun' on your PATH, and invoke it using:

rerun COMMAND

It runs COMMAND every time there's a filesystem modify event within your current directory (recursive.)

Things one might like about it:

  • It uses inotify, so is more responsive than polling. Fabulous for running sub-millisecond unit tests, or rendering graphviz dot files, every time you hit 'save'.
  • Because it's so fast, you don't have to bother telling it to ignore large subdirs (like node_modules) just for performance reasons.
  • It's extra super responsive, because it only calls inotifywait once, on startup, instead of running it, and incurring the expensive hit of establishing watches, on every iteration.
  • It's just 12 lines of Bash
  • Because it's Bash, it interprets commands you pass it exactly as if you had typed them at a Bash prompt. (Presumably this is less cool if you use another shell.)
  • It doesn't lose events that happen while COMMAND is executing, unlike most of the other inotify solutions on this page.
  • On the first event, it enters a 'dead period' for 0.15 seconds, during which other events are ignored, before COMMAND is run exactly once. This is so that the flurry of events caused by the create-write-move dance which Vi or Emacs does when saving a buffer don't cause multiple laborious executions of a possibly slow-running test suite. Any events which then occur while COMMAND is executing are not ignored - they will cause a second dead period and subsequent execution.

Things one might dislike about it:

  • It uses inotify, so won't work outside of Linuxland.
  • Because it uses inotify, it will barf on trying to watch directories containing more files than the max number of user inotify watches. By default, this seems to be set to around 5,000 to 8,000 on different machines I use, but is easy to increase. See https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • It fails to execute commands containing Bash aliases. I could swear that this used to work. In principle, because this is Bash, not executing COMMAND in a subshell, I'd expect this to work. I'd love to hear If anyone knows why it doesn't. Many of the other solutions on this page can't execute such commands either.
  • Personally I wish I was able to hit a key in the terminal it's running in to manually cause an extra execution of COMMAND. Could I add this somehow, simply? A concurrently running 'while read -n1' loop which also calls execute?
  • Right now I've coded it to clear the terminal and print the executed COMMAND on each iteration. Some folks might like to add command-line flags to turn things like this off, etc. But this would increase size and complexity many-fold.

This is a refinement of @cychoi's anwer.

5
  • 2
    I believe you should use "$@" instead of $@, in order to properly work with arguments containing spaces. But at the same time you use eval, which forces the user of rerun to be extra careful when quoting. Sep 10, 2015 at 15:20
  • Thanks Denilson. Could you given an example of where quoting needs to be done carefully? I've been using it the last 24 hours and haven't seen any problems with spaces thus far, nor carefully quoted anything - just invoked as rerun 'command'. Are you just saying that if I used "$@", then the user could invoke as rerun command (without quotes?) That doesn't seem as useful for me: I generally don't want Bash to do any processing of command before passing it to rerun. e.g. if command contains "echo $myvar", then I'll want to see the new values of myvar in each iteration. Sep 11, 2015 at 17:53
  • 1
    Something like rerun foo "Some File" might break. But since you are using eval, it can be rewritten as rerun 'foo "Some File". Note that sometimes the path expansion might introduce spaces: rerun touch *.foo will likely break, and using rerun 'touch *.foo' has slightly different semantics (path expansion happening only once, or multiple times). Sep 11, 2015 at 23:07
  • Thanks for the help. Yep: rerun ls "some file" breaks because of the spaces. rerun touch *.foo* works fine usually, but fails if the filenames that match *.foo contain spaces. Thanks for helping me see how rerun 'touch *.foo' has different semantics, but I suspect the version with single quotes is the semantic I want: I want each iteration of rerun to act as if I typed the command over again - hence I want *.foo to be expanded on each iteration. I'll try your suggestions to examine their effects... Sep 12, 2015 at 5:52
  • More discussion on this PR (github.com/tartley/rerun2/pull/1) and others. Sep 12, 2015 at 6:09
32

If you happen to have npm installed, nodemon is probably the easiest way to get started, especially on OS X, which apparently doesn't have inotify tools. It supports running a command when a folder changes.

4
  • 5
    However, it only watches .js and .coffee files.
    – zelk
    Jul 20, 2012 at 10:27
  • 7
    The current version seems to support any command, for example: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models
    – kek
    Apr 10, 2013 at 15:15
  • 1
    I wish I had more info, but osx does have a method to track changes, fsevents Oct 7, 2014 at 4:34
  • 1
    On OS X you can also use Launch Daemons with a WatchPaths key as shown in my link.
    – Adam Johns
    Nov 16, 2014 at 23:48
19

if you have nodemon installed, then you can do this:

nodemon -w <watch directory> -x "<shell command>" -e ".html"

In my case I edit html locally and ship it to my remote server when a file changes.

nodemon -w <watch directory> -x "scp filename [email protected]:/var/www" -e ".html"
15

Here's a simple shell Bourne shell script that:

  1. Takes two arguments: the file to be monitored and a command (with arguments, if necessary)
  2. Copies the file you are monitoring to the /tmp directory
  3. Checks every two seconds to see if the file you are monitoring is newer than the copy
  4. If it's newer it overwrites the copy with the newer original and executes the command
  5. Cleans up after itself when you press Ctr-C

    #!/bin/sh  
    f=$1  
    shift  
    cmd=$*  
    tmpf="`mktemp /tmp/onchange.XXXXX`"  
    cp "$f" "$tmpf"  
    trap "rm $tmpf; exit 1" 2  
    while : ; do  
        if [ "$f" -nt "$tmpf" ]; then  
            cp "$f" "$tmpf"  
            $cmd  
        fi  
        sleep 2  
    done  
    

This works on FreeBSD. The only portability issue I can think of is if some other Unix doesn't have the mktemp(1) command, but in that case you can just hard code the temp file name.

2
  • 9
    Polling is the only portable way, but most systems have a file change notification mechanism (inotify on Linux, kqueue on FreeBSD, ...). You have a severe quoting problem when you do $cmd, but fortunately that's easily fixable: ditch the cmd variable and execute "$@". Your script is not suitable for monitoring a large file, but that could be fixed by replacing cp by touch -r (you only need the date, not the contents). Portability-wise, the -nt test requires bash, ksh or zsh. Aug 27, 2010 at 22:22
  • 1
    I'm curious, how this compares with superuser.com/a/634313 Is creating a copy more portable than just stat on the file?
    – CervEd
    Jan 8, 2022 at 12:53
11

Have a look at incron. It's similar to cron, but uses inotify events instead of time.

2
  • This can be made to work, but creating an incron entry is quite a labour intensive process compared to other solutions on this page. Sep 9, 2015 at 20:37
  • This project was abandoned, then forked on GitHub (several Linux distributions are using this fork), then abandoned again… Jun 9, 2022 at 3:58
8

If your program generates some sort of log/output, you can create a Makefile with a rule for that log/output that depends on your script and do something like

while true; do make -s my_target; sleep 1; done

Alternately, you can create a phony target and have the rule for it both call your script and touch the phony target (while still depending on your script).

4
  • 11
    while sleep 1 ; do something ; done is slightly better than while true ; do something ; sleep 1 ; done. At least it stops easily when pressing Ctrl+C. Aug 28, 2010 at 4:59
  • Will removing the sleep cause a busy loop (CPU generating heat and hurting battery life on a laptop)?
    – Steven Lu
    Jul 12, 2012 at 4:52
  • 3
    @StevenLu: no, the sleep is not a busy wait. The problem is that if the sleep is in the body, Control-C will kill the sleep and the loop will start over. Power usage of starting the loop over is insignificant. Try it yourself in a terminal. You need to hold Control-C for it to work, if you have sleep in the body. Sep 19, 2012 at 11:25
  • Right. I think I missed it and didn't see that the sleep is still present as the loop condition. That little tweak is pretty awesome.
    – Steven Lu
    Sep 19, 2012 at 17:55
8

Another solution with NodeJs, fsmonitor :

  1. Install

    sudo npm install -g fsmonitor
    
  2. From command line (example, monitor logs and "retail" if one log file change)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    
2
  • Side note: the example could be solved by tail -F -q *.log, I think. Apr 18, 2015 at 1:39
  • It was just to give an example, tail -f doesn't clear the terminal.
    – Atika
    Feb 11, 2019 at 15:45
8

Under Linux:

man watch

watch -n 2 your_command_to_run

Will run the command every 2 seconds.

If your command takes more than 2 seconds to run, watch will wait until it's done before doing it again.

3
  • That's pretty simple, though somewhat of a waste, it's easy for development tasks like making live changes to styles.
    – Xeoncross
    Jul 15, 2014 at 18:32
  • @thirtythreeforty A quick experiment on Ubuntu shows that watch will wait the full two seconds no matter how long the command takes to run. FWIW, the sleep period can be specified with '-n', down to a minimum of 0.1 seconds. Sep 9, 2015 at 20:36
  • This does not answer the question but runs a program periodical.
    – dirdi
    Dec 1, 2019 at 12:49
8

Improved upon edit#3 of Gilles's answer.

This version runs inotifywait once and monitors for events (.e.g.: modify) thereafter. Such that inotifywait doesn't need to be re-executed upon every event encountered.

It's quick and fast!(even when monitoring large directory recursively)

inotifywait --quiet --monitor --event modify FILE | while read; do
    # trim the trailing space from inotifywait output
    REPLY=${REPLY% }
    filename=${REPLY%% *}
    # do whatever you want with the $filename
done
3
  • 1
    This is the best answer on the page for Linux-only users. Replace the stuff inside the loop with 'execute $@', and the user could call this script passing in their own command to run. It even works with commands that contain shell aliases if you source it, using something like ". scriptname COMMAND". This will still find scriptname on the PATH. Sep 9, 2015 at 21:00
  • I am having an issue, probably my ignorance. This does not seem to work for me. inotifywait --quiet --monitor --event modify $1 | while read; do # trim the trailing space from inotifywait output REPLY=${REPLY% } filename=${REPLY%% *} # do whatever you want with the $filename /usr/bin/pdfmom $1 > evince $2.pdf; done any help with this would be appreciated
    – A Feldman
    Oct 18, 2023 at 20:12
  • Ooops; inotifywait --quiet --monitor --event modify $1 | while read; do # trim the trailing space from inotifywait output REPLY=${REPLY% } filename=${REPLY%% *} # do whatever you want with the $filename /usr/bin/pdfmom $1 > $2.pdf; evince $2.pdf; done is what I should have tried, and still doesn't work
    – A Feldman
    Oct 18, 2023 at 20:40
7

Look into Guard, in particular with this plugin:

https://github.com/hawx/guard-shell

You can set it up to watch any number of patterns in your project's directory, and execute commands when changes occur. Good chance even that there's a plugin available for that what you're trying to do in the first place.

1
  • Note that Guard is configured in Ruby, which might be a pros or cons. Jun 9, 2022 at 4:15
7

Watchdog is a Python project, and may be just what you're looking for:

Supported platforms

  • Linux 2.6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD/BSD (kqueue)
  • Windows (ReadDirectoryChangesW with I/O completion ports; ReadDirectoryChangesW worker threads)
  • OS-independent (polling the disk for directory snapshots and comparing them periodically; slow and not recommended)

Just wrote a command-line wrapper for it watchdog_exec:

Example runs

On fs event involving files and folders in current directory, run echo $src $dst command, unless it the fs event is modified, then run python $src command.

python -m watchdog_exec . --execute echo --modified python

Using short arguments, and restricting to only execute when events involve "main.py":

python -m watchdog_exec . -e echo -a echo -s __main__.py

EDIT: Just found Watchdog has an official CLI called watchmedo, so check that out also.

6

I like the simplicity of while inotifywait ...; do ...; done however it has two issues:

  • File changes happening during the do ...; will be missed
  • Slow when using in recursive mode

Therefor I made a helper script that uses inotifywait without those limitations: inotifyexec

I suggest you put this script in your path, like in ~/bin/. Usage is described by just running the command.

Example: inotifyexec "echo test" -r .

2
  • Updated the script to support regex pattern matching.
    – Wernight
    Oct 21, 2014 at 12:56
  • Both problems are solved by using inotifywait in "--monitor" mode. See cychoi's answer. Sep 9, 2015 at 20:01
5

swarminglogic wrote a script called watchfile.sh, also available as a GitHub Gist.

1
  • 2
    This is a feature-packed 200 line Bash script that polls stat on the given filenames, runs md5sum on the output, and re-runs the given command if this value changes. Because it's Bash, I suspect it does a good job of running the given command exactly as if you typed it at a Bash prompt. (In contrast, most of the solutions here written in other languages will fail to execute commands which, for example, contain shell aliases such as ll) Sep 9, 2015 at 20:30
5

Improved Sebastian's solution with watch command:

watch_cmd.sh:

#!/bin/bash
WATCH_COMMAND=${1}
COMMAND=${2}

while true; do
  watch -d -g "${WATCH_COMMAND}"
  ${COMMAND}
  sleep 1     # to allow break script by Ctrl+c
done

Call example:

watch_cmd.sh "ls -lR /etc/nginx | grep .conf$" "sudo service nginx reload"

It works but be careful: watch command has known bugs (see man): it reacts on changes only in VISIBLE in terminal parts of -g CMD output.

5

You could try reflex.

Reflex is a small tool to watch a directory and rerun a command when certain files change. It's great for automatically running compile/lint/test tasks and for reloading your application when the code changes.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make
1
4

A little more on the programming side, but you want something like inotify. There are implementations in many languages, such as jnotify and pyinotify.

This library allows you to monitor single files or entire directories, and returns events when an action is discovered. The information returned includes the file name, the action (create, modify, rename, delete) and the file path, among other useful information.

4

For those of you who are looking for a FreeBSD solution, here is the port:

/usr/ports/sysutils/wait_on
3

A oneliner answer that I'm using to keep track on a file change:

$ while true ; do NX=`stat -c %Z file` ; [[ $BF != $NX ]] && date >> ~/tmp/fchg && BF=$NX || sleep 2 ; done

You don't need to initialize BF if you know that the first date is the starting time.

This is simple and portable. There is another answer based on the same strategy using a script here. Take a look also.


Usage: I'm using this to debug and keep an eye on ~/.kde/share/config/plasma-desktop-appletsrc; that for some unknown reason keeps loosing my SwitchTabsOnHover=false

3

I use this script to do it. I'm using inotify in monitor-mode

#!/bin/bash
MONDIR=$(dirname $1)
ARQ=$(basename $1)

inotifywait -mr -e close_write $MONDIR | while read base event file 
do
  if (echo $file |grep -i "$ARQ") ; then
    $1
  fi
done

Save this as runatwrite.sh

Usage: runatwrite.sh myfile.sh

it will run myfile.sh at each write.

3

Check out https://github.com/watchexec/watchexec.

watchexec is a simple, standalone tool that watches a path and runs a command whenever it detects modifications.

Example

Watch all JavaScript, CSS and HTML files in the current directory and all subdirectories for changes, running make when a change is detected:

$ watchexec --exts js,css,html make

2

For people who find this by Googling for changes to a particular file, the answer is much simpler (inspired by Gilles's answer).

If you want to do something after a particular file has been written to, here's how:

while true; do
  inotifywait -e modify /path/to/file
  # Do something *after* a write occurs, e.g. copy the file
  /bin/cp /path/to/file /new/path
done

Save this as, for example, copy_myfile.sh and put the .sh file into the /etc/init.d/ folder to have it run on startup.

1
  • Shares the problem with Giles' answer that it runs inotifywait on every iteration, which can be unresponsive for recursively watching very large directories. See cychoi's answer for the fix to this. Sep 9, 2015 at 19:51
2

For those using OS X, you can use a LaunchAgent to watch a path/file for changes and do something when that happens. FYI - LaunchControl is a good app to easily make/modify/remove daemons/agents.

(example taken from here)

<?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>
2

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

watchfiles <cmd> <paths...>

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

0
2

As a few others have done, I've also written a lightweight command line tool to do this. It's fully documented, tested and modular.

Watch-Do

Installation

You can install it (if you have Python3 and pip) using:

pip3 install git+https://github.com/vimist/watch-do

Usage

Use it straight away by running:

watch-do -w my_file -d 'echo %f changed'

Features Overview

  • Supports file globbing (use -w '*.py' or -w '**/*.py')
  • Run multiple commands on a file change (just specify the -d flag again)
  • Dynamically maintains the list of files to watch if globbing is used (-r to turn this on)
  • Multiple ways to "watch" a file:
    • Modification time (default)
    • File hash
    • Trivial to implement your own (this is the ModificationTime watcher)
  • Modular design. If you want to have commands run, when a file is accessed, it's trivial to write your own watcher (mechanism that determines if the doers should be run).
0

You must log in to answer this question.

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