当 stdout 和 stderr 都被重定向时,ssh 如何/为什么会输出到 tty?

当 stdout 和 stderr 都被重定向时,ssh 如何/为什么会输出到 tty?

我刚刚注意到

ssh user@host >/tmp/out 2>/tmp/err

可以写类似

Warning the RAS host key...
...
Are you sure you want to continue connecting (yes/no)?

stdout 和 stderr 都被重定向了但这仍然显示在 tty 中。

  1. 这是怎么ssh做的?
  2. 为什么ssh要这样做?这难道不违反 *nix 习惯用法吗?
  3. 当我以子进程的形式运行ssh(或另一个具有类似行为的程序)时,stdin/stdout/stderr 通过管道连接,我希望父进程能够看到子进程的输出。如果子进程像这样避开 stdout/stderr,父进程如何捕获它?

答案1

如何?

这是怎么ssh做的?

它打开了/dev/tty。相关行来自strace ssh …

openat(AT_FDCWD, "/dev/tty", O_RDWR)    = 4

然后文件描述符与和 一起4使用。write(2)read(2)

(测试平台:OpenSSH_7.9p1 Debian-10+deb10u2)。


为什么?

为什么ssh要这样做?这难道不违反 *nix 习惯用法吗?

我不太确定“*nix 习语”,不管它们是什么;但 POSIX明确允许

/dev/tty
在每个进程中,与该进程的进程组关联的控制终端的同义词(如果有)。对于希望确保无论输出如何重定向都能向终端写入消息或从终端读取数据的程序或 shell 过程来说,它很有用。[…]

(重点是我的)。

需要的工具相互影响用户倾向于使用,/dev/tty因为它有意义。通常当用户这样做时:

<local_file_0 ssh user@server tool >local_file_1 2>local_file_2

他们希望它尽可能与此相似:

<local_file_0 tool >local_file_1 2>local_file_2

唯一的区别应该是tool实际运行的位置。通常用户希望ssh透明。他们不希望它乱七八糟local_file_1local_file_2也不想知道是否需要在万一询问时放入或yes。通常人们无法预测是否会在任何特定情况下询问。nolocal_file_0sshssh

请注意,运行时ssh user@server tool远程端会涉及一个 shell(比较我的这个答案)。shell 可以获取一些启动脚本,这些脚本可能会使输出变得杂乱。这是一个不同的问题(以及相关启动脚本应该保持静默的原因)。


解决方案

当我以子进程的形式运行ssh(或另一个具有类似行为的程序)时,stdin/stdout/stderr 通过管道连接,我希望父进程能够看到子进程的输出。如果子进程像这样避开 stdout/stderr,父进程如何捕获它?

如上所述,您的愿望相当不寻常。这并不意味着它很奇怪或完全不常见。有些用例确实需要这样做。解决方案是提供您可以控制的伪终端。正确的工具是expect(1)。它不仅会提供 tty,还会允许您实现一些逻辑。您将能够检测(并记录)Are you sure you want to continue connecting并回答;yes或者no,如果ssh没有询问,则什么也不回答。

如果你想在正常交互的同时捕获整个输出,那么考虑script(1)


更广阔的前景

到目前为止,我们感兴趣的是在客户端分配 tty,即ssh运行的地方。通常,您可能希望运行一个需要/dev/tty在服务器端的工具。SSH 服务器是否能够分配伪终端,相关选项是-t-T(请参阅man 1 ssh)。例如,如果您这样做:

ssh user@server 'sudo whatever'

那么您很可能会看到sudo: no tty present …。在远程端提供一个 tty,它就会工作:

ssh -t user@server 'sudo whatever'

但有一个怪癖。如果没有-t默认的 stdin,远程命令的 stdout 和 stderr 将连接到本地ssh进程的 stdin、stdout 和 stderr。这意味着你可以区分远程 stdout 和远程 stderr本地。默认情况下,远程命令的-tstdin、stdout 和 stderr(和/dev/tty)指向同一个伪终端;现在,stdout、stderr 和远程命令写入其的任何内容/dev/tty都合并为一个单身的将本地ssh打印流式传输到其(本地)标准输出。您无法在本地区分它们。此命令:

ssh -t user@server 'sudo whatever' >local_file_1

会将提示从写入sudo文件!通过使用/dev/tty sudo自身,在重定向时尝试保持透明,但却ssh -t破坏了这一点。

在这种情况下,如果提供一个选项来在远端ssh分配一个伪终端( )并将其连接到本地的,将会很有用,/dev/tty/dev/ttyssh同时仍将默认的远程 stdin、stdout 和 stderr 连接到其本地对应端. 四个独立通道(其中一个是双向的/dev/tty:)。

据我所知,没有这样的选择(查看这个问题和我的回答:ssh具有单独的 stdin、stdout、stderr 和 tty)。目前,您可以拥有三个单向通道(不/dev/tty包括远程进程),或者/dev/tty为远程进程提供一个双向通道( ),ssh为本地用户提供两个单向通道(本地的 stdin 和 stdout )。

您的原始命令:

ssh user@host >/tmp/out 2>/tmp/err

未指定远程命令,因此它在远程端运行交互式 shell,并为其提供伪终端,就像您使用一样-t(除非没有本地终端)。这是“远程进程的一个双向通道”的情况。这意味着/tmp/err只能从ssh本身获取 stderr(例如,如果您使用ssh -v)。

一个交互的shell 的输出不会打印到(本地)终端,因此无法轻松地以交互方式使用。我希望这只是一个最简单的例子(如果不是,那么您可能需要重新考虑这一点)。

/dev/tty无论如何,您都可以看到,ssh以及其他工具的使用情况/dev/tty可能会很复杂。

相关内容