我正在尝试自动化一个涉及通过 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)
注意:这个问题与这些看似相似的问题不同:
答案1
如中所述答案你已经有了,遥控器sh
没有执行exit 5
。只是exit
:
$ ssh test sh -x -c 'exit 5'; echo $?
+ exit
0
例如,解释了这里发生的事情这个答案:
ssh
执行远程 shell 并传递细绳对它,不是参数列表。
当我们执行ssh host sh -c 'exit 5'
:
- 本地 shell 删除单引号 (报价删除);
- 客户
ssh
端获取参数host
、sh
、-c
和exit 5
。它将它们连接成一个字符串并将其发送到远程主机; - 在远程主机上,
ssh
调用 shell 并向其传递字符串sh -c exit 5
; - 远程 shell 调用
sh
并向其传递-c
选项,exit
如下所示命令串,并且5
作为命令名称。
请注意,如果我们在 后面添加单词exit 5
,它们只会作为进一步的参数传递sh
- 不会出现与它们不被 shell 识别相关的错误:
$ ssh test sh -x -c 'exit 5' a b c; echo $?
+ exit
0
strace
确认5
不是此处给出的命令字符串的一部分sh
;这是一个论点:
$ ssh test strace -e execve sh -c 'exit 5'; echo $?
execve("/usr/bin/sh", ["sh", "-c", "exit", "5"], 0x7ffc0d744c38 /* 14 vars */) = 0
+++ exited with 0 +++
0
为了按sh -c 'command'
预期在远程主机上执行,我们还必须确保正确地向其发送引号:
$ ssh test "sh -x -c 'exit 5'"; echo $?
+ exit 5
5
为了明确引用整个远程命令与我们当前的问题无关,我们可以这样写:
$ ssh test sh -x -c "'exit 5'"; echo $?
+ exit 5
5
用反斜杠转义内部引号,而不是引用两次,也可以。
关于该命令的注释ssh host sh -c ':; exit 5'
(来自对您的问题的评论)。它的作用是:
$ ssh test sh -x -c ':; exit 5'; echo $?
+ :
5
也就是说,exit 5
是由外壳执行的,而不是由sh
.再次,sh
使用所需的代码退出:
$ ssh test sh -x -c "':; exit 5'"; echo $?
+ :
+ exit 5
5
答案2
我可以使用您使用的命令复制此问题,并且可以通过将远程命令用引号括起来来解决它。这是我的测试用例:
#!/bin/bash -x
echo 'Unquoted Test:'
ssh evil sh -x -c exit 5 && echo OK || echo FAIL
echo 'Quoted Test 1:'
ssh evil sh -x -c 'exit 5' && echo OK || echo FAIL
echo 'Quoted Test 2:'
ssh evil 'sh -x -c "exit 5"' && echo OK || echo FAIL
结果如下:
bash-[540]$ bash -x test.sh
+ echo 'Unquoted Test:'
Unquoted Test:
+ ssh evil sh -x -c exit 5
+ exit
+ echo OK
OK
+ echo 'Quoted Test 1:'
Quoted Test 1:
+ ssh evil sh -x -c 'exit 5'
+ exit
+ echo OK
OK
+ echo 'Quoted Test 2:'
Quoted Test 2:
+ ssh evil 'sh -x -c "exit 5"'
+ exit 5
+ echo FAIL
FAIL
在第一次测试和第二次测试中,似乎没有像我们期望的那样5
被传递。exit
它似乎正在消失。它不会exit
,sh
不会抱怨5: command not found
,也ssh
不会抱怨。
在第三个测试中,exit 5
在远程主机上运行的较大命令中引用了,与第二个测试中相同。这确保了 被5
传递到,并且两者都作为的选项exit
执行。第二个和第三个测试之间的区别在于,整个命令和参数集被发送到作为单个命令参数引用的远程主机。-c
sh
ssh
答案3
其他答案很好地回答了这个问题,而不是给出的例子。我的实际应用程序更加复杂,涉及一系列脚本和子流程。这是我要执行的简化示例脚本:
#!/bin/bash
sub-process-that-fails
# store and echo returncode for debug purposes
rc=$?
echo $rc
exit $rc
为了确保远程执行的 shell 实际上是 bash 而不是 dash(正如@JeffSchaller 所指出的),我尝试像这样调用脚本:
~$ ssh -t -t host /bin/bash -x /srv/scripts/run.sh ; echo $?
这导致了这个奇怪的输出:
+ sub-process-that-fails
+ rc=5
+ echo 5
5
+ exit 5
0
经过几个小时的探索,我trap 'kill 0' EXIT
注意到.bashrc
.这样做是为了在 bash 被终止时终止所有子进程。 bash 的跟踪似乎没有显示此陷阱的执行情况。我将陷阱移至包装脚本中。现在我可以看到实际执行的内容:
+ trap 'kill 0' EXIT
+ sub-process-that-fails
+ rc=5
5
+ echo 5
+ exit 5
+ kill 0
0
远程 shell 以最后一个命令的退出代码退出。是的kill 0
,并且以 0 退出。