这会将输出发送到 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
我认为你无法回避这个问题。
使用-tt
,sshd
会生成一个伪终端,并使从属部分成为执行远程命令的 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 仅作为返回事件才有意义(告诉写作结束已关闭)。
我们必须为事件掩码指定一个非零值。如果我们使用0
,perl
甚至不调用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)