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

Mid*_*lai 17 process shell-script

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

while [ true ]
do
  sleep 5m
  # PART X: code that executes every 5 mins
done
Run Code Online (Sandbox Code Playgroud)

在运行时 (1) 如何从外部拦截程序和 (2) 停止睡眠过程并立即执行第 X 部分?我可以为此目的使用 SIGNALS 吗?或者,还有更好的方法?

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

Sar*_*a J 21

处理时间是脚本的责任。即使这意味着/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
Run Code Online (Sandbox Code Playgroud)

然后,你可能会导致部分X运行到运行kill -USR1 "$pid_of_the_script"或pkill -USR1 -f my_fancy_script

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

  • 该脚本通过设置/取消设置/测试`$sleep_pid`来处理*大多数*,但不是*所有*竞争条件。在 shell 刚刚启动 `sleep 5m` 时发送 USR1,或者在 `wait` 处于活动状态时快速连续发送两次,可能会破坏某些逻辑。这就是为什么它不是*完美*的原因,但是仅在 shell 中很难达到这种完美级别,而在 C 中则更容易一些。 (8认同)

pLu*_*umo 13

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

pkill -f "sleep 5m"
Run Code Online (Sandbox Code Playgroud)

循环将继续。


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

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

然后运行kill PID以终止该进程(此处:)kill 14628。


这是手动杀你的好sleep,但它可能不会在脚本用于一般目的的很好的解决方案,因为它会杀死所有过程与sleep 5m在命令的任何地方。


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

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

pkill -f "sleep 5m"
Run Code Online (Sandbox Code Playgroud)

然后你可以运行:

kill $(< ~/.cache/myscript/sleep.PID)
Run Code Online (Sandbox Code Playgroud)

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

  • 评论它不安全是好的,但是请至少写下原因(我可能会改进问题),否则您的评论对我来说相当粗鲁,对任何人都没有帮助。 (2认同)
  • 谢谢指出。但我仍然没有看到真正的问题。当我创建`/tmp/sleep.PID`时,它归我所有并且有644,所以没有人可以覆盖它。为什么有人要为 `/tmp` 写一些珍贵的东西。如果你愿意,你可以把它放在 `~/.cache/myscript/sleep.PID` 中。 (2认同)
  • @user414777 指出人们代码中的错误是可以的。说“人们认为这很好很可悲”而不实际说明为什么不是这样是不行的。 (2认同)

Dig*_*uma 8

我喜欢@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
Run Code Online (Sandbox Code Playgroud)

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

echo > /tmp/myfifo
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

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