SSH Bash -c 命令的退出状态没有传递

3
根据man ssh之前的回答,ssh应该传播它在远程服务器上运行的任何进程的退出状态。我似乎发现了一个令人困惑的例外!"Original Answer"翻译成"最初的回答"。
$ ssh myserver exit 34 ; echo $?
34

Good...

$ ssh myserver 'exit 34' ; echo $?
34

Good...

$ ssh myserver bash -c 'exit 34' ; echo $?
0

"最初的回答":什么?!
$ ssh myserver
ubuntu@myserver $ bash -c 'exit 34' ; echo $?
34

所以问题似乎不是ssh或bash -c单独存在的问题,而是它们的组合行为与我的预期不符。

我正在设计一个脚本,在远程机器上运行需要使用在客户端计算出的参数列表。为了论证,假设如果任何参数不是远程服务器上的文件,则脚本将失败:

最初的回答:

ssh myserver bash -c '
    for arg ; do
        if [[ ! -f "$arg" ]] ; then
            exit 1
        fi
    done
' arg1 arg2 ...

如何运行类似这样的代码并有效地检查其返回状态?上面的测试似乎表明我无法做到这一点。"最初的回答"

2
作为一般规则,不要依赖于 ssh 将多个参数组合成单个命令。始终传递包含命令的单个参数,例如 ssh myserver 'bash -c "exit 34"' 而不是 ssh myserver bash -c "exit 34" - chepner
这些问题并不是“显而易见”的相同,但是正如Barmar所详细描述的那样,这个问题的根本原因与另一个问题明确提出的问题是一样的;因此,在那里给出的答案也适用于这里。 - Charles Duffy
3个回答

5
问题在于引号被丢失了。ssh只是将参数串联起来,不会重新引用它们,因此你在服务器上实际执行的命令是:
bash -c exit 34
-c选项只接受一个参数,而不是所有剩余的参数,因此它只会执行exit命令;34被忽略。
如果你这样做,你可以看到类似的效果:
ssh myserver bash -c 'echo foo'

这将只会输出一个空行,而不是foo

你可以通过给ssh提供单个参数来解决它:

ssh myserver "bash -c 'exit 34'"

或者通过加倍引号:

ssh myserver bash -c "'exit 34'"

1

如果你的问题是如何在远程运行一个命令并通过ssh的命令行传递它,而不会出现触发问题的混乱,可以使用printf '%q '来请求shell代表你执行引用操作,并构建一个字符串,然后将其传递给ssh:

printf -v cmd_str '%q ' bash -c '
    for arg ; do
        if [[ ! -f "$arg" ]] ; then
            exit 1
        fi
    done
' arg1 arg2 ...
ssh "$host" "$cmd_str"

然而,这只有在远程用户的默认 shell 也是 bash(或者如果您在本地使用了 ksh 的 printf %q 并且远程 shell 是 ksh)时才能保证正确工作。更安全的方法是通过 stdin 将脚本文本传递出去。
printf -v arg_str '%q ' arg1 arg2 ...
ssh "$host" "bash -s $arg_str" <<'EOF'
for arg; do
  if [[ ! -f "$arg" ]]; then
    exit 1
  fi
done
EOF

...我们仍然依赖于printf %q来生成正确的输出,但只用于参数而不是脚本本身。


0
尝试用引号括起来:
╰─➤  ssh server "bash -c 'exit 34' "; echo $?
34

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接