Frage

I am using inotifywait to run a command when a filesystem event happens. I would like this to wait for 5 seconds to see if another filesystem event happens and if another one does, I would like to reset the timer back to five seconds and wait some more. Make sense?

My problem is I'm attacking this in Bash and I don't know how I would do this. In JavaScript, I'd use setTimeout with some code like this:

function doSomething() { ... }

var timer;
function setTimer() {
    window.clearTimeout(timer)
    timer = window.setTimeout(doSomething, 5000);
}

// and then I'd just plug setTimer into the inotifywait loop.

But are there addressable, clearable background timers in Bash?

War es hilfreich?

Lösung

One idea I've had rattling around is forking out a subshell that sleeps and then runs my desired end command, and then stuffing that in the background. If it's run again, it'll pick up the previous PID and try to nuke it.

As a safety feature, after the sleep has finished, the subshell clears $PID to avoid the command being killed mid-execution

PID=0
while inotifywait -r test/; do
    [[ $PID -gt 0 ]] && kill -9 $PID
    { sleep 5; PID=0; command; } & PID=$!
done

It's a bit messy but I've tested it and it works. If I create new files in ./test/ it sees that and if $PID isn't zero, it'll kill the previous sleeping command and reset the timer.

Andere Tipps

I provide this answer to illustrate a similar but more complex use case. Note that the code provided by @Oli is included in my answer.

I want to post process a file when it has changed. Specifically I want to invoke dart-sass on a scss file to produce a css file and its map file. Then the css file is compressed.

My problem is that editing/saving the scss source file could be done directly through vim (which uses a backup copy when writing the file) or through SFTP (specifically using macOS Transmit). That means the change could be seen with inotifywait as a pair CREATE followed by CLOSE_WRITE,CLOSE or as a single CREATE (due to the RENAME cmd through SFTP I think). So I have to launch the processing if I see a CLOSE_WRITE,CLOSE or a CREATE which is not followed by something.

Remarks:

  • It has to handle multiple concurrent edit/save.
  • The temporary files used by Transmit of the form <filename>_safe_save_<digits>.scss must not be taken into account.
  • The version of inotify-tools is 3.20.2.2 and has been compiled from the source (no package manager) to get a recent version with the include option.
#!/usr/bin/bash

declare -A pids

# $1: full path to source file (src_file_full)
# $2: full path to target file (dst_file_full)
function launch_dart() {
  echo "dart"
  /opt/dart-sass/sass "$1" "$2" && /usr/bin/gzip -9 -f -k "$2"
}

inotifywait -e close_write,create --include "\.scss$" -mr assets/css |
grep -v -P '(?:\w+)_safe_save_(?:\d+)\.scss$' --line-buffered |
  while read dir action file; do
    src_file_full="$dir$file"
    dst_dir="${dir%assets/css/}"
    dst_file="${file%.scss}.css"
    dst_file_full="priv/static/css/${dst_dir%/}${dst_file}"

    echo "'$action' on file '$file' in directory '$dir' ('$src_file_full')"
    echo "dst_dir='$dst_dir', dst_file='$dst_file', dst_file_full='$dst_file_full'"

    # if [ "$action" == "DELETE" ]; then
    #   rm -f "$dst_file_full" "${dst_file_full}.gz" "${dst_file_full}.map"
    if [ "$action" == "CREATE" ]; then
      echo "create. file size: " $(stat -c%s "$src_file_full")
      { sleep 1; pids[$src_file_full]=0; launch_dart "$src_file_full" "$dst_file_full"; } & pids[$src_file_full]=$!
    elif [ "$action" == "CLOSE_WRITE,CLOSE" ]; then
      [[ ${pids[$src_file_full]} -gt 0 ]] && kill -9 ${pids[$src_file_full]}
      launch_dart "$src_file_full" "$dst_file_full"
    fi
  done
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top