在 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 脚本中执行此操作的常用方法是什么?请注意,我想要两件事:
- 防止重新执行该
ssh
命令 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
)所以这并不像你想要的那么通用,但使用foo
非ssh
交互式脚本中的类似简单命令,该方法应该可以起作用。
kill
带有负参数会向整个进程组发送信号。
但需要注意的是:可能存在竞争条件。一般来说,foo
在子 shell 接收并处理信号之前,可能被终止。如果延迟足够长(无论出于何种原因),则在完成其工作后foo
可能会生成一个新的(尤其是在没有的情况下sleep 1
)kill
。考虑这种改进:
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
并采取适当的措施。规则如下:
- 如果
ssh
进程正常退出(例如有人exit
在交互式会话中输入内容),autossh
则退出而不是重新启动;- 如果
autossh
自身接收到SIGTERM
、SIGINT
或SIGKILL
信号,则认为这是故意发出的信号,并在终止子ssh
进程后退出;- […]
- […]
- 如果子
ssh
进程由于其他原因死亡,autossh
将尝试启动一个新的进程。
所以:
autossh … &
apid=$!
# the rest of the script here
kill "$apid"
请注意,如果隧道无法建立,您将不会收到通知。由于这也是您原始方法的一个可能缺陷,因此我不会在这里解决这个问题。