我正在尝试自动化一个过程,该过程涉及通过 ssh 在各种机器上运行脚本。捕获输出和返回代码(用于检测错误)至关重要。
明确设置退出代码按预期工作:
~$ ssh host exit 5 && echo OK || echo FAIL
FAIL
但是,如果有一个 shell 脚本发出不干净退出的信号,ssh 总是返回 0(由字符串执行模拟的脚本):
~$ ssh host sh -c 'exit 5' && echo OK || echo FAIL
OK
在交互式 shell 中在主机上运行相同的脚本就可以了:
~$ sh -c 'exit 5' && echo OK || echo FAIL
FAIL
我很困惑为什么会发生这种情况。如何告诉 ssh 传播 bash 的返回码?我可能不会更改远程脚本。
我正在使用公钥身份验证,私钥已解锁 - 无需用户交互。所有系统都是 Ubuntu 18.04。应用程序版本是:
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)
注意:这个问题与这些看似相似的问题不同:
正如您已经拥有的答案
sh
中所述,遥控器未执行exit 5
。只是exit
:例如,在这个答案中解释了这里发生的事情:
ssh
执行远程 shell 并向其传递一个字符串,而不是参数列表。当我们执行
ssh host sh -c 'exit 5'
:ssh
客户端获取参数、host
、sh
和-c
。exit 5
它将它们连接成一个字符串并将其发送到远程主机;ssh
调用 shell 并将字符串传递给它sh -c exit 5
;sh
并将-c
选项exit
作为命令字符串和命令名称5
传递给它。请注意,如果我们在 之后添加单词
exit 5
,它们只是sh
作为进一步的参数传递给它们 - 没有与它们相关的错误不被 shell 识别:strace
确认这5
不是给定的命令字符串的一部分sh
,这里;这是一个论点:为了
sh -c 'command'
按预期在远程主机上执行,我们必须确保也正确地发送引号:为了明确引用整个远程命令与我们当前的问题无关,我们可以写:
用反斜杠转义内部引号,而不是引用两次,也可以。
关于命令
ssh host sh -c ':; exit 5'
的注释(从评论到您的问题)。它的作用是:也就是说,
exit 5
由外壳执行,而不是由sh
. 再次,让sh
退出所需的代码:我可以使用您使用的命令来复制它,并且可以通过将远程命令用引号括起来来解决它。这是我的测试用例:
结果如下:
在第一次测试和第二次测试中,似乎没有像我们预期的那样
5
传递给它。exit
它似乎正在消失。它不会exit
,sh
不会抱怨5: command not found
,ssh
也不会抱怨。在第三个测试中,
exit 5
引用在远程主机上运行的较大命令,与第二个测试相同。这确保了5
传递给exit
,并且两者都作为 的-c
选项执行sh
。第二个和第三个测试之间的区别在于,整个命令和参数集被发送到作为单个命令参数引用的远程主机ssh
。其他答案很好地代替了给出的示例来回答问题。我的实际应用程序更复杂,涉及一系列脚本和子流程。这是我要执行的简化示例脚本:
为了确保远程执行的 shell 实际上是 bash 而不是破折号(正如@JeffSchaller 所指出的那样),我尝试像这样调用脚本:
这导致了这个奇怪的输出:
经过几个小时的闲逛,我注意到
trap 'kill 0' EXIT
..bashrc
这样做是为了在 bash 被杀死的情况下杀死所有子进程。bash 的跟踪似乎没有显示此陷阱的执行情况。我将陷阱移到了包装脚本中。现在我可以看到实际执行了什么:远程 shell 以最后一个命令的退出代码退出。它是
kill 0
,它以 0 退出。