为什么ssh -t
不等待后台作业完成?
例子:
ssh user@example 'sleep 2 &'
这按预期工作,因为 ssh 在 2 秒后返回,而
ssh user@example -t 'sleep 2 &'
不等待sleep
完成并立即返回。
谁能解释这背后的原因吗?有没有办法让ssh -t
所有后台进程完成后再返回?
我的用例是,我使用 启动一个脚本ssh -t
,并且该脚本启动几个后台作业,这些作业在主脚本完成后应该保持活动状态。到目前为止这ssh -t
是不可能的。
答案1
如果没有-t
,则通过两个管道sshd
获取远程 shell 的 stdout (以及子级 shell sleep
)和 stderr (还通过另一个管道发送客户端的输入)。
sshd
确实会等待启动用户登录 shell 的进程,而且在该进程终止后还会等待 stdout 管道上的 eof(至少在 openssh 的情况下不是 stderr 管道)。
当管道的写入端没有打开任何进程的文件描述符时,就会发生 eof,这通常仅在所有没有将其 stdout 重定向到其他内容的进程都消失时才会发生。
当您使用-t
,sshd
时不使用管道。相反,与远程 shell 及其子级的所有交互(stdin、stdout、stderr)都是使用一对伪终端完成的。
对于伪终端对,为了sshd
与主端交互,没有类似的 eof 处理,而至少某些系统提供了替代方法来了解是否仍有进程向伪终端的从端开放 fd(请参见@下面的 JdeBP 注释),sshd
不使用它们,因此它只是等待执行远程用户登录 shell 的进程终止,然后退出。
退出后,pty 对的主端关闭,这意味着 pty 被销毁,因此从属控制的进程将收到 SIGHUP (默认情况下会终止它们)。
编辑:最后一部分是不正确的,尽管最终结果是相同的。看@UNIX.root 的回答正确描述到底发生了什么。
答案2
(将评论移至此处以包含更多信息。)
接受的答案中的部分SIGHUP
不正确。
退出后,pty 对的主端关闭,这意味着 pty 被销毁,因此由从属控制的进程将收到 SIGHUP。
不是这种情况。根据 POSIX,“如果控制终端的终端接口检测到调制解调器断开连接 [...]SIGHUP
则应发送信号到控制过程对于ssh -t 'sleep 2 &'
,是控制进程退出导致 tty 断开连接,因此SIGHUP
无法发送到控制进程,因为它已经死了。sleep
被杀死SIGHUP
实际上是因为当会话领导者退出, “这SIGHUP
信号应发送到每个进程在前台进程组中”。
令人困惑的部分是sleep 2 &
.是的,这是一个在后台运行的命令,但它不是后台进程组。后台进程组相关作业控制默认情况下,它在非交互式 shell 中被禁用(如ssh ... 'sleep 2 &'
)。实际上sleep 2 &
正在运行前台进程组。例如:
$ ssh -t localhost 'sleep 2 & ps jt'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
88819 88825 88825 88825 pts/36 88825 Ss+ 0 0:00 bash -c sleep 2 & ps jt
88825 88826 88825 88825 pts/36 88825 S+ 0 0:00 sleep 2
88825 88827 88825 88825 pts/36 88825 R+ 0 0:00 ps jt
可以看到,所有进程的PGID(88825)与bash shell的PID相同,TPGID也是88825。也就是说后台进程sleep 2 &
也在这里面前台进程组。
如需比较,请参见
$ pgrep -af sleep
$ ssh -t localhost 'set -m; sleep 123 & ps jt'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
89002 89008 89008 89008 pts/3 89010 Ss 0 0:00 bash -c set -m; sleep 123 & ps jt
89008 89009 89009 89008 pts/3 89010 S 0 0:00 sleep 123
89008 89010 89010 89008 pts/3 89010 R+ 0 0:00 ps jt
Connection to localhost closed.
$ ps j 89009
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 89009 89009 89008 ? -1 S 0 0:00 sleep 123
$
我们可以看到,启用作业控制后(set -m
),sleep 2 &
正在其自己的进程组 (PGID 89009) 中运行,该进程组是后台进程组。并且ssh
终止后,sleep
仍在运行。
(有关更多讨论,请参阅类似的场景:Expect +“ssh -f”不起作用)
答案3
使用wait
:
ssh user@example -t 'sleep 2 & wait'