文件夹中没有写入 activity 时通知的任何脚本

Any script to notify when there is no write activity in folder

我正在尝试想出一种简单易用的方法来检测何时我想观看的文件夹中没有任何写入 activity。

基本上,我想要的是这样的:

#!/bin/sh

# colored red text (error)
function errorMsg () {
  echo '3[0;31m'""'3[0m'
}

# check for folder to monitor argument
if [ "$#" -ne 1 ]
then
  errorMsg "add experiment (folder) to monitor for activity!"
exit
fi

# time out time, 3 minutes
TIMEOUT_TIME=180
LAST_CHECKED=0

function refreshTimer () {
  # when called, check seconds since epoch
  CURRENT_TIME=date +%s

  if [ CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ]
  then
    echo "file write activity halted!" | mail -s "trouble!" "user@provider.ext"
  fi

  LAST_CHECKED=date +%s
}

# set last checked to now.
LAST_CHECKED=date +%s
# start monitoring for file changes, and update timer when new files are being written.
fswatch -r  | refreshTimer

但我认为需要各种 bash 魔法,因为 fswatch 是后台任务并且通过管道将其输出创建一个子 shell。 我还需要一些定时器逻辑...... 我在想像 setTimeout 这样的东西,当有 activity 时,时间参数会不断添加到其中,但我不知道如何在一个脚本中全部写出来。

Bash、Python、Ruby,任何可以在 OSX 上使用自制软件安装的东西都可以,但越简单越好(所以我明白发生了什么).

尝试以下操作 - 请注意它需要 bash:

#!/usr/bin/env bash

# colored red text (error)
function errorMsg () {
  printf '3[0;31m%s3[0m\n' "$*" >&2
}

# check for folder to monitor argument
if [[ $# -ne 1 ]]
then
  errorMsg "add experiment (folder) to monitor for activity!"
  exit 2
fi

# time-out: 3 minutes
TIMEOUT_TIME=180

# Read fswatch output as long as it is
# within the timeout value; every change reported
# resets the timer.
while IFS= read -t $TIMEOUT_TIME -d '' -r file; do 
  echo "changed: [$file]"
done < <(fswatch -r -0 "")

# Getting here means that read timed out.
echo "file write activity halted!" | mail -s "trouble!" "user@provider.ext"
  • fswatch 无限期 将行输出到 stdout,必须逐行读取 以获取 及时 对新输出采取行动。
  • fswatch -0 以 NUL(零字节)终止行,然后 read 通过将行定界符(分隔符)设置为空字符串 (-d '') 继续读取。
  • < <(fswatch -r -0 "") 通过标准输入 <while 循环提供输入,其中 read 一次消耗标准输入一个以 NUL 结尾的行。
    • <(fswatch -r -0 "") 是一个 process substitution,它根据 fswatch -r -0 ""(监视文件夹 </code> 的子树 (<code>-r) 用于文件更改,并报告每个以 NUL (-0)) 终止。
    • 由于 fswatch 命令 运行 无限期地执行,"ad-hoc file" 将继续提供输入,尽管通常只是间歇性地提供输入,具体取决于文件系统 activity.
  • 每当 read 命令在超时期限 (-t $TIMEOUT_TIME) 内接收到新行时,它会 成功终止 (退出代码 0),导致要执行的循环体,然后 read 被调用 再次
    • 因此,无论何时接收到一行,超时期限都会有效地重置,因为新的 read 调用会在超时期限内重新开始。
    • 相比之下,如果在接收到另一行之前超时期限到期,read 终止 不成功 - 非零退出代码指示 失败 ,这会导致 while 循环终止。
  • 因此,循环后的代码只有在read命令超时时才到达。

至于你的原码:

注意:所讨论的一些问题可以在 shellecheck.net

的帮助下检测到
  • echo '3[0;31m'""'3[0m'
    • printf 在解释转义序列时是更好的选择,因为 echo 的行为因 shell 和平台而异;例如,运行将您的脚本与 bash 结合将 不会 解释它们(除非您还添加了 -e 选项)。
  • function refreshTimer ()
    • function 语法是非标准的(不符合 POSIX),所以你不应该将它与 sh shebang 行一起使用(这就是 chepner 在他的评论中的意思) .在 OSX 上,您可以摆脱它,因为 bash 充当 sh 并且 most bashism 在 运行 为 sh,但它在其他系统上不起作用。如果你知道你无论如何都会 运行 bash,最好使用 bash shebang 线。
  • CURRENT_TIME=date +%s
    • 您不能通过简单地将命令按原样放在分配的 RHS 上来将命令的输出分配给变量;相反,你需要一个 command substitution;在手头的例子中:CURRENT_TIME=$(date +%s)(带反引号的旧语法 - CURRENT_TIME=`date +%s` - 也可以,但有缺点)。
  • [ CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ]

    • > in [ ... ] and bash's [[ ... ]] conditionals is lexical comparison and the variable names must be $-前缀;你必须使用 arithmetic 条件语法:(( CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ))
    • 顺便说一句,在shell编程中better not to use all-uppercase variable names更好。
  • fswatch -r | refreshTimer

    • refreshTimer 直到管道填满(您无法预测的时间)才会被调用,因为您没有尝试逐行阅读 .
    • 即使你解决了这个问题,refreshTimer 中的变量也不会被保留,因为 refreshTimer 运行s 在 new subshell 每次,由于使用管道 (|)。在 bash 中,这个问题经常通过 process substitution (<(...)) 提供输入来解决,您可以在我上面的代码中看到它的实际效果。