在 Bash 中将 SIGTERM 转发给孩子

Lor*_*enz 120 shell bash signals docker

我有一个 Bash 脚本,它看起来类似于:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon
Run Code Online (Sandbox Code Playgroud)

现在,如果运行脚本的 bash shell 收到一个 SIGTERM 信号,它也应该向正在运行的服务器发送一个 SIGTERM(它会阻塞,所以不可能有陷阱)。那可能吗?

cuo*_*glm 139

尝试:

#!/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"
Run Code Online (Sandbox Code Playgroud)

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

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

  • 但是[exec用给定的程序替换shell](http://wiki.bash-hackers.org/commands/builtin/exec),我不清楚为什么需要随后的`wait`调用? (7认同)
  • 我认为 1_CR 的观点是有效的。要么你简单地使用`exec /bin/start/main/server --nodaemon`(在这种情况下shell进程被服务器进程替换并且你不需要传播任何信号)或者你使用`/bin/start /main/server --nodaemon &`,但是`exec` 没有什么意义。 (5认同)
  • 如果您希望 shell 脚本仅在 child 终止后终止,那么在 `_term()` 函数中,您应该再次`wait "$child"`。如果你有一些其他的监督进程在重新启动它之前等待 shell 脚本死掉,或者如果你还困了 `EXIT` 来做一些清理并且只需要它在子进程完成后运行,这可能是必要的。 (4认同)
  • 根据“man”和我的本地测试,我认为“bash”仅在“交互”模式下忽略 SIGTERM。否则很好的答案。 (2认同)
  • @AlexanderMills 阅读其他答案。要么您正在寻找 [`exec`](https://unix.stackexchange.com/a/196053/7733),或者您想 [设置陷阱](https://unix.stackexchange.com/a /444676/7733)。 (2认同)

Stu*_*ley 103

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

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon
Run Code Online (Sandbox Code Playgroud)

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

  • 这是正确答案!更加简洁,准确地解决了 OP 的原始问题 (3认同)

Sen*_*ith 40

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} 2>/dev/null
    trap - TERM INT
    wait ${term_child_pid} 2>/dev/null
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term
Run Code Online (Sandbox Code Playgroud)

  • @StuartP.Bentley,谢谢。我 _was_ 惊讶地组装这需要两个(未接受的)答案和一个外部参考,然后我不得不降低竞争条件。我会将我对链接的引用升级为我可以给予的额外荣誉。 (4认同)
  • 出色的工作 - 我已经更新了我的答案中的链接以指向此处(除此之外,这是一个更全面的解决方案,我仍然有点生气 StackExchange UI 没有将我归功于 cuonglm 的 [修复脚本实际上做它应该做的](https://unix.stackexchange.com/revisions/146770/4)和[编写几乎所有的解释性文本](https://unix.stackexchange.com/revisions/146770/ 5) 在 OP 之后 [谁甚至不明白](https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash/196053#comment328666_146770) 做了一些小的重新-编辑)。 (3认同)
  • @TorstenBronger 它_应该_是可移植的,但我没有在 Bash 下测试过它。我没有使用任何刻意的 Bashisms(没有 'function' 关键字,没有双括号条件,在输出重定向中没有花哨的技巧,陷阱语法是 Posix)。 (2认同)
  • @TorstenBronger 在 Ubuntu 18.04 Bash 4.4.20(不是我的原始目标)下重新测试并使用第 # 行和“终止”获取 Bash 调试输出,但是当孩子在陷阱之前没有退出时(奇怪)。在第一次等待后忘记 PID 可能是合法的,但在某些系统上第二次等待是必要的,所以没有好的答案。(退出代码在此测试中仍然可用。)我编辑以将“错误”输出重定向到 null 以用于发生它的时间/系统。 (2认同)
  • 在 https://gist.github.com/bronger/acce7736141b3fa118b0d47f1a2035ac#file-signal_propagation-sh-L45 上,您可以看到在我的情况下 (zsh) 需要什么。它仍然没有涵盖所有边缘情况,但无论如何它们都可能被视为编程错误。 (2认同)

小智 7

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

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
Run Code Online (Sandbox Code Playgroud)