如果我有一个类似这样的 Bash 脚本:
function repeat {
while :; do
echo repeating; sleep 1
done
}
repeat &
echo running once
running once
被打印一次,但repeat
叉子永远存在,无限打印。
我应该如何防止repeat
创建它的脚本退出后继续运行?
我想也许显式实例化一个新的bash -c
解释器会迫使它退出,因为它的父进程已经消失了,但我猜孤儿进程被init
PID 1 所采用。
使用另一个文件对此进行测试:
# repeat.bash
while :; do echo repeating; sleep 1; done
# fork.bash
bash -c "./repeat.bash & echo an exiting command"
运行./fork.bash
仍然会导致repeat.bash
永远继续在后台运行。
简单而懒惰的解决方案是将行添加到fork.bash
:
pkill repeat.bash
但是你最好不要再有另一个重要的进程使用这个名字,否则它也会被抹掉。
我想知道,是否有更好或可接受的方法来处理分叉 shell 中的后台作业,当创建它们的脚本(或进程)退出时,这些作业应该退出?
如果没有比盲目地使用
pkilling
相同名称的所有进程更好的方法,那么应该如何处理与网络服务器之类的东西一起运行的重复作业以退出?我想避免cron
工作,因为脚本位于git
存储库中,并且代码应该是独立的,无需更改/etc/
.
答案1
这会在脚本退出之前终止后台进程:
trap '[ "$pid" ] && kill "$pid"' EXIT
function repeat {
while :; do
echo repeating; sleep 1
done
}
repeat &
pid=$!
echo running once
怎么运行的
trap '[ "$pid" ] && kill "$pid"' EXIT
这创建了一个陷阱。每当脚本即将退出时,就会运行单引号中的命令。该命令检查 shell 变量是否
pid
已分配非空值。如果有,则与之关联的进程将pid
被终止。pid=$!
这会将前面的后台命令 () 的进程 ID 保存
repeat &
在 shell 变量中pid
。
改进
作为帕特里克在评论中指出,该脚本有可能被杀死后后台进程启动但是前变量pid
已设置。我们可以用下面的代码来处理这种情况:
my_exit() {
[ "$racing" ] && pid=$!
[ "$pid" ] && kill "$pid"
}
trap my_exit EXIT
function repeat {
while :; do
echo repeating; sleep 1
done
}
racing=Y
repeat &
pid=$!
racing=
echo running once
答案2
shelltcsh
有一个hup
命令告诉 shell 在退出时终止该命令:
hup mycommand...
这对运行函数没有帮助,因为tcsh
它不支持函数。
在 中zsh
,当启用作业控制时,默认情况下会对所有后台作业执行此操作。
$ zsh -c 'set -m; sleep 2 &'; ps
[1] 23672
zsh:1: warning: 1 jobs SIGHUPed
不过,在脚本中启用作业控制可能会产生副作用。
bash
也可以做到这一点,但只有在交互时(尽管在脚本上使用 -i 调用它也可以做到这一点)并且只有在登录 shell 时(!?)。并且您需要启用该huponexit
选项。所以:
bash -O huponexit -lic 'sleep 2 &'
但这有很多副作用,所以可能不是一个好主意。
另一种选择zsh
是在退出时杀死所有已知的正在运行的子进程(在任何情况下,我们都无法轻易了解孙子进程(我们也不总是想杀死它们))。zsh
使其在$jobstates
关联数组中可用:
TRAPEXIT() kill -s HUP ${${jobstates[(R)running:*]/#*:/}/%=*/}
trap exit INT HUP TERM
其他 shell 不会公开其子进程列表,但您可以终止 ppid 为 的进程$$
,例如:
trap 'pkill -HUP -P "$$"' EXIT INT HUP TERM