这是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
...因此表明 parent 和 child 在(击键)中断时结束。通过实时检查 ps -ef
并查看脚本进程在中断之前存在并在中断之后消失,证实了这一点。
如果父脚本像这样被 cron 调用......
* * * * * ~/parent.sh
...out.txt 的内容总是...
1
2
3
4
5
6
7
8
9
10
...因此表明至少 child 没有因(kill 命令)中断而结束。这通过实时检查 ps -ef
得到证实,并看到脚本进程在中断之前存在,只有父进程在中断之后消失,但子进程一直存在,直到它运行它的类(class)。
尝试解决...
- Shell 选项在这里只能是一个因素,因为父级运行
set -bm
的非交互式调用(这需要子级的 PGID 不同于父级的 PGID - 前面相关)。除此之外,两个脚本都只显示 选项 hB 已启用,无论是否以交互方式运行。 - 通过 man bash 寻找线索,但没有找到任何有用的信息。
- 尝试了一些网络搜索,其中包含许多来自
stackoverflow,但虽然有些类似于这个问题,但没有
我们是一样的。最接近的答案需要...
- 使用 wait 获取子进程 ID 并对其调用 kill - 导致“/parent.sh: line 30: kill: (17955) - Operation not permitted”
- 在进程组上调用 kill - 导致“~/parent.sh: line 31: kill: (-15227) - Operation not permitted”(使用子进程的 PGID 杀死,非交互时它与父进程不同,由于作业控制启用)
- 遍历当前作业并杀死每个作业
这些解决方案的问题是父级作为普通用户运行,而子级通过 sudo 作为 root 运行(它最终将是一个二进制文件,而不是 suid 脚本),所以父级无法杀死它?如果这就是“不允许操作”的意思,为什么在通过终端发送击键中断时可以终止 sudo 调用的进程?
自然的过程是避免额外的代码,除非必要——即因为脚本在交互运行时表现正确,如果可行的话,最好在非交互运行时/通过 cron 简单地应用相同的行为。
底线问题是,如何才能使非交互式运行时发出的中断(或术语)信号产生与交互式运行时发出的中断信号相同的行为?
谢谢。非常感谢任何帮助。
最佳答案
- 当您从交互式 shell(通常在 pty 上运行)手动运行脚本时,终端驱动程序会捕获
CTRL-C
并将其转换为SIGINT
并发送到前台进程组中的所有进程(脚本本身和sudo
命令)。 - 当您的脚本从 cron 运行时,您只需将
SIGINT
发送到 shell 脚本本身,sudo
命令将继续运行,并且 bash 在退出时不会杀死它的子级对于这种情况。
要向整个进程组显式发送信号,您可以使用负的进程组 ID。 对于您的情况,pgid 应该是 shell 脚本的 PID,所以请这样尝试:
trap 'kill -SIGINT -$$' SIGCHLD
更新:
事实证明我对 pgid 值的假设是错误的。刚刚用这个简单的 cron.sh
做了一个测试:
#!/bin/bash
set -m
sleep 888 &
sudo sleep 999
和 crontal -l
看起来像这样:
30 * * * * /root/tmp/cron.sh
当 cron 作业运行时,ps
输出如下:
PPID PID PGID SID COMMAND
15486 15487 15487 15487 /bin/sh -c /root/tmp/cron.sh
15487 15488 15487 15487 /bin/bash /root/tmp/cron.sh
15488 15489 15489 15487 sleep 888
15488 15490 15490 15487 sudo sleep 999
15490 15494 15490 15487 sleep 999
所以 sudo
(及其子项)在单独的 pgrp 中运行,pgid 不是 cron.sh
的 pid 所以我的解决方案( kill -INT -$$
) 将不起作用。
那么我认为我们可以这样解决问题:
#!/bin/bash
set -m
sudo sleep 999 & # run sudo in backgroup
pid=$! # save the pid which is also the pgid
sleep 5
sudo kill -INT -$pid # kill the pgrp.
# Use sudo since we're killing root's processes
关于linux - 当父脚本以交互方式/通过终端调用时,bash 子脚本与父脚本一起退出,但在非交互方式/通过 cron 调用时则不会,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41234093/