O comando ssh continua inesperadamente em outro sistema depois que o ssh termina

11

Estou executando o comando abaixo e monitorando o arquivo de saída no outro sistema:

ssh $ip_address 'for n in 1 2 3 4 5; do sleep 10; echo $n >>/tmp/count; done'

Se eu matar o comando ssh usando ^Cou apenas matando o terminal no qual estou conectado, esperaria que o comando remoto também terminasse. Porém, isso não acontece: /tmp/countobtém todos os números de 1 a 5 independentemente e ps -ejHmostra o shell e seu sleepfilho continuando em execução.

Esse comportamento é esperado e está documentado em algum lugar? Posso desativá-lo? Pela leitura, eu esperava ter que habilitar explicitamente esse tipo de comportamento com nohup, não para que seja o padrão.

Dei uma olhada nas páginas de manual para ssh e sshd, mas não vi nada óbvio, e o Google me aponta instruções para ativar esse comportamento, não para desativá-lo.

Estou executando o Red Hat Enterprise Linux 6.2, com um login root e um shell bash nos dois sistemas.

eu e
fonte

Respostas:

11

A resposta de uther diz para você alocar um terminal, mas não explica o porquê. O motivo não é específico ao ssh, é uma questão de geração e propagação de sinal. Convido você a ler O que faz com que vários sinais sejam enviados? para mais informações.

No host remoto, existem dois processos relevantes:

  • uma instância do daemon ssh ( sshd), que está retransmitindo a entrada e a saída do programa remoto para o terminal local;
  • um shell, que está executando esse forloop.

O shell pode morrer naturalmente quando atinge o final do loop ou experimenta um erro fatal ou um sinal. A questão é: por que o shell receberia um sinal?

Se o shell remoto estiver conectado aos sshdtubos, o que acontece quando você especifica um comando na sshlinha de comando, ele morrerá como um SIGPIPE se sshdsair e o shell tentará gravar no tubo. Enquanto o shell não estiver gravando no pipe, ele não receberá um SIGPIPE. Aqui, o shell nunca está gravando nada em sua saída padrão, para que possa viver para sempre.

Você pode passar a -topção para ssh, para que ele emule um terminal no lado remoto e execute o comando especificado neste terminal. Então, se o cliente SSH desaparecer, sshdfecha a conexão e sai, destruindo o terminal. Quando o dispositivo terminal desaparece, qualquer processo em execução nele recebe um SIGHUP . Portanto, se você matar o cliente SSH (com killou fechando o terminal em que o cliente está executando), o shell remoto será SIGHUPped.

Se você passar a -topção, o SSH também retransmitirá o SIGINT . Se você pressionar Ctrl+ C, o shell remoto receberá um SIGINT.

Gilles 'SO- parar de ser mau'
fonte
Use em -ttvez de -tse o próprio ssh não tiver um tty alocado. O SSH não receberá um tty alocado se o ssh ficar em segundo plano imediatamente após a chamada, por meio de opções como -fou -n.
Miron V
3

Tente alocar um psuedo-tty com seu sshcomando.

ssh -t $ip_address 'for n in 1 2 3 4 5; do sleep 10; echo $n >>/tmp/count; done'

Quando você desconecta a sessão ssh, o processo deve terminar.

Como esse é um pseudo-tty, a inicialização do seu shell provavelmente não irá para os arquivos de configuração de origem, deixando seu comando com um ambiente muito simples. Pode ser necessário configurar um .ssh/environmentarquivo que defina variáveis ​​de ambiente, como PATH. A partir deman 1 ssh

Additionally, ssh reads ~/.ssh/environment, and adds lines of the format 
“VARNAME=value” to the environment if the file exists and users are allowed
to change their environment.  For more information, see the 
PermitUserEnvironment option in sshd_config(5).
George M
fonte
2

Como alternativa ao uso da -topção para sshfazer com que o comando remoto seja encerrado quando o sshcliente sai ou (é morto), um pipe nomeado pode ser usado para converter "EOF para SIGHUP" quando o stdin de sshdestá sendo fechado (consulte o Bug 396 - sshd processos órfãos quando nenhum pty foi alocado ).

# sample code in Bash
# press ctrl-d for EOF
ssh localhost '
TUBE=/tmp/myfifo.fifo
rm -f "$TUBE"
mkfifo "$TUBE"
#exec 3<>"$TUBE"

<"$TUBE" sleep 100 &  appPID=$!

# cf. "OpenSSH and non-blocking mode", 
# http://lists.mindrot.org/pipermail/openssh-unix-dev/2005-July/023090.html
#cat >"$TUBE"
#socat -u STDIN "PIPE:$TUBE"
dd of="$TUBE" bs=1 2>/dev/null
#while IFS="" read -r -n 1 char; do printf '%s' "$char"; done > "$TUBE"

#kill -HUP -$appPID 
kill $appPID

rm -f "$TUBE"
'
teru
fonte
1

Esta é a melhor maneira que encontrei para fazer isso. Você deseja algo no lado do servidor que tente ler stdin e, em seguida, mate o grupo de processos quando isso falhar, mas também deseje um stdin no lado do cliente que bloqueie até que o processo do lado do servidor seja concluído e não deixe processos remanescentes como <( infinito sono) pode.

ssh localhost "sleep 99 < <(cat; kill -INT 0)" <&1

Na verdade, ele não parece redirecionar o stdout para lugar nenhum, mas funciona como uma entrada de bloqueio e evita a captura de teclas.

Bug openssh relevante: https://bugzilla.mindrot.org/show_bug.cgi?id=396#c14

Eric Woodruff
fonte
0

Se você deseja desativar esse comportamento (aparentemente padrão), é necessário ativar o ssh-keep-alive no cliente ou no servidor.

Se você observar as opções ssh-keep-alive-nas páginas de manual, poderá ver que elas estão desativadas por padrão.

Nils
fonte
0

Minha resposta é baseada em teru. Eu preciso de um script auxiliar ~/helperno servidor server.ip:

#!/bin/bash
TUBE=/tmp/sshCoLab_myfifo.$$;
mkfifo "$TUBE"
( <"$TUBE" "$@" ; rm "$TUBE" ; kill -TERM 0 ) &  
cat >"$TUBE" ;
rm "$TUBE" ;
kill -TERM 0 ;

Se for chamado, por exemplo, com

ssh server.ip ~/helper echo hello from server

e executa echo hello from serverem server.ip e, em seguida, o cliente ssh será encerrado.

Se for chamado, por exemplo, com

ssh server.ip ~/helper sleep 1000 &
CHID=$!

kill -9 $CHIDirá parar o script no servidor também. Para mim, kill -INT $CHIDnão funciona, mas não sei por quê.

Na resposta do teru, o comando ssh aguardará para sempre quando o comando remoto for concluído, porque catnunca termina.

Todas as respostas com ssh -t não funcionaram para mim kill, mas apenas com Ctrl-C.

Edit: Eu descobri que isso funcionou de um novo Ubuntu 14.01 para uma antiga caixa de linux científica - mas não o contrário. Portanto, acho que não há solução geral. Esquisito.

Peter Steier
fonte