通过 ssh -t 的 stderr

通过 ssh -t 的 stderr

这会将输出发送到 STDERR,但不会传播Ctrl+ C(即Ctrl+C将杀死ssh但不会杀死远程sleep):

$ ssh localhost 'sleep 100;echo foo ">&2"'

这会传播Ctrl+ C(即Ctrl+C将杀死ssh远程sleep),但将 STDERR 发送到 STDOUT:

$ ssh -tt localhost 'sleep 100;echo foo ">&2"'

如何强制第二个将 STDERR 输出发送到 STDERR,同时仍然传播Ctrl+ C

背景

GNU Parallel 使用“ssh -tt”来传播Ctrl+ C。这使得终止远程运行的作业成为可能。但发送到STDERR的数据在接收端应该继续转到STDERR。

答案1

我认为你无法回避这个问题。

使用-ttsshd会生成一个伪终端,并使从属部分成为执行远程命令的 shell 的 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

但在我看来,最好的办法是-t完全避免使用。这实际上仅适用于从真实终端进行交互使用。

poll()您可以使用执行 a 操作的包装器来检测已终止ssh或已关闭的连接,而不是依赖于传输 ^C 来让远程端关闭连接。

也许类似(简化后,您需要添加一些错误检查):

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)可能看起来很愚蠢,但其想法是等待挂起事件(stdout 上管道的读取端关闭)。 POLLHUP 作为要求的掩码被忽略。 POLLHUP 仅作为返回事件才有意义(告诉写作结束已关闭)。

我们必须为事件掩码指定一个非零值。如果我们使用0perl甚至不调用poll。所以这里我们使用POLLIN。

在 Linux 上,无论您请求什么,如果管道损坏,poll() 都会返回 POLLERR。

在 Solaris 和 FreeBSD 上,管道是双向的,当管道的读取端(也是那里的写入端)关闭时,它会以 POLLHUP 返回(以及 FreeBSD 上的 POLLIN,您必须请求 POLLIN,否则$p->poll()不会返回)返回)。

我无法说它在这三个操作系统之外的可移植性如何。

答案2

为了使其能够在其他平台上运行,这成为了最终的解决方案。它检查 ssh 客户端是否断开连接,因此父进程的 pid 变为 1:

$SIG{CHLD} = sub { $done = 1; };
$pid = fork;
unless($pid) {
    # Make own process group to be able to kill HUP it later
    setpgrp;
    exec $ENV{SHELL}, "-c", ($bashfunc."@ARGV");
    die "exec: $!\n";
}
do {
    # Parent is not init (ppid=1), so sshd is alive
    # Exponential sleep up to 1 sec
    $s = $s < 1 ? 0.001 + $s * 1.03 : $s;
    select(undef, undef, undef, $s);
} until ($done || getppid == 1);
# Kill HUP the process group if job not done
kill(SIGHUP, -${pid}) unless $done;
wait;
exit ($?&127 ? 128+($?&127) : 1+$?>>8)

相关内容