我有一个类似于以下内容的Bash脚本:正在运行的服务器(会阻塞,因此无法进行陷阱)。有可能吗?

#1 楼

请尝试:

#!/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将忽略任何信号。使用&启动服务器会将其后台运行到Shell的作业控制系统中,其中$!保存服务器的PID(与waitkill一起使用)。然后,调用wait将等待具有指定PID(服务器)的作业完成,或者等待任何信号被触发。

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

评论


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

–洛伦茨
2014年7月26日在20:22

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

– iruvar
2014年7月26日在22:08

我认为1_CR的观点是正确的。您只需使用exec / bin / start / main / server --nodaemon(在这种情况下,shell进程将被服务器进程替换,并且您无需传播任何信号),或者使用/ bin / start / main /服务器--nodaemon&,但是exec并没有真正的意义。

– Andreas Veithen
2014年11月13日19:39

如果要让shell脚本仅在child终止后才终止,则应在_term()函数中再次等待“ $ child”。如果您还有其他监督过程在重新启动外壳脚本之前等待外壳脚本消失,或者您还困住EXIT进行清理并使其仅在子进程完成后才运行,则可能有必要。

–LeoRochael
17年5月8日在22:54

@AlexanderMills阅读其他答案。您正在寻找执行程序,或者您想设置陷阱。

– Stuart P. Bentley
18-09-29在16:06

#2 楼

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

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


如果出于某种原因需要保留外壳(即,在服务器终止后需要进行一些清理),则应结合使用trapwaitkill。请参阅SensorSmith的答案。

评论


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

– BrDaHa
19年8月15日在17:05

#3 楼

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


评论


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

– Stuart P. Bentley
18-09-29在16:01

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

–SensorSmith
18/09/30在20:21

@TorstenBronger它应该是可移植的,但是除了Bash之外,我没有对其进行任何测试。我没有使用任何故意的Bashisms(没有'function'关键字,没有双括号条件,在输出重定向中没有花哨的技巧,并且陷阱语法是Posix)。

–SensorSmith
19年11月12日在17:24

@TorstenBronger在Ubuntu 18.04 Bash 4.4.20(不是我的原始目标)下进行了重新测试,并获得带有行号和“ Terminated”的Bash调试输出,但当孩子未在陷阱之前退出(奇数)。在第一次等待之后忘记PID是合法的,但是在某些系统上第二次等待是必需的,因此没有好的答案。 (在此测试中,退出代码仍然可用。)我进行了编辑,以将发生错误的时间/系统将“错误”输出重定向为null。

–SensorSmith
20 Sep 20 '23:58

在gist.github.com/bronger/…,您会发现在我的案例(zsh)中需要做的事情。它仍然不能涵盖所有的极端情况,但是无论如何它们都可能被认为是编程错误。

–折腾者
20-09-21在5:09

#4 楼

提供的解决方案对我不起作用,因为在等待命令实际完成之前进程已被终止。我发现该文章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=$?