当父以交互方式/通过终端调用父进程时,bash子脚本与父脚本一起退出,但在非交互式/通过cron调用时则不会

S K*_*Kos 8 linux bash shell

这是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
Run Code Online (Sandbox Code Playgroud)

这是child.sh:

#!/bin/bash

test -f out.txt && rm out.txt

for second in {1..10}; do
    echo "$second" >> out.txt
    sleep 1
done
Run Code Online (Sandbox Code Playgroud)

如果在这样的终端中运行父脚本...

~/parent.sh
Run Code Online (Sandbox Code Playgroud)

......大约3秒钟后,按击键发出中断.几秒钟后检查out.txt时,它看起来像......

1  
2  
3  
Run Code Online (Sandbox Code Playgroud)

...因此表明父母和孩子结束(击键)中断.通过ps -ef实时检查并查看脚本进程在中断之前存在并在中断之后消失来证实这一点.

如果cron调用父脚本,那么......

* * * * * ~/parent.sh  
Run Code Online (Sandbox Code Playgroud)

... out.txt的内容总是......

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
Run Code Online (Sandbox Code Playgroud)

...因此表明至少孩子没有结束(杀死命令)中断.这通过ps -ef实时检查并在中断之前看到脚本进程存在并且在中断之后仅父进程消失来证实,但是子进程持续直到它运行它的过程.

试图解决......

  1. Shell选项只能是一个因素,因为父运行的非交互式调用set -bm(这需要孩子的PGID与父级的PGID不同 - 相关的提前).除此之外,两个脚本仅显示启用的选项hB,无论是否以交互方式运行.
  2. 通过男人的bash寻找线索,但没有发现任何帮助.
  3. 尝试了一些网络搜索,其中包括来自stackoverflow的许多结果,但有些类似于这个问题,没有一个是相同的.最接近的答案需要......
    • 使用wait来获取子进程id并对其调用kill - 导致"/parent.sh:line 30:kill:(17955) - 不允许操作"
    • 在进程组上调用kill - 导致"〜/ parent.sh:第31行:kill:( - 15227) - 不允许操作"(使用子进程的PGID进行终止,这与非进行时的父进程不同,由于工作控制启用)
    • 通过当前的工作循环并杀死每个工作

这些解决方案的问题是父运行作为普通用户运行,而子运行通过sudo以root身份运行(它最终将是二进制文件,而不是suid脚本),所以父进程无法杀死它?如果这是"不允许操作"的意思,为什么在通过终端发送击键中断时sudo调用进程可以运行?

自然过程是为了避免额外的代码,除非必要 - 即由于脚本在交互式运行时表现正确,如果可行的话,在非交互式/通过cron运行时简单地应用相同的行为更为可取.

最重要的问题是,在非交互式运行时发出中断(或术语)信号可以做什么,产生与交互式运行时发出的中断信号相同的行为?

谢谢.任何帮助是极大的赞赏.

pyn*_*exj 2

  1. 当您从交互式 shell (通常在pty上运行)手动运行脚本时,终端驱动程序会捕获CTRL-C它并将其转换为SIGINT并发送到前台进程组中的所有进程(脚本本身和sudo命令)。
  2. 当你的脚本从 cron 运行时,你只发送SIGINT到 shell 脚本本身,sudo命令将继续运行,并且 bash 在这种情况下退出时不会杀死它的子脚本。

要显式向整个进程组发送信号,您可以使用负进程组 ID对于您的情况,pgid 应该是 shell 脚本的 PID,因此请尝试如下:

trap 'kill -SIGINT -$$' SIGCHLD
Run Code Online (Sandbox Code Playgroud)

更新:

事实证明我对 pgid 值的假设是错误的。刚刚做了一个简单的测试cron.sh

#!/bin/bash
set -m
sleep 888 &
sudo sleep 999
Run Code Online (Sandbox Code Playgroud)

看起来crontal -l像这样:

30 * * * * /root/tmp/cron.sh
Run Code Online (Sandbox Code Playgroud)

当 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
Run Code Online (Sandbox Code Playgroud)

因此sudo(及其子项)在单独的 pgrp 中运行,并且 pgid 不是 pid,cron.sh因此我的解决方案 ( 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
Run Code Online (Sandbox Code Playgroud)