当我的脚本停止时,如何停止内联后台进程?

当我的脚本停止时,如何停止内联后台进程?

在 shell 脚本中,我执行以下操作:

#!/bin/sh

while true; do ssh -o ExitOnForwardFailure=yes -L 8080:localhost:80 -N server; sleep 1; done &

... rest of the script, which uses the tunnel as made above ...

这保证了隧道始终保持打开状态,因此在连接丢失的情况下可以重新打开。此隧道用于主脚本的其他部分,此处省略。这些部分可以处理不起作用的隧道,它们只需稍后重试即可。

当主脚本终止时(例如由于SIGTERM或 )SIGINT,我希望该while循环也停止。主脚本终止后,无需保持该隧道打开。

在 shell 脚本中执行此操作的常用方法是什么?请注意,我想要两件事:

  1. 防止重新执行该ssh命令
  2. ssh尽快停止并断开当前正在进行的会话

我不确定如何在 shell 脚本中完成所有这些操作。

请注意,我目前正在从事普通工作,但如果需要,sh我可以继续。bash

答案1

一种有点普遍的方法。

while true; do foo; sleep 1; done &
# the rest of the script here
kill -- -"$$"

诀窍是脚本运行子进程(这里foo还有其他进程),其进程组 ID (PGID) 等于 shell 的 PID。这会传播到孙进程等等。shell 本身也在这个进程组中。也有例外(交互式 shell 中的作业,timeout)所以这并不像你想要的那么通用,但使用foossh交互式脚本中的类似简单命令,该方法应该可以起作用。

kill带有负参数会向整个进程组发送信号。

但需要注意的是:可能存在竞争条件。一般来说,foo在子 shell 接收并处理信号之前,可能被终止。如果延迟足够长(无论出于何种原因),则在完成其工作后foo可能会生成一个新的(尤其是在没有的情况下sleep 1kill。考虑这种改进:

while true; do foo; sleep 1; done &
subpid=$!
# the rest of the script here
kill "$subpid"
wait "$subpid" 2>/dev/null
# at this moment we're certain the subshell is no more, new foo will not be spawned
trap '' TERM
# foo will maintain the old PGID, so…
kill -- -"$$" 2>/dev/null

此处的陷阱只是为了让主 shell 正常退出,而无需打印Terminated到控制台。


ssh对于任何后台进程来说,这不是一种通用的方法,但通常是在类似场景中有用的方法。

使用autossh. 来自它的手册

autossh是一个启动副本ssh并监视它的程序,如果它死机或停止传输流量则根据需要重新启动它。

[…]

autossh尝试辨别其所监视进程的死亡方式ssh并采取适当的措施。规则如下:

  1. 如果ssh进程正常退出(例如有人exit在交互式会话中输入内容),autossh则退出而不是重新启动;
  2. 如果autossh自身接收到SIGTERMSIGINTSIGKILL信号,则认为这是故意发出的信号,并在终止子ssh进程后退出;
  3. […]
  4. […]
  5. 如果子ssh进程由于其他原因死亡,autossh将尝试启动一个新的进程。

所以:

autossh … &
apid=$!
# the rest of the script here
kill "$apid"

请注意,如果隧道无法建立,您将不会收到通知。由于这也是您原始方法的一个可能缺陷,因此我不会在这里解决这个问题。

相关内容