Reputation: 59345
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
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
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
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
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 eval
s 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:
Upvotes: 12
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
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
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
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
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
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
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
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
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
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