通过 ssh 执行的 bash 脚本返回不正确的退出代码 0

Her*_*ann 7 shell ssh exit-status

我正在尝试自动化一个过程,该过程涉及通过 ssh 在各种机器上运行脚本。捕获输出和返回代码(用于检测错误)至关重要。

设置退出代码按预期工作:

~$ ssh host exit 5 && echo OK || echo FAIL
FAIL
Run Code Online (Sandbox Code Playgroud)

但是,如果有 shell 脚本发出不干净的退出信号,ssh 总是返回 0(通过字符串执行模拟的脚本):

~$ ssh host sh -c 'exit 5' && echo OK || echo FAIL
OK
Run Code Online (Sandbox Code Playgroud)

在交互式 shell 中的主机上运行完全相同的脚本工作正常:

~$ sh -c 'exit 5' && echo OK || echo FAIL
FAIL
Run Code Online (Sandbox Code Playgroud)

我很困惑为什么会发生这种情况。如何告诉 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)

注意:这个问题与这些看似相似的问题不同:

Tim*_*edy 7

我可以使用您使用的命令复制它,并且我可以通过将远程命令括在引号中来解决它。这是我的测试用例:

#!/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
Run Code Online (Sandbox Code Playgroud)

结果如下:

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
Run Code Online (Sandbox Code Playgroud)

在第一次测试和第二次测试中,似乎5没有exit像我们期望的那样传递给它。它似乎正在消失。它不会exitsh不会抱怨5: command not foundssh也不会抱怨。

在第三个测试中,exit 5在更大的命令中引用以在远程主机上运行,​​与第二个测试相同。这可确保5传递给exit,并且两者都作为-c选项执行sh。第二个和第三个测试之间的区别在于,整个命令和参数集被发送到作为单个命令参数引用到的远程主机ssh


fra*_*san 7

正如您已经拥有的答案所述,遥控器sh没有执行exit 5. 只是exit

$ ssh test sh -x -c 'exit 5'; echo $?
+ exit
0
Run Code Online (Sandbox Code Playgroud)

例如,在这个答案中解释了这里发生的事情:

ssh执行一个远程 shell并向它传递一个字符串而不是一个参数列表。

当我们执行ssh host sh -c 'exit 5'

  1. 本地shell去除单引号(quote去除);
  2. ssh客户端获取的参数hostsh-c,和exit 5。它将它们连接成一个字符串并将其发送到远程主机;
  3. 在远程主机上,ssh调用一个 shell 并将字符串传递给它sh -c exit 5
  4. 远程外壳所调用sh并将其传递给-c选项,exit作为命令串,并5作为命令名

请注意,如果我们在 之后添加单词exit 5,它们只是sh作为进一步的参数传递给- 没有与 shell 无法识别它们相关的错误:

$ ssh test sh -x -c 'exit 5' a b c; echo $?
+ exit
0
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

为了sh -c 'command'按预期在远程主机上执行,我们还必须确保正确地将引号发送给它:

$ ssh test "sh -x -c 'exit 5'"; echo $?
+ exit 5
5
Run Code Online (Sandbox Code Playgroud)

为了明确引用整个远程命令与我们当前的问题无关,我们可以这样写:

$ ssh test sh -x -c "'exit 5'"; echo $?
+ exit 5
5
Run Code Online (Sandbox Code Playgroud)

用反斜杠转义内部引号,而不是引用两次,也可以。


关于命令ssh host sh -c ':; exit 5'的注释(从评论到您的问题)。它的作用是:

$ ssh test sh -x -c ':; exit 5'; echo $?
+ :
5
Run Code Online (Sandbox Code Playgroud)

也就是说,exit 5由外壳程序执行,而不是由sh. 再次,让sh退出所需的代码:

$ ssh test sh -x -c "':; exit 5'"; echo $?
+ :
+ exit 5
5
Run Code Online (Sandbox Code Playgroud)