How to run a shell script when a file or directory changes?

LinuxShellInotify

Linux Problem Overview


I want to run a shell script when a specific file or directory changes.

How can I easily do that?

Linux Solutions


Solution 1 - Linux

You may try entr tool to run arbitrary commands when files change. Example for files:

$ ls -d * | entr sh -c 'make && make test'

or:

$ ls *.css *.html | entr reload-browser Firefox

or print Changed! when file file.txt is saved:

$ echo file.txt | entr echo Changed!

For directories use -d, but you've to use it in the loop, e.g.:

while true; do find path/ | entr -d echo Changed; done

or:

while true; do ls path/* | entr -pd echo Changed; done

Solution 2 - Linux

I use this script to run a build script on changes in a directory tree:

#!/bin/bash -eu
DIRECTORY_TO_OBSERVE="js"      # might want to change this
function block_for_change {
  inotifywait --recursive \
    --event modify,move,create,delete \
    $DIRECTORY_TO_OBSERVE
}
BUILD_SCRIPT=build.sh          # might want to change this too
function build {
  bash $BUILD_SCRIPT
}
build
while block_for_change; do
  build
done

Uses inotify-tools. Check inotifywait man page for how to customize what triggers the build.

Solution 3 - Linux

Use inotify-tools.

The linked Github page has a number of examples; here is one of them.

#!/bin/sh

cwd=$(pwd)

inotifywait -mr \
  --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' \
  -e close_write /tmp/test |
while read -r date time dir file; do
       changed_abs=${dir}${file}
       changed_rel=${changed_abs#"$cwd"/}

       rsync --progress --relative -vrae 'ssh -p 22' "$changed_rel" \
           [email protected]:/backup/root/dir && \
       echo "At ${time} on ${date}, file $changed_abs was backed up via rsync" >&2
done

Solution 4 - Linux

How about this script? 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

Solution 5 - Linux

Check out the kernel filesystem monitor daemon

http://freshmeat.net/projects/kfsmd/

Here's a how-to:

http://www.linux.com/archive/feature/124903

Solution 6 - Linux

As mentioned, inotify-tools is probably the best idea. However, if you're programming for fun, you can try and earn hacker XPs by judicious application of tail -f .

Solution 7 - Linux

Just for debugging purposes, when I write a shell script and want it to run on save, I use this:

#!/bin/bash
file="$1" # Name of file
command="${*:2}" # Command to run on change (takes rest of line)
t1="$(ls --full-time $file | awk '{ print $7 }')" # Get latest save time
while true
do
  t2="$(ls --full-time $file | awk '{ print $7 }')" # Compare to new save time
  if [ "$t1" != "$t2" ];then t1="$t2"; $command; fi # If different, run command
  sleep 0.5
done

Run it as

run_on_save.sh myfile.sh ./myfile.sh arg1 arg2 arg3

Edit: Above tested on Ubuntu 12.04, for Mac OS, change the ls lines to:

"$(ls -lT $file | awk '{ print $8 }')"

Solution 8 - Linux

Add the following to ~/.bashrc:

function react() {
    if [ -z "$1" -o -z "$2" ]; then
        echo "Usage: react <[./]file-to-watch> <[./]action> <to> <take>"
    elif ! [ -r "$1" ]; then
        echo "Can't react to $1, permission denied"
    else
        TARGET="$1"; shift
        ACTION="$@"
        while sleep 1; do
            ATIME=$(stat -c %Z "$TARGET")
            if [[ "$ATIME" != "${LTIME:-}" ]]; then
                LTIME=$ATIME
                $ACTION
            fi
        done
    fi
}

Solution 9 - Linux

Here's another option: http://fileschanged.sourceforge.net/

See especially "example 4", which "monitors a directory and archives any new or changed files".

Solution 10 - Linux

inotifywait can satisfy you.

Here is a common sample for it:

inotifywait -m /path -e create -e moved_to -e close_write | # -m is --monitor, -e is --event
    while read path action file; do
        if [[ "$file" =~ .*rst$ ]]; then             # if suffix is '.rst'
            echo ${path}${file} ': '${action}        # execute your command
            echo 'make html'
            make html
        fi
    done

Solution 11 - Linux

Quick solution for fish shell users who wanna track a single file:

while true
    set old_hash $hash
    set hash (md5sum file_to_watch)
    if [ $hash != $old_hash ]
        command_to_execute
    end
    sleep 1
end

replace md5sum with md5 if on macos.

Solution 12 - Linux

Suppose you want to run rake test every time you modify any ruby file ("*.rb") in app/ and test/ directories.

Just get the most recent modified time of the watched files and check every second if that time has changed.

Script code

t_ref=0; while true; do t_curr=$(find app/ test/ -type f -name "*.rb" -printf "%T+\n" | sort -r | head -n1); if [ $t_ref != $t_curr ]; then t_ref=$t_curr; rake test; fi; sleep 1; done

Benefits

  • You can run any command or script when the file changes.
  • It works between any filesystem and virtual machines (shared folders on VirtualBox using Vagrant); so you can use a text editor on your Macbook and run the tests on Ubuntu (virtual box), for example.

Warning

  • The -printf option works well on Ubuntu, but do not work in MacOS.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew LeSueurView Question on Stackoverflow
Solution 1 - LinuxkenorbView Answer on Stackoverflow
Solution 2 - LinuxDominykas MostauskisView Answer on Stackoverflow
Solution 3 - LinuxFrédéric HamidiView Answer on Stackoverflow
Solution 4 - LinuxVDRView Answer on Stackoverflow
Solution 5 - LinuxBrian ClementsView Answer on Stackoverflow
Solution 6 - LinuxYoricView Answer on Stackoverflow
Solution 7 - LinuxjonatanView Answer on Stackoverflow
Solution 8 - LinuxDan GarthwaiteView Answer on Stackoverflow
Solution 9 - LinuxslowdogView Answer on Stackoverflow
Solution 10 - LinuxpwxcooView Answer on Stackoverflow
Solution 11 - LinuxAdham ZahranView Answer on Stackoverflow
Solution 12 - LinuxIs MaView Answer on Stackoverflow