使用 inotifywait 监视突发事件

使用 inotifywait 监视突发事件

我有一项服务,它通过rsync.当发生这种情况时,我想触发服务器端程序的执行。

由于该inotifywait命令,监视文件或目录的更改相当容易。但我希望得到通知每次修改突发仅一次,因为上传后过程很繁重,并且不想为每个修改的文件执行它。

根据事件时间戳提出一些破解应该不是一个巨大的努力……但我相信这是一个相当常见的问题。但我没能找到任何有用的东西。

有什么巧妙的命令可以解决突发问题吗?我正在考虑可以这样使用的东西:

inotifywait -m "$dir" $opts | detect_burst --execute "$post_upload"

答案1

根据您自己的答案,如果您想使用 shell,read您可以利用-t超时选项,如果超时,它将返回代码设置为 >128。例如,您的burst脚本可以松散地变为:

interval=$1; shift
while :
do  if read -t $interval
    then    echo "$REPLY"            # not timeout
    else    [ $? -lt 128 ] && exit   # eof
            "$@"
            read || exit    # blocking read infinite timeout
            echo "$REPLY"
    fi
done

您可能希望从初始阻塞读取开始,以避免在开始时检测到突发结束。

答案2

欧普在这里。

我提出的解决方案:它是一个名为burst

inotifywait -e moved_to "$monitored_dir" -m \
    | burst 2 'echo run post-upload'

剧本burst

#!/bin/bash

help() {
    >&2 echo "Usage: $0 <interval> <command>"
}

set -e
trap help EXIT
interval=${1:?missing interval}; shift
: ${1:?missing command}

trap - EXIT
set +e
exec 3> >(sed "s/^/burst: /" >&2)
while read line; do
    echo "$line" >&3
    test -n "$!" && kill -term $! 2>/dev/null
    (sleep $interval && $SHELL -c "$*") &
done

基本上,这是创建一个子 shell,它在启动实际命令( )之前等待定义的时间(sleep $interval && $SHELL -c "$*")。读取的任何新行inotifywait都会简单地杀死此类 shell(如果存在)并再次创建它。

一旦连串的行结束,shell 将能够结束sleep,并且命令将被执行。

它有一些缺点:

  1. 它会为 stdin 中的每一行杀死并生成一个进程。这意味着您希望首先减少行数(因此-e moved_to需要过滤器 inotifywait)。如果上传的文件数量足够大,它无法适应 wrt 性能!

  2. 如果在几秒钟后出现第二次突发$interval(或者文件传输需要这段时间),它很可能会终止正在运行的更新后进程。只要这样的过程是幂等的或事务性的,那就没问题……所以请注意。

答案3

楼主。一个可能的解决方案可能类似于

inotifywait -m "$dir" -e moved_to --timefmt='%s' --format '%T' | stdbuf -oL uniq | ...

编辑: 请参阅我对此问题的其他答案,因为恕我直言,第二个答案更好

它的工作原理是在每次执行时打印纪元以来的时间move_to,这是单文件传输的最后一步。

即使一次上传持续超过一秒,它可能会多次触发后上传过程,但这种方法工作得很好。

--timefmt通过将标志更改为某个不同的值可以获得不同的粒度。

将其发布在这里作为半不错的想法,尽管我不太喜欢它......但我仍然想分享它。

答案4

您可以创建一个循环来检查当前时间并跳过与先前事件相同(或更一般地说,以您可以定义的方式类似)的事件。在 bash、ksh 或 zsh 中,以下简单循环会跳过 10 秒周期内的重复事件:

inotifywait -m … | {
  previous_SECONDS=0
  previous_event=
  while IFS= read -r event; do
    if [[ "$event" = "$previous_event" && $SECONDS <= $previous_SECONDS + 10 ]]; then
      continue
    fi
    previous_SECONDS=$SECONDS previous_event=$event
    "$post_upload" "$event"
  done
}

Shell 不擅长高性能文本处理,因此最好使用不同的工具。您可以使用 awk,由于历史原因,它有一种非常奇怪的方式来公开时间(自纪元以来的秒数):调用srand()返回上次调用srand().

inotifywait -m … | awk '
    {srand(); current_time = srand()}
    $0 == previous_event && current_time <= previous_time + 10 {
        system(ENVIRON["post_upload"])
    }
    {previous_event = $0; previous_time = current_time}
'

除非您确实需要可移植性,否则我会放弃它并使用更高级的脚本语言,例如 Perl、Python 或 Ruby。这些语言都有一个 inotify 接口,因此不需要调用inotifywait并进行任何文本解析。

相关内容