【问题标题】:bash child script exits along with parent script when parent invoked interactively / by terminal, but not when invoked non-interactively / by cron当父以交互方式/通过终端调用时,bash子脚本与父脚本一起退出,但在以非交互方式/通过cron调用时不退出
【发布时间】: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 并看到脚本进程在中断之前存在并且只有父进程在中断之后消失了,但子进程一直存在直到它运行它,这证实了这一点。

尝试解决...

  1. Shell 选项在这里只能是一个因素,因为父运行set -bm 的非交互式调用(这需要子的 PGID 与父的 PGID 不同 - 提前相关)。除此之外,两个脚本都只显示 已启用选项 hB,无论是否以交互方式运行。
  2. 通过 man bash 寻找线索,但没有发现任何帮助。
  3. 尝试了一些网络搜索,其中包括许多来自 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 - 谢谢你们的意见。现在按照下面的答案,寻找可能的干净解决方案。

标签: linux bash shell


【解决方案1】:
  1. 当您从 交互式 shell(通常在 pty 上运行)手动运行脚本时,终端驱动程序会捕获@987654321 @ 并将其转换为SIGINT 并发送到前台进程组中的所有进程(脚本本身和sudo 命令)。
  2. 当您的脚本从 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

【讨论】:

  • 试一试,因为它与过去“在进程组上调用终止”的尝试有些不同。它失败了,即使孩子没有被 sudo 调用(为了临时简化)。尝试了 SIGINT 和 SIGTERM,每个都使用 kill 内置函数和 kill 实用程序,但均无济于事。以防万一:在 cron 调用运行期间,当在 sleep 5 & 完成后检查 ps -ef 输出时,parent.sh 显示为“[parent.sh]
  • 好的。您对流程组的关注让一些事情变得明朗。当父进程由终端运行时,父进程、子进程和各自的睡眠进程都具有相同的 PGID。但是当通过cron时,父母有PGID“a”,它的睡眠有“b”,孩子和它的睡眠共享“c”。这似乎是由启用作业控制 (set -bm) 引起的,因为禁用它会导致所有这些进程的 PGID 相同。
  • 现在,手头的任务需要作业控制(或一些替代);不过,让我们暂时排除它。尝试kill -SIGINT -$pgid 实际上失败了,但kill -SIGTERM -$l_pgid 成功了。但是,当恢复 sudo 到子调用时 - sudo ~/child.sh - PGID 仍然相同,但子进程像以前一样不受影响。似乎权限不足应该不是问题,因为当终端调用父进程时,信号会终止子进程。感谢您的帮助。
  • 刚刚尝试在我的脚本中添加set -m,你是对的,现在我可以看到不同的 PGID。 sudo ./child.sh & 方式适合您吗?
  • 好的。对不起,上面的失败就像sudo ~/child.sh &。这是你提问的意图吗? - 后台调用子调用?
猜你喜欢
  • 1970-01-01
  • 2012-07-07
  • 2019-06-29
  • 2013-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多