Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

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.

share|improve this question
    
Possible cross site duplicate of: stackoverflow.com/questions/2972765/… ( although here it is on topic =) ) –  Ciro Santilli Mar 11 at 17:31
add comment

13 Answers

up vote 94 down vote accepted

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

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

This has a big limitation: if some program replaces myfile.py with a different file, rather than writing to the existing myfile, inotifywait will die. Most editors work that way.

To overcome this limitation, use inotifywait on the directory:

while true; do
  change=$(inotifywait -e close_write,moved_to,create .)
  change=${change#./ * }
  if [ "$change" = "myfile.py" ]; then ./myfile.py; fi
done
share|improve this answer
11  
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 –  Denilson Sá Aug 30 '10 at 0:57
    
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done is fantastic. Thank you. –  Rhys Ulerich Dec 15 '11 at 16:49
1  
inotifywait does not play well with temporary files. If you save a file with vim (:w) you'll get 2 CREATE and DELETE signals, as well as 2 MOVE signals. stackoverflow.com/questions/10527936/… –  puk May 10 '12 at 22:30
1  
inotifywait -e delete_self seems to work well for me. –  Kos Oct 1 '13 at 11:29
    
Nice one, boostrap 3 require to compile style.less whatever less file was changed :) while inotifywait -e close_write less/*; do lessc --verbose less/style.less > css/style.css; done –  sobi3ch Feb 11 at 12:55
add comment

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.

share|improve this answer
    
nice simple script! –  heinrich5991 Aug 31 '12 at 17:16
1  
@ysangkok yes it does, in the latest version of the code :) –  joh Oct 11 '12 at 16:35
9  
As much as an interesting approach this is - I will have to down vote. This is essentially an infinite loop checking the time of the file every half second. inotifywait is a MUCH better solution as it relies on the kernel to tell it that a file was changed instead of the date on the file. Windows also has a very similar API. –  Nathan Adams Nov 11 '12 at 22:35
2  
joh seems to have updated the script to use inotify now! –  rescdsk Sep 30 '13 at 17:35
1  
@rescdsk yup, you can even watch entire directories for changes now :) –  joh Oct 4 '13 at 9:22
show 2 more comments

For future visitors, 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.

share|improve this answer
    
However, it only watches .js and .coffee files. –  cormacrelf Jul 20 '12 at 10:27
2  
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 '13 at 15:15
    
I didn't know nodemon was this awesome, thank you –  pyeleven Oct 24 '13 at 18:38
add comment

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.

share|improve this answer
add comment

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.

share|improve this answer
5  
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. –  Gilles Aug 27 '10 at 22:22
add comment

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

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

   if [[ "$ATIME" != "$LTIME" ]]
   then    
       echo "RUN COMMNAD"
       LTIME=$ATIME
   fi
   sleep 5
done
share|improve this answer
1  
Bonus points for simplicity and using basic build-in unix tools :) –  CJBrew Oct 18 '13 at 10:54
add comment

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

share|improve this answer
add comment

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

/usr/ports/sysutils/wait_on
share|improve this answer
add comment

entr (http://entrproject.org/) 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. At the time of writing (Oct 2013) it is still being actively maintained.

share|improve this answer
1  
that works great and seems to be actively maintained. –  nikola Mar 10 at 14:28
add comment

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.

share|improve this answer
add comment

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

share|improve this answer
6  
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. –  Denilson Sá Aug 28 '10 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 '12 at 4:52
1  
@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. –  Janus Troelsen Sep 19 '12 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 '12 at 17:55
add comment

Under Linux:

man watch
watch -n 2 command_to_run

will run the command every 2 seconds.

share|improve this answer
add comment

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"
    
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.