Estou tentando automatizar um processo que envolve a execução de scripts em várias máquinas via ssh. É vital capturar tanto a saída quanto o código de retorno (para a detecção de erros).
Definir o código de saída explicitamente funciona conforme o esperado:
~$ ssh host exit 5 && echo OK || echo FAIL
FAIL
No entanto, se houver um script de shell sinalizando uma saída não limpa, o ssh sempre retornará 0 (script simulado pela execução da string):
~$ ssh host sh -c 'exit 5' && echo OK || echo FAIL
OK
Executar o mesmo script no host em um shell interativo funciona bem:
~$ sh -c 'exit 5' && echo OK || echo FAIL
FAIL
Estou confuso sobre por que isso acontece. Como posso dizer ao ssh para propagar o código de retorno do bash? Eu não posso alterar os scripts remotos.
Estou usando autenticação de chave pública, a chave privada está desbloqueada – não há necessidade de interação do usuário. Todos os sistemas são Ubuntu 18.04. As versões do aplicativo são:
OpenSSH_7.6p1 Ubuntu-4ubuntu0.1, OpenSSL 1.0.2n 7 Dec 2017
GNU bash, Version 4.4.19(1)-release (x86_64-pc-linux-gnu)
Observação: esta pergunta é diferente dessas perguntas aparentemente semelhantes:
- bash shell - saída de captura de script remoto ssh e código de saída?
- https://stackoverflow.com/questions/15390978/shell-script-ssh-command-exit-status
- https://stackoverflow.com/questions/36726995/exit-code-from-ssh-command
- https://superuser.com/questions/652729/command-executed-via-ssh-does-not-return-proper-return-code
Conforme observado na resposta que você já possui, o controle remoto
sh
não está em execuçãoexit 5
. Apenasexit
:O que está acontecendo aqui é explicado, por exemplo, nesta resposta :
ssh
executa um shell remoto e passa uma string para ele, não uma lista de argumentos.Quando executamos
ssh host sh -c 'exit 5'
:ssh
cliente obtém os argumentoshost
,sh
,-c
eexit 5
. Ele os concatena em uma string e a envia para o host remoto;ssh
invoca um shell e passa a stringsh -c exit 5
;sh
e passa a-c
opção,exit
como a string de comando e5
como o nome do comando .Observe que, se adicionarmos palavras após
exit 5
, elas serão apenas passadassh
como argumentos adicionais - nenhum erro relacionado a elas não serem reconhecidas pelo shell:strace
confirma que5
não faz parte da string de comando dada ash
, aqui; é um argumento:Para executar
sh -c 'command'
em um host remoto como pretendido, temos que enviar corretamente as cotações também:Para deixar claro que citar todo o comando remoto não é relevante para nosso problema atual, poderíamos apenas escrever:
Escapar as aspas internas com barras invertidas, em vez de citar duas vezes, também funcionaria.
Uma nota sobre o comando
ssh host sh -c ':; exit 5'
(dos comentários à sua pergunta). O que ele faz é:Ou seja,
exit 5
é executado pelo shell externo, não pelosh
. Novamente, para deixarsh
sair com o código desejado:Posso duplicar isso usando o comando que você usou e posso resolvê-lo colocando o comando remoto entre aspas. Aqui estão meus casos de teste:
Aqui estão os resultados:
No primeiro teste e no segundo teste, parece que
5
não está sendo passadoexit
como esperávamos. Apenas parece estar desaparecendo. Não vaiexit
,sh
não está reclamando5: command not found
essh
não está reclamando disso.No terceiro teste,
exit 5
é citado dentro do comando maior a ser executado no host remoto, igual ao segundo teste. Isso garante que o5
seja passado paraexit
, e ambos sejam executados como-c
opção parash
. A diferença entre o segundo e o terceiro teste é que todo o conjunto de comandos e argumentos é enviado para o host remoto citado como um único argumento de comando parassh
.As outras respostas são boas para responder à pergunta em vez dos exemplos dados. Minha aplicação no mundo real é mais complicada e envolve uma série de scripts e subprocessos. Aqui está um script de exemplo resumido que eu quero executar:
Tentando ter certeza de que o shell executado remotamente era realmente bash e não dash (como apontado por @JeffSchaller), tentei chamar o script assim:
O que levou a essa saída estranha:
Depois de horas bisbilhotando, notei que havia um
trap 'kill 0' EXIT
conjunto no arquivo.bashrc
. Isso é feito para matar todos os subprocessos caso o bash seja morto. o rastreamento do bash não parece exibir a execução desta armadilha. Mudei a armadilha para o script wrapper. Agora posso ver o que realmente é executado:O shell remoto sai com o código de saída do último comando. É
kill 0
e sai com 0.