如何中断或去抖动inotifywait循环?

15

我有一个小脚本,它使用 inotifywait 监控文件的变化。当有变化发生时,一批文件会通过一个需要约10秒运行时间的处理过程(编译、压缩、重新组织等)。

考虑以下示例:

touch oli-test
inotifywait -mq oli-test | while read EV; do sleep 5; echo "$EV"; done

如果你在另一个终端运行touch oli-test几次,你会发现每个循环都会在继续之前完成。这种情况对我来说非常真实。如果我在文件正在处理时忘记保存或者注意到一个错误,事件会堆积起来,我就需要等上数分钟。

在我的思考中,有两种技术可以让这个工作流程更加高效。我不确定哪一种是最简单或最好的,所以我把它们都列了出来:

  1. 打断之前的运行,并立即重新启动。目前脚本进程只是一组内联命令。我可以将它们拆分为Bash函数,但我不喜欢把它们再拆得更细。

  2. 去抖动等待处理的事项列表,这样如果同时发生五个事件(或者在它正在处理时),它只会再执行一次。

(或者两者都使用......因为我确信有些情况下两者都很有用)

我也可以接受与inotifywait不同的方法,但它们必须给我相同的结果,并且在Ubuntu上工作。

4个回答

8

以下是简洁的解决方案:

inotifywait -q -m -e modify -e create -e close_write --format "%w%f" /etc/nginx/ |\
while read -r path; do
    echo $path changed
    echo "Skipping $(timeout 3 cat | wc -l) further changes"
    service nginx reload
done

第一个read等待一行数据,因此这不会占用您的CPU。 timeout 3 cat接下来读取在接下来的3秒钟内产生的更改通知。然后才重新加载nginx


1
哇,这真是一种非常优雅的解决方案!我从来没有想过那样做。谢谢你分享! - ndbroadbent

7

如果要打断,可以将处理程序移动到后台子shell中运行,并且每个新的inotifywait事件都会终止后台进程:

inotifywait -mq oli-test | while read EV; do
    jobs -p | xargs kill -9
    (
        # do expensive things here
        sleep 5  # a placeholder for compiling
        echo "$EV"
    ) &
done

这看起来是你想要做的事情的好解决方案。 - hek2mgl
有没有办法静音管道到 kill 的输出?我已经尝试将其重定向到 /dev/null,无论我尝试了什么类型的重定向,我仍然能看到输出。 - surgiie
@surgiie 未经测试,但 kill 命令可能会输出到 STDERR (fd/2)。当你无法重定向某些内容时,通常会出现这种情况。尝试将该行替换为 jobs -p | xargs kill -9 2>/dev/null - Oli
@Oli 我也尝试过这个方法,但仍然看到了“kill”的输出:./myscript: 第12行:542 已杀死 (mycommand) - surgiie
这会杀死其他后台作业吗? - Marcel
2
如果在此bash会话中存在其他后台作业,那么是的,它们都将被取消。但是试一下,在一个终端中输入sleep 2000 &,在另一个终端中输入jobs -p。它不会破坏其他会话的作业。 - Oli

1

使用bc是完全多余的,它是一个次优的实现。所有可以用整数完成。

如果我们真的需要毫秒级精度,可以使用纯bash版本(除了inotifywait之外没有外部命令)。 (我们甚至可以做到纳秒级精度,但会限制最大抖动期)

#!/bin/bash

getTime() {
   # EPOCHREALTIME has the EPOCH time in nanoseconds 
   # (i.e: 1653121901,339206 )
   # We extract the milliseconds and seconds up to 9 
   # digits precision. 
   # Using the value above, this is "121901339" 
   # (Those values fit neatly into a bash integer)

   [[ $EPOCHREALTIME =~ ([^,].....),(...).*$ ]] && \
       echo "${BASH_REMATCH[1]}${BASH_REMATCH[2]}"

   # (This way of pattern extraction avoids the use of sed on this cases.
   # Totally recommended)
}

lastRunTime=$(getTime)

# We avoid the pipe, so the main program always
# runs on the same process. 
while read path event file; do

    currentTime=$(getTime)
    delta=$(( $currentTime - $lastRunTime ))

    if [[ "${delta}" -gt 1000 ]] ; then
        echo "run"
        lastRunTime=$(getTime)
    fi
done < <(inotifywait -mr ./web -e create -e delete -e modify)
# This way the extra process is the generator command, which is generally what you
# want.

话虽如此,如果您对接收的事件感兴趣,则将所有事件视为相同事件进行处理。您不知道在去抖期间发生了哪些事件。

可以通过使用关联数组来存储去抖期间发生的所有不同事件来解决这个问题。

#!/bin/bash

getTime() {
   [[ $EPOCHREALTIME =~ ([^,].....),(...).*$ ]] && echo "${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
}

declare -A Changes
lastRunTime=0

while read path event file; do

    currentTime=$(getTime)
    delta=$(( $currentTime - $lastRunTime ))

    Changes["$event $file"]=1

    if [[ "${delta}" -gt 1000 ]] ; then
        for change in "${!Changes[@]}"; do 
           echo "run $change";
        done
        Changes=()
        lastRunTime=$currentTime
    fi
done < <(inotifywait -mr ./web/  -e create -e delete -e modify)

也许将正则表达式改为 ([^,].....)[^[:digit:]](...).*$ 可以适应不是逗号的小数分隔符。 - Robin A. Meade

0

我写了这个解决方案,只有在安装了bc(计算器)应用程序的情况下才能正常工作,因为bash无法处理浮点数。

lastRunTime=$(date +'%H%M%S.%N')

inotifywait -mr ./web -e create -e delete -e modify | while read file event tm; do

    currentTime=$(date +'%H%M%S.%N')
    delta=$(bc <<< "$lastRunTime - $currentTime")
    echo "$currentTime, $lastRunTime, $delta"

    if (( $(echo "$delta < -1.0" | bc -l) )); then
        echo "run"
        lastRunTime=$(date +'%H%M%S.%N')
    fi

done

解释:在这里,我们将上次运行的日期时间设置为NOW,并且只有在上次运行的日期时间小于delta(在我的情况下为-1.0)时才允许下一次运行。
示例:
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
120845.009293691, 120842.581019388, -2.428274303
run
120845.018643243, 120845.017585539, -.001057704
120845.026360234, 120845.017585539, -.008774695


好的,但为什么不直接避免小数? - ErichBSchulz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接