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

标签 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

这是 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)。

尝试解决...

  1. Shell 选项在这里只能是一个因素,因为父级运行 set -bm 的非交互式调用(这需要子级的 PGID 不同于父级的 PGID - 前面相关)。除此之外,两个脚本都只显示 选项 hB 已启用,无论是否以交互方式运行。
  2. 通过 man bash 寻找线索,但没有找到任何有用的信息。
  3. 尝试了一些网络搜索,其中包含许多来自 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 简单地应用相同的行为。

底线问题是,如何才能使非交互式运行时发出的中断(或术语)信号产生与交互式运行时发出的中断信号相同的行为?

谢谢。非常感谢任何帮助。

最佳答案

  1. 当您从交互式 shell(通常在 pty 上运行)手动运行脚本时,终端驱动程序会捕获 CTRL-C 并将其转换为 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

关于linux - 当父脚本以交互方式/通过终端调用时,bash 子脚本与父脚本一起退出,但在非交互方式/通过 cron 调用时则不会,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41234093/

相关文章:

bash - 如何在shell编程中从键盘输入数据

python - 编辑后如何在 Python shell 中更新已执行的脚本导入?

c++ - __cxa_finalize 和 __attribute__

由于执行新的应用程序,Python 操作系统命令无法正常工作

linux - 在 Ubuntu 12.04 上编译 2.6 内核

shell - npx:shell-auto-fallback 参数已被删除

linux - 从 shell 脚本打开 gnome-terminal 并保持窗口打开

linux - 如何在 linux 中使用 xmlstarlet 从 xml 中获取值

python - 从多个文件中搜索和排序数据

linux - PuTTY:Linux 窗口 - 如何在函数执行期间更新窗口标题?