stderr over ssh -t

11

Ini mengirimkan output ke STDERR, tetapi tidak menyebarkan Ctrl+ C(yaitu Ctrl+ Cakan membunuh sshtetapi tidak jarak jauh sleep):

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

Ini menyebar Ctrl+ C(yaitu Ctrl+ Cakan membunuh sshdan remote sleep), tetapi mengirim STDERR ke STDOUT:

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

Bagaimana saya bisa memaksa yang kedua untuk mengirim output STDERR ke STDERR, sementara masih menyebarkan Ctrl+ C?

Latar Belakang

GNU Parallel menggunakan 'ssh -tt' untuk menyebarkan Ctrl+ C. Hal ini memungkinkan untuk membunuh pekerjaan yang berjalan jarak jauh. Tetapi data yang dikirim ke STDERR harus terus ke STDERR di pihak penerima.

Ole Tange
sumber

Jawaban:

5

Saya tidak berpikir Anda bisa mengatasi itu.

Dengan -tt, sshdmemunculkan terminal pseudo dan menjadikan slave sebagai stdin, stdout dan stderr dari shell yang mengeksekusi perintah remote.

sshdmembaca apa yang datang dari (tunggal) fd ke bagian master dari pseudo-terminal dan mengirimkannya (melalui satu saluran tunggal) ke sshklien. Tidak ada saluran kedua untuk stderr karena tidak ada -t.

Selain itu perhatikan bahwa disiplin garis terminal pseudo-terminal dapat (dan akan secara default) mengubah output. Misalnya LF akan dikonversi ke CRLF di sana dan tidak di terminal lokal, jadi Anda mungkin ingin menonaktifkan output pasca-pemrosesan.

$ 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

Lebih banyak hal akan terjadi pada sisi input (seperti ^Ckarakter yang akan menyebabkan SIGINT, tetapi juga sinyal lain, gema dan semua penanganan yang terlibat dalam editor baris mode kanonik ).

Anda mungkin bisa mengarahkan stderr ke fifo dan mengambilnya menggunakan yang kedua ssh:

ssh -tt host 'mkfifo fifo && cmd 2> fifo' &
ssh host 'cat fifo' >&2

Tetapi IMO terbaik adalah untuk menghindari menggunakan -tsama sekali. Itu benar-benar hanya dimaksudkan untuk penggunaan interaktif dari terminal nyata.

Alih-alih mengandalkan transmisi ^ C untuk membiarkan remote control koneksi ditutup, Anda bisa menggunakan pembungkus yang berfungsi poll()untuk mendeteksi sshkoneksi yang terbunuh atau tertutup.

Mungkin sesuatu seperti (disederhanakan, Anda ingin menambahkan beberapa pengecekan kesalahan):

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'

Hal di $p->mask(STDOUT, POLLIN)atas mungkin terlihat konyol, tetapi idenya adalah menunggu acara hang-hup (agar ujung pipa pembacaan pada stdout ditutup). POLLHUP sebagai topeng yang diminta diabaikan. POLLHUP hanya bermakna sebagai acara yang dikembalikan (untuk mengatakan bahwa akhir penulisan telah ditutup).

Kami harus memberikan nilai bukan nol untuk masker acara. Jika kita menggunakan 0, perlbahkan tidak menelepon poll. Jadi di sini kita menggunakan POLLIN.

Di Linux, apa pun yang Anda minta, jika pipa menjadi rusak, poll () mengembalikan POLLERR.

Pada Solaris dan FreeBSD, di mana pipa dua arah, ketika ujung pembacaan pipa (yang juga merupakan akhir tulisan) ditutup, ia kembali dengan POLLHUP (dan POLLIN di FreeBSD, di mana Anda harus meminta POLLIN atau yang lain $p->poll()tidak kembali).

Saya tidak bisa mengatakan seberapa portabelnya di luar ketiga sistem operasi itu.

Stéphane Chazelas
sumber
Saya suka ide Anda, tetapi saya tidak bisa membuat pembungkus Anda mendeteksi sinyal apa pun kecuali '-tt' diatur. Ini berfungsi parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31}:, tetapi hapus '-tt' dan kemudian tidak berfungsi.
Ole Tange
@OleTange Tujuan wrapper adalah agar SIGHUP dikirim ke pekerjaan jarak jauh ketika ssh mati (setelah koneksi ssh hang-up). Saya tidak tahu apa yang catch_all_signals Anda lakukan, tetapi yang akan didapat hanyalah SIGHUP itu dan hanya setelah koneksi ssh turun (jadi jika mencetak apa pun di stdout, Anda tidak akan melihatnya).
Stéphane Chazelas
catch_all_signals mencatat semua sinyal ke file, dan seperti disebutkan berfungsi dengan '-tt', tetapi gagal tanpa. Dengan kata lain: Itu tidak menerima SIGHUP dari catch_wrap ketika ssh mati.
Ole Tange
Masih hanya berfungsi -ttsetelah diedit. Harap diingat jika Anda tidak menjalankan perintah melalui paralel, ssh akan mewarisi terminal tempat Anda menjalankannya.
Ole Tange
@ OleTange, saya tidak bisa mereproduksi, itu berfungsi untuk saya, sudahkah Anda mengujinya dengan kode yang saya posting? Silakan kirim catch_wrap dan catch_all_signals Anda ke suatu tempat sehingga saya dapat melihatnya. Dengan -t, saya berharap itu tidak berhasil.
Stéphane Chazelas
1

Untuk membuatnya bekerja pada platform lain, ini menjadi solusi terakhir. Ia memeriksa apakah klien ssh terputus dan dengan demikian orang tua menjadi 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)
Ole Tange
sumber