我有以下简单的 shell 命令,我预计它会失败,而且它在我的本地也失败了:
$ DIR=$(false) && echo ok || echo fail
fail
$ sh -c 'DIR=$(false) && echo ok || echo fail'
fail
但是当我通过 ssh 传递此命令时,它不再按预期工作:
$ ssh user@host sh -c 'DIR=$(false) && echo ok || echo fail'
ok
所以我不太清楚问题出在哪里。我已经使用撇号来避免变量扩展过早。
发生了什么事,以及如何根据变量赋值返回的退出代码使命令替换正常工作?
我在以下示例中发现了另一个异常:
$ ssh user@host sh -c 'echo 1; echo 2;'
2
即仅打印 2,而不是同时打印 1 和 2。
答案1
我认为问题在于三涉及的炮弹:
- 处理命令的本地 shell
ssh
; ssh
在目标主机中调用来处理命令的远程shell ;sh -c
远程命令中调用的附加 shell 。
引文有两个作用:
- 它们停止本地 shell 评估本地 shell 中的任何变量 (1);
- 他们确保遥控器只有一个参数
sh -c
。
因此ssh
可以看到四个参数:user@host
、sh
和-c
所需的命令。但是,引号被剥夺在构建第四个参数时,当远程 shell (2) 收到其参数时,它会自我解释$(false)
,并在调用子 shell (3) 之前在其自己的环境中设置完成代码。
因此,附加 shell (3) 看到的DIR= && echo ok || echo fail
是DIR=
一个完全有效、无错误的命令,因此产生ok
分支。
您可以在以下位置看到相同的效果:
sh -c 'sh -c "DIR=$(false) && echo ok || echo not"'
或者:
sh -c "sh -c 'DIR=$(false) && echo ok || echo not'"
\
在这两种情况下,如果你在 之前放置$
,它都可以“正常”工作,因为这会阻止第二个 shell 扩展$(false)
。我使用双引号使机制更清晰 - 如果你喜欢,可以采用相当曲折的方式:
sh -c 'sh -c '\''DIR=$(false) && echo ok || echo not'\'
Richard 的示例有效,因为所有扩展都在单个远程 shell 中完成,并且首字母false;
对后续命令没有影响。这与他的链接中讨论的主题无关。
答案2
这应该可以按预期工作:
ssh user@host 'DIR=$(false) && echo ok || echo fail'
看起来,当按上述方式调用时,如果 shell 管道中的第一个语句是变量赋值,它将不起作用。
奇怪的是,这也按预期工作:
ssh user@host sh -c 'false; DIR=$(false) && echo ok || echo fail'
我不完全确定这里发生了什么,但它可能与这个问题有关: https://unix.stackexchange.com/questions/126938/why-is-setting-a-variable-before-a-command-legal-in-bash