Bash script, watch folder, execute command

2019-01-12 22:52发布

问题:

I am trying to create a bash script that takes 2 parameters: a directory and a command. I need to watch this directory for changes and when something has been changed I need to execute the command. I'm really new to bash scripting and am not really sure what I am doing, so go easy on me. I'm also on a mac, not linux. Any pointers, or external resources would greatly help. I know that a lot of people try this on the internet and no one can seem to get it right. I am really 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

回答1:

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



回答2:

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.



回答3:

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 you're 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/



回答4:

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.



回答5:

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


回答6:

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.



回答7:

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

  • grunt
  • grunt-contrib-watch
  • grunt-shell

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');
};


回答8:

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


回答9:

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


回答10:

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


回答11:

#!/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


回答12:

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.



标签: macos bash shell