我认为您无法解决这个问题。
使用-tt
,sshd
生成伪终端,并使从属部分成为执行远程命令的外壳程序的stdin,stdout和stderr。
sshd
从其(单个)fd读取什么到伪终端的主机部分,并将其(通过一个单一通道)发送给ssh
客户端。stderr没有第二个通道,而没有-t
。
此外,请注意,伪终端的终端线路规则可能会(并且默认情况下)会更改输出。例如,LF将在那儿而不是在本地终端上转换为CRLF,因此您可能要禁用输出后处理。
$ ssh localhost 'echo x' | hd
00000000 78 0a |x.|
00000002
$ ssh -t localhost 'echo x' | hd
00000000 78 0d 0a |x..|
00000003
$ ssh -t localhost 'stty -opost; echo x' | hd
00000000 78 0a |x.|
00000002
输入端会发生更多事情(例如^C
会导致SIGINT 的字符,以及其他信号,回波和规范模式行编辑器中涉及的所有处理)。
您可以将stderr重定向到fifo并使用第二个命令检索它ssh
:
ssh -tt host 'mkfifo fifo && cmd 2> fifo' &
ssh host 'cat fifo' >&2
但是,最好的IMO是避免-t
完全使用。这实际上仅是为了在真实终端中进行交互使用。
不必依靠^ C的传输来让远程端关闭连接,而是可以使用包装器poll()
来检测被终止ssh
或关闭的连接。
可能是这样的(简化的,您需要添加一些错误检查):
LC_HUP_DETECTOR='
use IO::Poll;
$SIG{CHLD} = sub {$done = 1};
$p = IO::Poll->new;
$p->mask(STDOUT, POLLIN);
$pid=fork; unless($pid) {setpgrp; exec @ARGV; die "exec: $!\n"}
$p->poll;
kill SIGHUP, -$pid unless $done;
wait; exit ($?&127 ? 128+($?&127) : 1+$?>>8)
' ssh host 'perl -e "$LC_HUP_DETECTOR" some cmd'
在$p->mask(STDOUT, POLLIN)
上述可能看起来很傻,但这个想法是等待挂起HUP事件(在标准输出管道的读出端被关闭)。POLLHUP作为请求的掩码将被忽略。POLLHUP仅作为返回的事件有意义(告诉写端已关闭)。
我们必须为事件掩码提供非零值。如果我们使用0
,perl
甚至不会打电话poll
。因此,这里我们使用POLLIN。
在Linux上,无论您要求什么,如果管道断开,poll()都会返回POLLERR。
在Solaris和FreeBSD上,管道是双向的,当管道的读取端(也是那里的写入端)关闭时,它将返回POLLHUP(在FreeBSD上为POLLIN,您必须在其中请求POLLIN,否则$p->poll()
不需要返回)。
我不能说它在这三个操作系统之外的便携性。
parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31}
,但是删除'-tt'则不起作用。