我有一个 bash 脚本 start.sh ,如下所示:
for thing in foo bar; do
{
background_processor $thing
cleanup_on_exit $thing
} &
done
这就是我想要的:我运行 start.sh,它以代码 0 退出,两个子 shell 在后台运行。每个子shell运行background_processor
,当它退出时,它会运行cleanup_on_exit
。即使我退出最初运行 start.sh 的终端(即使这是一个 ssh 连接),这仍然有效。
然后我尝试了这个:
ssh user@host "start.sh"
这有效,除了之后start.sh
已经退出, ssh 显然也在等待子 shell 退出。我真的不明白为什么。一次start.sh
退出时,子 shell 成为 pid 1 的子级,并且它们甚至没有分配 tty...所以我无法理解它们如何仍然与我的 ssh 连接相关联。
我后来尝试了这个:
ssh -t user@host "start.sh"
现在进程已经分配了一个伪终端。现在,我发现 ssh 确实会退出start.sh
退出,但它也会杀死子进程。
我猜测在后一种情况下子进程会被发送 SIGHUP,所以我这样做了:
ssh -t user@host "nohup start.sh"
这确实有效!所以,我有一个解决我的实际问题的方法,但我想在这里掌握 SIGHUP/tty 内容的微妙之处。
总之,我的问题是:
- 为什么 ssh (不带 -t)甚至在之后还要等待子进程
start.sh
退出,即使它们有父 pid 1?
- 为什么 ssh (使用 -t )会杀死子进程(显然是使用 SIGHUP ),即使当我从终端运行它们并从该终端注销时不会发生这种情况?
我想我现在可以解释这一点了!我必须了解一些关于会话和流程组是什么的知识,我通过阅读做到了这一点TTY 揭秘 http://www.linusakesson.net/programming/tty/.
- 为什么 ssh(不带 -t)即使在 start.sh 退出后仍等待子进程,即使它们的父进程 pid 为 1?
因为没有 tty,ssh 通过管道(然后由子进程继承)连接到 shell 进程的 stdin/stdout/stderr,并且我正在使用的 OpenSSH 版本 (OpenSSH_4.3p2) 会等待这些套接字在关闭之前退出。某些早期版本的 OpenSSH 并非如此。对此有一个很好的解释,有道理,here http://www.snailbook.com/faq/background-jobs.auto.html.
相反,当使用交互式登录(或ssh -t
)、ssh 且进程正在使用 TTY,因此无需等待管道。
我可以通过重定向流来恢复我想要的行为。此变体立即返回:ssh user@host "start.sh < /dev/null > /dev/null 2>&1"
- 为什么 ssh (使用 -t )会杀死子进程(显然是使用 SIGHUP ),即使当我从终端运行它们并从该终端注销时不会发生这种情况?
因为 bash 以非交互模式启动,这意味着默认情况下禁用作业控制,因此子进程与父 bash 进程(即会话领导者)位于同一进程组中。当父 bash 进程退出时,内核向其进程组(位于前台)发送 SIGHUP,如下所述setpgid(2)
:
如果会话有控制终端,...[并且]会话领导者退出,则 SIGHUP 信号将被发送到控制终端的前台进程组中的每个进程。
相反,当使用交互式登录时,bash 处于交互模式,这意味着默认情况下启用作业控制,因此子进程进入单独的进程组,并且在退出时永远不会收到 SIGHUP。
我可以通过使用恢复我想要的行为set -m
在 bash 中启用作业控制。如果我添加set -m
to start.sh
,当 ssh 退出时,孩子们不再被杀死。
谜团解开了:)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)