stderr ponad ssh -t

11

To wysyła dane wyjściowe do STDERR, ale nie propaguje Ctrl+ C(tzn. Ctrl+ CZabije, sshale nie zdalny sleep):

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

To propaguje Ctrl+ C(tzn. Ctrl+ CZabije sshi zdalnie sleep), ale wysyła STDERR do STDOUT:

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

Jak mogę zmusić sekundę do wysłania wyjścia STDERR do STDERR, wciąż propagując Ctrl+ C?

tło

GNU Parallel używa 'ssh -tt' do propagowania Ctrl+ C. Umożliwia to zabijanie zdalnie uruchomionych zadań. Ale dane wysyłane do STDERR powinny nadal przechodzić do STDERR po stronie odbierającej.

Ole Tange
źródło

Odpowiedzi:

5

Nie sądzę, że można to obejść.

Za pomocą -tt, sshdspawnuje pseudo-terminal i sprawia, że ​​slave staje się stdin, stdout i stderr powłoki wykonującej zdalne polecenie.

sshdodczytuje to, co nadchodzi z (pojedynczego) fd do głównej części pseudo-terminala i wysyła to (jednym kanałem) do sshklienta. Nie ma drugiego kanału dla stderr, ponieważ jest bez -t.

Ponadto zauważ, że dyscyplina linii pseudo-terminala może (i domyślnie) zmieni dane wyjściowe. Na przykład LF zostanie tam przekonwertowany na CRLF, a nie na lokalnym terminalu, więc możesz chcieć wyłączyć przetwarzanie końcowe.

$ 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

Po stronie wejściowej wydarzy się znacznie więcej rzeczy (np. ^CZnak, który spowoduje SIGINT, ale także inne sygnały, echo i cała obsługa związana z edytorem linii w trybie kanonicznym ).

Możesz ewentualnie przekierować stderr do fifo i odzyskać go za pomocą sekundy ssh:

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

Ale najlepszym IMO byłoby -tcałkowite uniknięcie używania . To naprawdę jest przeznaczone tylko do interaktywnego użytku z prawdziwego terminala.

Zamiast polegać na transmisji ^ C, aby umożliwić zdalnemu końcowi połączenie jest zamknięte, możesz użyć opakowania, które poll()wykrywa przerwane sshlub zamknięte połączenie.

Może coś takiego (uproszczone, będziesz chciał dodać trochę sprawdzania błędów):

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)Powyżej może wydawać się głupie, ale chodzi o to, aby czekać na imprezie hang-hup (na koniec czytania rury na stdout być zamknięte). POLLHUP jako żądana maska ​​jest ignorowana. POLLHUP ma znaczenie jedynie jako wydarzenie zwrócone (aby powiedzieć, że koniec pisania został zamknięty).

Musimy podać niezerową wartość dla maski zdarzenia. Jeśli skorzystamy 0, perlnawet nie zadzwonimy poll. Więc tutaj używamy POLLIN.

W Linuksie, cokolwiek poprosisz, jeśli potok się zepsuje, poll () zwróci POLLERR.

W systemach Solaris i FreeBSD, gdzie potoki są dwukierunkowe, gdy koniec odczytu potoku (który jest tam również końcem zapisu) jest zamknięty, zwraca on z POLLHUP (i POLLIN na FreeBSD, gdzie musisz poprosić POLLIN, w przeciwnym $p->poll()razie powrót).

Nie mogę powiedzieć, jak przenośny jest poza tymi trzema systemami operacyjnymi.

Stéphane Chazelas
źródło
Podoba mi się twój pomysł, ale nie mogę sprawić, by opakowanie wykryło jakiekolwiek sygnały, dopóki nie zostanie ustawione „-tt”. To działa parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31}:, ale usuń „-tt”, a następnie nie będzie działać.
Ole Tange
@OleTange Zadaniem opakowania jest wysłanie SIGHUP do zadania zdalnego po śmierci ssh (po rozłączeniu połączenia ssh). Nie wiem, co robi twoja funkcja catch_all_signals, ale wszystko, co by to było, to SIGHUP i dopiero po zerwaniu połączenia ssh (więc jeśli wydrukuje coś na standardowym wyjściu, nie zobaczysz tego).
Stéphane Chazelas
catch_all_signals rejestruje wszystkie sygnały do ​​pliku i, jak wspomniano, działa z '-tt', ale bez niego zawiedzie. Innymi słowy: po śmierci ssh nie otrzymuje SIGHUP od catch_wrap.
Ole Tange
Nadal działa tylko -ttpo edycji. Pamiętaj, że jeśli nie uruchomisz polecenia równolegle, ssh odziedziczy terminal, z którego go uruchomisz.
Ole Tange
@OleTange, nie mogę się rozmnażać, to działa dla mnie, czy przetestowałeś to z kodem, który opublikowałem? Proszę zamieścić gdzieś swoje catch_wrap i catch_all_signals, abym mógł zobaczyć. Z -t, spodziewam się, że to nie zadziała.
Stéphane Chazelas
1

Aby działało na innych platformach, stało się to ostatecznym rozwiązaniem. Sprawdza, czy klient ssh się rozłączył, a tym samym rodzic stał się 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
źródło