#!/usr/bin/env bash # See documentation below. # # Changelog: # 2023-10-01 Justin: Init from some SO/Gist answer # 2023-11-14 Justin: Added recursive option # Events that occur within this time from an initial one are ignored cooldown_s='0.250' clear='' verbose='' recursive='' # check dependencies if ! command -v inotifywait >/dev/null then echo "error: inotifywait: command not found. hint: install inotify-tools" exit 1 fi function usage { echo "Rerun a given command every time filesystem changes are detected." echo "" echo "Usage: $(basename $0) [OPTIONS] COMMAND" echo "" echo " -c, --clear Clear the screen before each execution of COMMAND." echo " -r, --recursive Watch directories for changes recursively." echo " -v, --verbose Print the name of the files that changed to cause" echo " each execution of COMMAND." echo " -h, --help Display this help and exit." echo "" echo "Run the given COMMAND, and then every time filesystem changes are" echo "detected in or below the current directory, run COMMAND again." echo "Changes within $cooldown_s seconds are grouped into one." echo "" echo "This is useful for running commands to regenerate visual output every" echo "time you hit [save] in your editor. For example, re-run tests, or" echo "refresh markdown or graphviz rendering." echo "" echo "COMMAND can only be a simple command, ie. \"executable arg arg...\"." echo "For compound commands, use:" echo "" echo " rerun bash -c \"ls -l | grep ^d\"" echo "" echo "Using this it's pretty easy to rig up ad-hoc GUI apps on the fly." echo "For example, every time you save a .dot file from the comfort of" echo "your favourite editor, rerun can execute GraphViz to render it to" echo "SVG, and refresh whatever GUI program you use to view that SVG." echo "" echo "COMMAND can't be a shell alias, and I don't understand why not." } while [ $# -gt 0 ]; do case "$1" in -c|--clear) clear='1';; -v|--verbose) verbose='1' ;; -r|--recursive) recursive='--recursive';; -h|--help) usage; exit;; *) break;; esac shift done function execute() { [ -n "$clear" ] && clear [ -n "$verbose" ] && echo "$@" "$@" } execute "$@" end_of_cooldown="$( date +%s%N )" cooldown_ns="$( LC_ALL=C printf '%0.9f' "$cooldown_s" | sed -r -e 's|\.||' -e 's|^0+||' )" inotifywait --quiet $recursive --monitor --format '%e %w%f' \ --event='modify,close_write,move,create,delete' \ --exclude='\.git|\..*\.swp|__pycache__|\.cache|\.pytest_cache' \ . | while read changed do now="$( date +%s%N )" if (( now > end_of_cooldown )); then end_of_cooldown="$(( now + cooldown_ns ))" ( sleep "$cooldown_s" && execute "$@" ) & fi if [ -n "$verbose" ]; then echo "$changed" fi done