我正在尝试设置一个 shell 脚本,以便它运行后台进程,当我使用Ctrlcshell 脚本时,它会杀死子进程,然后退出。
我能想到的最好的就是这个。看起来,它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
命令是倒退的。
与许多 UNIX 命令一样,以减号开头的选项必须先出现在其他参数之前。
如果你写
kill -INT 0
它将 视为-INT
一个选项,并发送到SIGINT
(0
是0
一个特殊数字,表示当前进程组中的所有进程)。
但如果你写
kill 0 -INT
它看到0
, 决定没有更多选项,因此SIGTERM
默认使用。并发送那到当前进程组,就像您所做的一样
kill -TERM 0 -INT
(它也会尝试发送SIGTERM
到-INT
,这会导致语法错误,但它首先发送SIGTERM
到0
,并且永远不会到达那么远。)
所以你的主脚本SIGTERM
在运行wait
and之前会得到 a echo DONE
。
添加
trap 'echo got SIGTERM' TERM
在顶部,就在之后
trap 'killall' INT
并再次运行来证明这一点。
正如 Stephane Chazelas 指出的那样,你有背景的孩子(process1
等)将SIGINT
默认忽略。
无论如何,我认为发送SIGTERM
更有意义。
最后,我不确定是否kill -process group
能保证先给孩子们。关闭时忽略信号可能是个好主意。
所以试试这个:
#!/bin/bash
trap 'killall' INT
killall() {
trap '' INT TERM # ignore INT and TERM while shutting down
echo "**** Shutting down... ****" # added double quotes
kill -TERM 0 # fixed order, send TERM not INT
wait
echo DONE
}
./process1 &
./process2 &
./process3 &
cat # wait forever
答案2
不幸的是,在后台启动的命令被 shell 设置为忽略 SIGINT,更糟糕的是,它们无法使用trap
.否则,你所要做的就是
(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait
因为当您按 Ctrl-C 时,process1 和 process2 会收到 SIGINT,因为它们属于同一进程组(即终端的前台进程组)。
上面的代码将与 pdksh 和 zsh 一起使用,在这方面它们不符合 POSIX 标准。
对于其他 shell,您必须使用其他方法来恢复 SIGINT 的默认处理程序,例如:
perl -e '$SIG{INT}=DEFAULT; exec "process1"' &
或者使用不同的信号,例如 SIGTERM。
答案3
如果您想要管理一些后台进程,为什么不使用 bash 作业控制功能呢?
$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]- Running gedit &
[2]+ Running emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]- Interrupt gedit
[2]+ Done emacs
答案4
所以我也对此进行了修改。在 Bash 中,您可以定义函数并用它们做一些有趣的事情。我和我的同事使用terminator
,因此对于批处理执行,如果我们想查看输出,我们通常会生成一大堆终结器窗口(不如tmux
选项卡优雅,但您可以使用更像 GUI 的界面哈哈)。
这是我提出的设置,以及您可以运行的示例:
#!/bin/bash
custom()
{
terun 'something cool' 'yes'
run 'xclock'
terun 'echoes' 'echo Hello; echo Goodbye; read'
func()
{
local i=0
while :
do
echo "Iter $i"
let i+=1
sleep 0.25
done
}
export -f func
terun 'custom func' 'func'
}
# [ Setup ]
run()
{
"$@" &
# Give process some time to start up so windows are sequential
sleep 0.05
}
terun()
{
run terminator -T "$1" -e "$2"
}
finish()
{
procs="$(jobs -p)"
echo "Kill: $procs"
# Ignore process that are already dead
kill $procs 2> /dev/null
}
trap 'finish' 2
custom
echo 'Press <Ctrl+C> to kill...'
wait
运行它
$ ./program_spawner.sh
Press <Ctrl+C> to kill...
^CKill: 9835
9840
9844
9850
$
编辑 @Maxim,刚刚看到你的建议,这使它变得简单多了!谢谢!