在ssh -t上的stderr


11

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

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

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

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

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

背景

GNU Parallel使用'ssh -tt'来传播Ctrl+ C。这样可以杀死远程运行的作业。但是发送到STDERR的数据应在接收端继续发送到STDERR。

Answers:


5

我认为您无法解决这个问题。

使用-ttsshd生成伪终端,并使从属部分成为执行远程命令的外壳程序的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仅作为返回的事件有意义(告诉端已关闭)。

我们必须为事件掩码提供非零值。如果我们使用0perl甚至不会打电话poll。因此,这里我们使用POLLIN。

在Linux上,无论您要求什么,如果管道断开,poll()都会返回POLLERR。

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

我不能说它在这三个操作系统之外的便携性。


我喜欢您的想法,但是除非设置了“ -tt”,否则我无法使您的包装器检测到任何信号。可以使用:parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31},但是删除'-tt'则不起作用。
Ole Tange 2014年

@OleTange包装的目的是在ssh死时(在ssh连接挂起时)将SIGHUP发送到远程作业。我不知道您的catch_all_signals做什么,但是它只会得到SIGHUP,并且只有在ssh连接断开后才可以(因此,如果它在stdout上打印任何内容,您将看不到它)。
斯特凡Chazelas

catch_all_signals将所有信号记录到文件中,并且如上所述,它与“ -tt”一起使用,但如果没有则失败。换句话说:当ssh死时,它不会从catch_wrap接收到SIGHUP。
Ole Tange 2014年

仍仅-tt在您编辑后才能使用。请记住,如果不通过并行运行命令,ssh将继承您从中运行命令的终端。
Ole Tange 2014年

@OleTange,我无法复制,它对我有用,您是否使用我发布的代码对其进行了测试?请将您的catch_wrap和catch_all_signals张贴在某处,以便我看看。有了-t,我希望它工作。
斯特凡Chazelas

1

为了使其在其他平台上工作,这成为最终解决方案。它检查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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.