ThomasReggi
ThomasReggi

Reputation: 59345

Bash script, watch folder, execute command

I am trying to create a bash script with 2 parameters:

I want to watch the directory parameter for changes: when something has been changed the script should execute the command.

I'm running MacOS, not Linux; any pointers or external resources would greatly help as I have see that this is difficult to achieve. Really OI am trying to mimic SASS's watch functionality.

#!/bin/bash

#./watch.sh $PATH $COMMAND

DIR=$1  

ls -l $DIR > $DIR/.begin
#this does not work
DIFFERENCE=$(diff .begin .end)

if [ $DIFFERENCE = '\n']; then
    #files are same
else
    $2
fi 

ls -l $DIR > $DIR/.end

Upvotes: 54

Views: 65943

Answers (14)

Eduin Garcia Cordero
Eduin Garcia Cordero

Reputation: 91

You can use this command to take the full state of a directory.

find src -name "*" | xargs stat -f "%Sa %N" | sort

Example:

PREVIOUS_STATE=""
while true; do
  CURRENT_STATE=`find src -name "*" | xargs stat -f "%Sa %N" | sort`
  if [[ $CURRENT_STATE != $PREVIOUS_STATE ]]; then
    # do something here
    PREVIOUS_STATE=$CURRENT_STATE
  fi
  sleep 0.2
done

Upvotes: 0

Bryan Jyh Herng Chong
Bryan Jyh Herng Chong

Reputation: 630

Radek's answer "sort of" worked for me (OSx) but it slows down my terminal. I made some modifications on it and here's what I think works for me:

#!/bin/bash

daemon() {
    chsum1=""
    targetFolder=path-to-target-folder

    while [[ true ]]
    do
        chsum2=`find ${targetFolder} -type f | xargs stat -f "%m" | md5`
        if [[ $chsum1 != $chsum2 ]] ; then
            # echo the date to indicate we are updating   
            date
            ./do-something.sh
            # tracks the check-sum
            chsum1=$chsum2
        fi
        # sleep for 2 secs
        sleep 2
    done
}

daemon 

The meat is in:

chsum2=`find ${targetFolder} -type f | xargs stat -f "%m" | md5`

which means find all files in ${targetFolder}, pipe that into stat -f "%m" 1-by-1 and then pipe that into md5. stat -f "%m" [filepath] gives you the last modified timestamp.

Do help me improve it. Thanks!

Upvotes: 0

Radek
Radek

Reputation: 3931

To continuously recursively monitor folder (md5) and execute a command on change:

daemon() {
    chsum1=""

    while [[ true ]]
    do
        chsum2=`find src/ -type f -exec md5 {} \;`
        if [[ $chsum1 != $chsum2 ]] ; then           
            if [ -n "$chsum1" ]; then
                compile
            fi
            chsum1=$chsum2
        fi
        sleep 2
    done
}

Works on my OS X as I do not have digest.

On Linux, you can use md5sum as a replacement for the md5 command.

Upvotes: 49

Swift
Swift

Reputation: 13188

METHOD 1:

#!/bin/sh

check() {
    dir="$1"
    chsum1=`digest -a md5 $dir | awk '{print $1}'`
    chsum2=$chsum1

    while [ $chsum1 -eq $chsum2 ]
    do
        sleep 10
        chsum2=`digest -a md5 $dir | awk '{print $1}'`
    done

    eval $2
}

check $*

This script takes in two parameters [directory, command]. Every 10 seconds the script executes check() to see it the folder has changed. If not it sleeps and the cycle repeats.

In the event that the folder has changed, it evals your command.

METHOD 2:
Use a cron to monitor the folder.

You'll have to install incron:

 sudo apt-get install incron

And then your script will look something like this:

#!/bin/bash
eval $1

(You won't need to specify the folder since it will be the job of the cron to monitor the specified directory)

A full, working example can be found here:

http://www.errr-online.com/index.php/2011/02/25/monitor-a-directory-or-file-for-changes-on-linux-using-inotify/

Upvotes: 12

ThomasReggi
ThomasReggi

Reputation: 59345

Here's an example of watching a folder for changes and running a the less compiler when one is updated. As a prereq you need npm and these the module onchange. The node community has a whole bunch of different watch commands (like onchange) I'm not aware of any that are compiled self-contained binaries.

npm install less onchange -g

Then you can use something like:

onchange "./stylesheets/*.less" -- lessc main.less > main.css

I prefer a BASH command over the Grunt answer I gave a while back.

Upvotes: 15

anon
anon

Reputation:

I can’t believe nobody posted this yet.

First make sure inotify-tools are installed.

Then use them like this:

logOfChanges="/tmp/changes.log.csv" # Set your file name here.

# Lock and load
inotifywait -mrcq $DIR > "$logOfChanges" &
IN_PID=$$

# Do your stuff here
...

# Kill and analyze
kill $IN_PID
cat "$logOfChanges" | while read entry; do
   # Split your CSV, but beware that file names may contain spaces too.
   # Just look up how to parse CSV with bash. :)
   path=... 
   event=...
   ...  # Other stuff like time stamps?
   # Depending on the event…
   case "$event" in
     SOME_EVENT) myHandlingCode path ;;
     ...
     *) myDefaultHandlingCode path ;;
done

Alternatively, using --format instead of -c on inotifywait would be an idea.

Just man inotifywait and man inotifywatch for more infos.

Upvotes: 19

S0AndS0
S0AndS0

Reputation: 910

Here's a template to work with, it'll check every 120 seconds for changes in passed directory and notify on creation of directories,files,or names pipes. If you also want to run commands when something is removed then check my other answer on stackoverflow for additional looping examples.

#!/usr/bin/env bash
Var_dir="${1:-/tmp}"
Var_diff_sleep="${2:-120}"
Var_diff_opts="--suppress-common-lines"
Func_parse_diff(){
    _added="$(grep -E '>' <<<"${@}")"
    if [ "${#_added}" != "0" ]; then
        mapfile -t _added_list <<<"${_added//> /}"
        _let _index=0
        until [ "${#_added_list[@]}" = "${_index}" ]; do
            _path_to_check="${Var_dir}/${_added_list[${_index}]}"
            if [ -f "${_path_to_check}" ]; then
                echo "# File: ${_path_to_check}"
            elif [ -d "${_path_to_check}" ]; then
                echo "# Directory: ${_path_to_check}"
            if [ -p "${_path_to_check}" ]; then
                echo "# Pipe: ${_path_to_check}"
            fi
            let _index++
        done
        unset _index
    fi
}
Func_watch_bulk_dir(){
    _current_listing=""
    while [ -d "${Var_dir}" ]; do
        _new_listing="$(ls "${Var_dir}")"
        _diff_listing="$(diff ${Var_dec_diff_opts} <(${Var_echo} "${_current_listing}") <(${Var_echo} "${_new_listing}"))"
        if [ "${_diff_listing}" != "0" ]; then
            Func_parse_diff "${_diff_listing}"
        fi
        _current_listing="${_new_listing}"
        sleep ${Var_diff_sleep}
    done
}

Hint if you replace the echo lines above with eval <some command> for each type of action monitored for you'll be all the closer to automation of actions. And if you wish to see what the above looks like when used within a script then check out the latest script version for the project I've been working on for automation of encryption and decryption via gpg and tar.

Upvotes: 0

Devonte
Devonte

Reputation: 3465

#!/bin/bash

# Author: Devonte
# NGINX WATCH DAEMON
# Place file in root of nginx folder /etc/nginx
# This will test your nginx config on any change and
# if there are no problems it will reload your configuration
# USAGE: sh nginx-watch.sh

dir=`dirname $0`

checksum_initial=`tar -cf - $dir | md5sum | awk '{print $1}'`
checksum_now=$checksum_initial

# Start nginx
nginx

while true
do
    sleep 3
    checksum_now=`tar -cf - $dir | md5sum | awk '{print $1}'`

    if [ $checksum_initial != $checksum_now ]; then
        echo "[ NGINX ] A configuration file changed. Reloading..."
        nginx -t && nginx -s reload;
    fi

    checksum_initial=$checksum_now
done

Upvotes: 1

swalog
swalog

Reputation: 4536

I wrote a general utility called watchfile for simplifying these kinds of operations.

It is less powerfull than inotifywatch, but I prefer a simpler, less verbose utility.

For the desired task, you want to monitor if any files in current directory have been modified. To list all files recursively in the current directory:

find . -type f

To output the timestamp information of each of these files:

find . -type f -print0 | xargs -0 stat

Now, you can monitor this output with the watchfile utility and execute a command CMD when this information changes:

watchfile -s "find . -type f -print0 | xargs -0 stat" -e CMD

Upvotes: 1

Devrim
Devrim

Reputation: 2904

probably the fastest way of doing it.. (on 1G git repo, returns under 1sec.)

#!/bin/bash

watch() {

    echo watching folder $1/ every $2 secs.

while [[ true ]]
do
    files=`find $1 -type f -mtime -$2s`
    if [[ $files == "" ]] ; then
        echo "nothing changed"
    else
            echo changed, $files
    fi
    sleep $2
done
}

watch folder 3

Upvotes: 6

ThomasReggi
ThomasReggi

Reputation: 59345

Almost 3 years later and I'd recommend this grunt based solution.

I created a working example here https://github.com/reggi/watch-execute.

Here's the Gruntfile.js:

module.exports = function (grunt) {
  grunt.initConfig({
    shell: {
      run_file:{
        command: 'sh ./bash.sh',
        options: {
            stdout: true
        }
      }
    },
    watch: {
      run_file: {
        files: ["./watchme/*"],
        tasks: ["shell:run_file"]
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-shell');
};

Upvotes: 1

exic
exic

Reputation: 2648

If you only need to check for files being created/deleted on top level (not checking subfolders) you might want to use the following.

It uses few ressources hence it can react quickly, I use it to check for a changed file.

#!/bin/bash

file="$1"
shift

tmp=$(mktemp)
trap 'rm "$tmp"' EXIT

while true; do
    while [ ! "$tmp" -ot "$file" ]; do
        sleep 0.5
    done
    eval "$@ &"
    echo $! > "$tmp"
    wait
done

Upvotes: 0

John Vargo
John Vargo

Reputation: 530

In Mac OS X, you can just control-click a folder, then click 'Folder Actions Setup'. This will allow you attach actions to a folder, i.e. scripts to run.

OS X comes with a number of prebuilt scripts, or you can create your own.

Upvotes: 3

Eric Fortis
Eric Fortis

Reputation: 17350

Why not using AppleScript

http://www.tuaw.com/2009/03/26/applescript-exploring-the-power-of-folder-actions-part-iii/

on adding folder items to this_folder after receiving added_items
tell application "Finder"
...

Upvotes: 0

Related Questions