将SIGTERM转发给Bash中的孩子


86

我有一个Bash脚本,看起来与此类似:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

现在,如果运行脚本的bash shell收到SIGTERM信号,它也应该将SIGTERM发送到正在运行的服务器(该服务器会阻塞,因此无法进行陷阱)。那可能吗?

Answers:


91

尝试:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

通常,bash在执行子进程时将忽略任何信号。通过启动服务器,&$!保持服务器的PID(与wait和一起使用kill),使其进入Shell的作业控制系统。wait然后,调用将等待具有指定PID的作业(服务器)完成,或等待任何信号被触发

当shell收到SIGTERM(或服务器独立退出)时,wait调用将返回(以服务器的退出代码退出,或者在收到信号的情况下以信号编号+ 128退出)。之后,如果外壳程序接收到SIGTERM,它将_term在退出之前调用指定为SIGTERM陷阱处理程序的函数(在该过程中,我们将进行任何清理,并使用手动将信号传播到服务器进程kill)。


看起来不错!我会尝试一下,并在测试时做出回应。
洛伦兹

7
但是exec用给定的程序替换了shell,我不清楚为什么随后wait需要调用吗?
iruvar

5
我认为1_CR的观点是正确的。要么简单地使用exec /bin/start/main/server --nodaemon(在这种情况下,将shell进程替换为服务器进程,并且不需要传播任何信号),要么使用/bin/start/main/server --nodaemon &,但exec实际上并没有意义。
Andreas Veithen 2014年

2
如果您希望您的shell脚本仅在终止child之后才终止,那么_term()您应该在函数中wait "$child"再次终止。如果您还有其他监督过程在重新启动外壳脚本之前等待外壳脚本终止,或者您还被困EXIT进行一些清理工作并且仅在子进程完成后才开始运行,则可能有必要。
LeoRochael '17

1
@AlexanderMills阅读其他答案。您正在寻找exec,或者您想设置陷阱
Stuart P. Bentley

78

Bash不会将诸如SIGTERM之类的信号转发到其当前正在等待的进程。如果您想通过进入服务器来结束脚本(允许它处理信号和其他任何事情,就像您直接启动了服务器一样),则应使用exec,它将用正在打开的进程替换shell

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

如果你需要保持周围的外壳因某种原因(即你需要做一些清理服务器终止后),你应该使用的组合trapwaitkill。请参阅SensorSmith的答案


这是正确的答案!简明扼要,准确解决了OP的原始要求
BrDaHa

20

Andreas Veithen指出,如果您不需要从调用中返回(例如在OP的示例中),只需通过exec命令进行调用就足够了(@Stuart P. Bentley的答案)。否则,“传统” trap 'kill $CHILDPID' TERM(@cuonglm的答案)是一个开始,但是该wait调用实际上在陷阱处理程序运行之后返回,但仍可能在子进程实际退出之前。因此,建议您进行“额外”调用wait@ user1463361的答案)。

尽管这是一项改进,但它仍然具有竞争条件,这意味着该过程可能永远不会退出(除非发信号器重试发送TERM信号)。漏洞窗口介于注册陷阱处理程序和记录孩子的PID之间。

以下内容消除了该漏洞(打包在函数中以供重用)。

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid}
    trap - TERM INT
    wait ${term_child_pid}
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term

2
出色的工作-我已经更新了答案中的链接,以指向此处(最重要的是,它是一个更全面的解决方案,但我还是有些恼火,因为StackExchange UI并没有使我相信cuonglm 将脚本修复为实际上执行了预期的操作,并且在OP 甚至不理解之后做了几乎所有的解释性文字,然后做了一些小的重新编辑)。
Stuart P. Bentley

2
@ StuartP.Bentley,谢谢。我惊讶地组装了这个需要两个(不被接受)的答案和一个外部参考资料,然后我不得不降低比赛条件。我将把对链接的引用升级为我可以提供的其他一些小荣誉。
SensorSmith

3

提供的解决方案对我不起作用,因为在等待命令实际完成之前进程已被终止。我发现该文章http://veithen.github.io/2014/11/16/sigterm-propagation.html,对于我的应用程序来说,最后一个代码片段在自定义sh运行程序的OpenShift中很好用。需要sh脚本是因为我需要具有获取线程转储的能力,而在Java进程的PID为1的情况下这是不可能的。

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.