【发布时间】:2017-05-05 04:24:42
【问题描述】:
这是 parent.sh:
#!/bin/bash
trap 'exit' SIGHUP SIGINT SIGQUIT SIGTERM
if ! [ -t 0 ]; then # if running non-interactively
sleep 5 & # allow a little time for child to generate some output
set -bm # to be able to trap SIGCHLD
trap 'kill -SIGINT $$' SIGCHLD # when sleep is done, interrupt self automatically - cannot issue interrupt by keystroke since running non-interactively
fi
sudo ~/child.sh
这是 child.sh:
#!/bin/bash
test -f out.txt && rm out.txt
for second in {1..10}; do
echo "$second" >> out.txt
sleep 1
done
如果像这样在终端中运行父脚本...
~/parent.sh
...大约 3 秒后,通过击键发出中断。几秒钟后检查 out.txt 时,它看起来像......
1
2
3
...因此表明父母和孩子在(击键)中断时结束。通过实时检查ps -ef 并查看脚本进程在中断之前存在并在中断之后消失,可以证实这一点。
如果父脚本像这样被 cron 调用...
* * * * * ~/parent.sh
...out.txt 的内容总是...
1
2
3
4
5
6
7
8
9
10
...因此表明至少孩子没有在(杀死命令)中断时结束。通过实时检查ps -ef 并看到脚本进程在中断之前存在并且只有父进程在中断之后消失了,但子进程一直存在直到它运行它,这证实了这一点。
尝试解决...
- Shell 选项在这里只能是一个因素,因为父运行
set -bm的非交互式调用(这需要子的 PGID 与父的 PGID 不同 - 提前相关)。除此之外,两个脚本都只显示 已启用选项 hB,无论是否以交互方式运行。 - 通过 man bash 寻找线索,但没有发现任何帮助。
- 尝试了一些网络搜索,其中包括许多来自
stackoverflow,但虽然有些类似于这个问题,但没有
我们是一样的。最接近的答案需要...
- 使用 wait 获取子进程 ID 并对其调用 kill - 导致“/parent.sh: line 30: kill: (17955) - Operation not allowed”
- 在进程组上调用 kill - 导致“~/parent.sh: line 31: kill: (-15227) - Operation not allowed”(使用子进程的 PGID 杀进程,在非交互式时与父进程不同,由于启用了作业控制)
- 循环遍历当前作业并杀死每个作业
这些解决方案的问题是父级以普通用户身份运行,而子级通过 sudo 以 root 身份运行(最终将是二进制文件,而不是 suid 脚本),因此父级无法杀死它?如果这就是“不允许操作”的意思,为什么在通过终端发送击键中断时,sudo 调用的进程会被杀死?
自然的做法是避免额外的代码,除非必要 - 即由于脚本在交互运行时表现正确,如果可行,最好在非交互运行/通过 cron 时简单地应用相同的行为。
最重要的问题是,如何使非交互式运行时发出的中断(或期限)信号产生与交互式运行时发出的中断信号相同的行为?
谢谢。非常感谢任何帮助。
【问题讨论】:
-
这是一个很好的问题,询问过程控制的一个毛茸茸的边缘案例。尽管我在 bash 中没有专门的直接解决方案,但我建议您让子进程成为父进程的管道后代,这应该允许更细粒度的控制,原因有两个:您的脚本不处理 SIGPIPE,并且“下游” sudo 进程可以将 SIGPIPE 或 EOF 视为终止自身的充分条件。您正沿着过程控制的崎岖边缘运行,“在非 shell 中实现它”可能是最好的答案。如果做不到这一点,管道是一种可靠的选择。
-
这是一个可能的解决方案;使用
screen -dmS child sudo ~/child.sh启动子进程,然后screen -S child -X quit杀死它。第一个命令启动一个名为“child”的屏幕,然后在里面运行“sudo ~/child.sh”。第二个杀死屏幕,它也应该带着脚本。它并不优雅,但它应该可以完成工作。 -
@msw - 谢谢你们的意见。现在按照下面的答案,寻找可能的干净解决方案。