web-dev-qa-db-ja.com

ssh -t上のstderr

これはSTDERRに出力を送信しますが、伝搬しません Ctrl+C (すなわち Ctrl+Csshを殺しますが、リモートsleepは殺しません):

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

これは伝播します Ctrl+C (すなわち Ctrl+Csshとリモートsleep)を強制終了しますが、STDERRをSTDOUTに送信します。

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

2番目のSTDERR出力をSTDERRに送信するように強制しながら、それを伝搬するにはどうすればよいですか? Ctrl+C

背景

GNU Parallelは「ssh -tt」を使用して伝播します Ctrl+C。これにより、リモートで実行中のジョブを強制終了できます。ただし、STDERRに送信されたデータは、受信側のSTDERRに送信され続ける必要があります。

11
Ole Tange

あなたはそれを回避することはできないと思います。

_-tt_を使用すると、sshdは疑似端末を生成し、スレーブ部分をリモートコマンドを実行するシェルのstdin、stdout、およびstderrにします。

sshdは、その(単一の)fdから擬似端末のマスター部分に送信されるものを読み取り、それを(単一のチャネルを介して)sshクライアントに送信します。 _-t_がないため、stderrの2番目のチャネルはありません。

さらに、疑似端末の端末回線規則により、出力が変更される可能性があります(デフォルトでは変更されます)。たとえば、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
_

入力側ではさらに多くのことが起こります(SIGINTを引き起こす_^C_文字など)だけでなく、他の信号、エコー、および標準モード行エディターに関連するすべての処理)。

Stderrをfifoにリダイレクトし、2番目の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)はばかげているように見えるかもしれませんが、アイデアはhang-hupイベントを待つことです(標準出力のパイプの読み取り側が閉じられるため)。 requestedマスクとしてのPOLLHUPは無視されます。 POLLHUPは、返されるイベントとしてのみ意味があります(writingの終わりが閉じられたことを通知します)。

イベントマスクにはゼロ以外の値を指定する必要があります。 _0_を使用する場合、Perlpollを呼び出すことさえありません。ここでは、POLLINを使用します。

Linuxでは、要求が何であれ、パイプが壊れた場合、poll()はPOLLERRを返します。

パイプが双方向であるSolarisおよびFreeBSDでは、パイプの読み取り側(書き込み側でもある)が閉じている場合、POLLHUP(およびFreeBSDではPOLLIN、POLLINまたはelse $p->poll()は返されません)。

それ以外の点では、これら3つのオペレーティングシステムの外では、どれほどポータブルであるかはわかりません。

5

他のプラットフォームで機能させるには、これが最終的な解決策となりました。 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)
1
Ole Tange