stderr over ssh -t

11

Это отправляет вывод в 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 на принимающей стороне.

Оле Танге
источник

Ответы:

5

Я не думаю, что вы можете обойти это.

С -tt, sshdпорождает псевдо-терминал и делает ведомый участию 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целом. Это действительно только для интерактивного использования с реального терминала.

Вместо того, чтобы полагаться на передачу ^ 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 имеет смысл только как возвращаемое событие (чтобы сказать, что конец записи был закрыт).

Мы должны дать ненулевое значение для маски события. Если мы используем 0, perlдаже не звонить poll. Так что здесь мы используем Поллин.

В Linux, что бы вы ни запросили, если канал разорван, poll () возвращает POLLERR.

В Solaris и FreeBSD, где каналы являются двунаправленными, когда конец чтения канала (который также является концом записи там) закрыт, он возвращается с POLLHUP (и POLLIN во FreeBSD, где вы должны запросить POLLIN, иначе $p->poll()нет возвращение).

Я не могу сказать, насколько он переносим за пределами этих трех операционных систем.

Стефан Шазелас
источник
Мне нравится ваша идея, но я не могу заставить вашу оболочку обнаруживать какие-либо сигналы, если не задано '-tt'. Это работает:, parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31}но удалите '-tt' и тогда это не будет работать.
Оле Танге
@OleTange Назначение оболочки - отправка SIGHUP удаленному заданию, когда ssh умирает (после зависания ssh-соединения). Я не знаю, что делает ваш catch_all_signals, но все, что он получит, это SIGHUP и только после разрыва соединения ssh (поэтому, если он напечатает что-либо на stdout, вы его не увидите).
Стефан Шазелас
catch_all_signals записывает все сигналы в файл и, как уже упоминалось, работает с '-tt', но без сбоев. Другими словами: он не получает SIGHUP от catch_wrap, когда ssh умирает.
Оле Танге
Все еще работает только -ttпосле вашего редактирования. Помните, что если вы не запускаете команду через параллель, ssh унаследует терминал, с которого вы ее запускаете.
Оле Танге
@OleTange, я не могу воспроизвести, это работает для меня, вы проверили это с кодом, который я разместил? Пожалуйста, опубликуйте ваши catch_wrap и catch_all_signals где-нибудь, чтобы я мог посмотреть. С -t, я ожидаю , что это не к работе.
Стефан Шазелас
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)
Оле Танге
источник