如何在 shell 脚本休眠时从外部控制它?

如何在 shell 脚本休眠时从外部控制它?

我对 shell 脚本相当陌生。我有一个类似于以下的脚本,

while [ true ]
do
  sleep 5m
  # PART X: code that executes every 5 mins
done

当它运行时,(1)如何从外部拦截该程序以及(2)停止睡眠进程并立即执行第 X 部分?我可以使用 SIGNALS 来实现此目的吗?或者,还有更好的方法?

您能否指出解决此类问题的总体方向?

答案1

处理时间是脚本的责任。即使这意味着/bin/sleep今天使用,将来也可能不会使用,因此实际上并不能保证长期有效。好吧,我想你做出这样的保证,但最好不这样做。我的观点是,您不应该直接从脚本外部终止睡眠,因为睡眠是一个实现细节。相反,让您的脚本捕获不同的信号,例如SIGUSR1并将其发送到您的脚本。

一个简单的例子可能看起来像

#!/usr/bin/env bash

kill_the_sleeper () {
    # This probably isn't really needed here
    # If we don't kill the sleep process, it'll just
    # hang around in the background until it finishes on its own
    # which isn't much of an issue for "sleep" in particular
    # But cleaning up after ourselves is good practice, so let's
    # Just in case we end up doing something more complicated in future
    if [ -v sleep_pid ]; then
        kill "$sleep_pid"
    fi
}

trap kill_the_sleeper USR1 EXIT

while true; do
    # Signals don't interrupt foreground jobs,
    # but they do interrupt "wait"
    # so we "sleep" as a background job
    sleep 5m &
    # We remember its PID so we can clean it up
    sleep_pid="$!"
    # Wait for the sleep to finish or someone to interrupt us
    wait
    # At this point, the sleep process is dead (either it finished, or we killed it)
    unset sleep_pid

    # PART X: code that executes every 5 mins
done

那么你可以引起第十部分通过跑步kill -USR1 "$pid_of_the_script"pkill -USR1 -f my_fancy_script

这个脚本不是完美的无论如何,但至少对于简单的情况来说应该是不错的。

答案2

在另一个终端中,
终止该sleep进程:

pkill -f "sleep 5m"

循环将会继续下去。


如果pkill在您的操作系统中不可用,您可以使用以下方法获取 PID:

$ ps aux | grep '[s]leep'
username 14628  0.0  0.0   8816   672 pts/19   S+   08:33   0:00 sleep 5m

然后运行kill PID杀死该进程(此处kill 14628:)。


这对于手动终止您的 很有用sleep,但对于脚本编写中的一般用途来说,它可能不是一个好的解决方案,因为它会终止sleep 5m命令中任何位置的所有进程。


如果您可以编辑脚本,则更安全的选择:

更改脚本以将 PID 写入文件:

TMP_DIR="$HOME/.cache/myscript/"
mkdir -p "$TMP_DIR"
while [ true ]
do
  sleep 5m &
  printf '%s' $! > "$TMP_DIR"/sleep.PID
  wait
  # PART X: code that executes every 5 mins
done

然后你可以运行:

kill $(< ~/.cache/myscript/sleep.PID)

请注意,您的 PID 文件可能会被覆盖,例如被同一脚本的另一个实例覆盖,并且可能无法足够可靠地满足您的要求。

答案3

我喜欢@sitaram 的使用方法ionotifywait。然而,可以使用更普遍的基础设施来完成类似的事情 - shellread命令(在 Bash/ksh/Zsh 中可以采用可选的超时)和 FIFO。

等待循环看起来像这样:

mkfifo /tmp/myfifo
while true; do
    read -t $((5*60)) <> /tmp/myfifo
    date
    echo "Executed every 5 minutes"
done

只需向 FIFO 写入换行符即可提前中断读取:

echo > /tmp/myfifo

一些注意事项:

  1. 使用非阻塞重定向<>而不是常规<. 常规重定向会在 Bash 中阻塞(直到 FIFO 被写入),在 Bash 启动之前read,因此如果 FIFO 从未被写入,则永远不会超时。
  2. while [ true ]表示测试字符串“true”是否非空,如果非空则继续 while 循环。但是while true,意味着运行true命令(正如您可能猜到的那样)始终返回 true,如果为 true,则继续 while 循环。在这种情况下,结果是完全相同的。不过我认为没有测试结构会更清楚[ ]。考虑while [ false ]while false...
  3. $((5*60))是算术展开式。 300可以很容易地使用,但这有助于演示如何在命令行中轻松地执行算术。

答案4

我认为最简单的方法是让脚本检查某个文件是否存在。然后,在 while 语句中,以较短的时间间隔睡眠多次。如果使用秒作为短间隔,则将使用运行 300 次(= 5 分钟)的 for 循环。伪代码: for x=1 to 300 sleep 1 Second check_if_certain_file_exists did 从外部,您可以随时创建文件,脚本会在一秒的时间间隔内注意到它。您还可以创建具有不同内容的文件,并让您的脚本根据这些内容执行不同的操作。

相关内容