我对 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
一些注意事项:
- 使用非阻塞重定向
<>
而不是常规<
. 常规重定向会在 Bash 中阻塞(直到 FIFO 被写入),在 Bash 启动之前read
,因此如果 FIFO 从未被写入,则永远不会超时。 while [ true ]
表示测试字符串“true”是否非空,如果非空则继续 while 循环。但是while true
,意味着运行true
命令(正如您可能猜到的那样)始终返回 true,如果为 true,则继续 while 循环。在这种情况下,结果是完全相同的。不过我认为没有测试结构会更清楚[ ]
。考虑while [ false ]
与while false
...$((5*60))
是算术展开式。300
可以很容易地使用,但这有助于演示如何在命令行中轻松地执行算术。
答案4
我认为最简单的方法是让脚本检查某个文件是否存在。然后,在 while 语句中,以较短的时间间隔睡眠多次。如果使用秒作为短间隔,则将使用运行 300 次(= 5 分钟)的 for 循环。伪代码: for x=1 to 300 sleep 1 Second check_if_certain_file_exists did 从外部,您可以随时创建文件,脚本会在一秒的时间间隔内注意到它。您还可以创建具有不同内容的文件,并让您的脚本根据这些内容执行不同的操作。