2

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

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

#!/bin/sh

# colored red text (error)
function errorMsg () {
  echo '\033[0;31m'"$1"'\033[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 ${1} | refreshTimer

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

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

4

1 回答 1

2

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

#!/usr/bin/env bash

# colored red text (error)
function errorMsg () {
  printf '\033[0;31m%s\033[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 "${1}")

# Getting here means that read timed out.
echo "file write activity halted!" | mail -s "trouble!" "user@provider.ext"
  • fswatch 无限期地将行输出到标准输出,必须逐行读取才能对新输出及时采取措施。
  • fswatch -0以 NUL(零字节)终止行,read然后通过将行分隔符(分隔符)设置为空字符串 ( -d ''),读取为 1。
  • < <(fswatch -r -0 "${1}")通过 stdin<while循环提供输入,其中read一次使用一个以 NUL 结尾的行的 stdin 输入。
    • <(fswatch -r -0 "${1}")是一个进程替换fswatch -r -0 "${1}",它从由(监视文件夹${1}的子树 ( -r) 的文件更改,并报告每个以 NUL ( -0)终止)产生的输出形成一个“临时文件”(技术上,一个 FIFO 或命名文件描述符) .
    • 由于该fswatch命令无限期地运行,“临时文件”将继续提供输入,尽管通常只是间歇性的,具体取决于文件系统活动。
  • 每当read命令在超时期限 () 内收到新行时,它就会成功-t $TIMEOUT_TIME终止(退出代码 0),导致循环体被执行,然后再次被调用。 read
    • 因此,每当接收到一行时,超时周期就会有效地重置,因为新的read调用会以超时周期重新开始。
    • 相反,如果在接收到另一行之前超时期限到期,则read终止不成功- 带有指示失败的非零退出代码,这将导致循环终止。while
  • read因此,只有在命令超时时才会到达循环之后的代码。

至于您的原始代码

注意:所讨论的一些问题可以在shellecheck.net的帮助下检测到

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

    • >in[ ... ]和 bash 的[[ ... ]]条件是词法比较,变量名必须加$- 前缀;您必须在语法中使用算术条件:(( CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ))
    • 顺便说一句,最好不要在 shell 编程中使用全大写的变量名。
  • fswatch -r ${1} | refreshTimer

    • refreshTimer在管道填满之前不会被调用(您无法预测的时间),因为您没有尝试逐行读取。
    • 即使你解决了这个问题,里面的变量refreshTimer也不会被保留,因为每次都refreshTimer在一个新的子shell|中运行,因为使用了管道( )。在bash中,这个问题经常通过通过进程替换( <(...)) 提供输入来解决,您可以在我上面的代码中看到它的实际作用。
于 2015-05-21T14:01:37.860 回答