![当我按 Ctrl+C 键时,如何终止 shell 脚本并等待后台进程完成?](https://linux22.com/image/600486/%E5%BD%93%E6%88%91%E6%8C%89%20Ctrl%2BC%20%E9%94%AE%E6%97%B6%EF%BC%8C%E5%A6%82%E4%BD%95%E7%BB%88%E6%AD%A2%20shell%20%E8%84%9A%E6%9C%AC%E5%B9%B6%E7%AD%89%E5%BE%85%E5%90%8E%E5%8F%B0%E8%BF%9B%E7%A8%8B%E5%AE%8C%E6%88%90%EF%BC%9F.png)
我正在尝试设置一个 shell 脚本,以便它运行后台进程,当我按 ctrl+C 键运行 shell 脚本时,它会终止子进程,然后退出。
我所能想到的最好的办法就是这个。看来kill 0 -INT
在等待发生之前也会终止脚本,因此 shell 脚本会在子脚本完成之前终止。
关于如何让这个 shell 脚本在发送 INT 后等待子进程死亡,有什么想法吗?
#!/bin/bash
trap 'killall' INT
killall() {
echo **** Shutting down... ****
kill 0 -INT
wait # Why doesn't this wait??
echo DONE
}
process1 &
process2 &
process3 &
cat # wait forever
答案1
首先,你应该知道kill 0
向当前进程组中的所有进程发送信号。(参见kill(1) - Linux 手册页) 我猜是它终止的进程比它应该终止的进程多。这就是为什么它wait
不是在等待,而是出于某种原因被终止。解决这个问题的最佳方法是逐个终止正在运行的作业jobs -pr
:这样我就可以确保信号只发送给当时的子进程,而不会发送给其他进程。
为了实现这个功能,我做了几个测试,但一直无法向子进程发送 SIGINT。它们根本无法对此做出反应!在网上搜索后,我在 Stack Overflow 上找到了这个答案:
https://stackoverflow.com/questions/2524937/how-to-send-a-signal-sigint-from-script-to-script-bash
所以真正的问题是无法将 SIGINT 从一个脚本发送到另一个脚本,因为在非交互式 shell 中信号会被忽略。我无法解决这个问题(使用bash -i
调用子脚本不起作用)。
我知道您可能想要发送 SIGINT 并等待子进程正常关闭,但如果您不介意使用 SIGTERM(或除 SIGINT 之外的任何其他信号),这是我编写的最佳脚本:
#!/bin/bash
trap 'killall' INT
killall() {
echo '**** Shutting down... ****'
jobs -pr
for i in $(jobs -pr); do
kill -TERM $i
done
wait
echo DONE
}
./long.sh &
./long.sh &
./long.sh &
cat # wait forever
为了测试,我创建了这个long.sh
脚本:
#!/bin/bash
trap 'killall' TERM
echo "Started... $$" >> file.txt
killall() {
# shutting down gracefully
echo "Finished... $$" >> file.txt
exit # don't forget this!
}
# infinite loop
while true; do echo loop >/dev/null ; done
在最后一个脚本中,我没有使用sleep
函数,因为它会创建一个新进程,该进程即使在脚本完成后仍停留在内存中。在脚本中,您可以调用新进程,但应确保向所有进程广播 SIGTERM(或您使用过的任何其他信号)。
答案2
这似乎有效。请确保使用“/usr/bin/kill”,而不是 Bash 内置的“kill”。
[myles@marklar ~]$ /usr/bin/kill --version
kill from util-linux-2.13-pre7
请注意,非交互式 Bash shell(例如脚本)中未启用作业控制。
#!/bin/bash
trap kill_jobs 2
kill_jobs()
{
while /usr/bin/kill 0; do :; done
exit 0
}
foo &
foo &
foo &
sleep 777777