Bash 为什么eval会退出subshell mid&&;用set-e?

Bash 为什么eval会退出subshell mid&&;用set-e?,bash,shell,eval,exit,subshell,Bash,Shell,Eval,Exit,Subshell,为什么bash在子shell中使用复合命令实现了我在这里所期望的功能: $ bash -x -c 'set -e; (false && true; echo hi); echo here' + set -e + false + echo hi hi + echo here here 但不是像我在这里期望的那样: $ bash -x -c 'set -e; (eval "false && true"; echo hi); echo here' + set -e +

为什么bash在子shell中使用复合命令实现了我在这里所期望的功能:

$ bash -x -c 'set -e; (false && true; echo hi); echo here'
+ set -e
+ false
+ echo hi
hi
+ echo here
here
但不是像我在这里期望的那样:

$ bash -x -c 'set -e; (eval "false && true"; echo hi); echo here'
+ set -e
+ eval 'false && true'
++ false
基本上,区别在于“eval”(评估复合命令)和仅执行复合命令之间的区别。当shell执行复合命令时,复合命令中失败的非终端命令不会导致整个复合命令失败,它们只是终止命令。但是,当eval运行component命令并且任何非终端子命令以错误终止该命令时,eval将以错误终止该命令

我想我需要这样格式化我的eval语句:

eval "false && true" || :
因此eval命令不会带着错误退出我的子shell,因为它的工作方式与我预期的一样:

$ bash -x -c 'set -e; (eval "false && true" || :; echo hi); echo here'
+ set -e
+ false
+ echo hi
hi
+ echo here
here
我的问题是我写了一个函数:

function execute() {
    local command="$1"
    local remote="$2"
    if [ ! -z "$remote" ]; then
        $SSH $remote "$command" || :
    else
        eval "$command" || :
    fi
}
我正在脚本中使用
set-e
。在这个函数中,ssh也会出现同样的问题——如果ssh脚本中的最后一个命令是提前终止的复合命令,则整个命令将以错误终止。我希望这样的命令的行为就像它们在本地执行一样-早期终止复合命令不应导致ssh或eval返回1,从而使整个命令失败。如果我在eval语句或ssh语句的末尾加上
|:
,那么所有这些命令都会成功,即使它们不应该成功,因为eval'd或ssh'd命令中的最后一个命令失败了


任何想法都将不胜感激。

eval
将自己的命令和退出代码计算在内


由于
eval“false&&true”
返回一个1的退出代码,它会触发
set-e

我还应该提到
set-e
非常容易出错;请参阅,以获取大量示例。因此,最好的解决方案可能是省去它,编写自己的逻辑来检测错误并中止


那太离谱了

这里的问题是
eval“false&&true”
是一个命令,其计算结果为false(非零),因此
set-e
在该命令运行后中止

如果改为运行
eval“false&&true;true”
,则不会看到此行为,因为
eval
的计算结果为true(零)。(注意,尽管
eval
确实实现了
set-e
行为,但它遵守了
false&&true
不中止的规则。)

顺便说一句,这实际上并不特定于
eval
。出于同样的原因,子shell将给出相同的结果:

$ bash -x -c 'set -e; (false && true); echo here'
+ set -e
+ false

对于您的问题,最简单的修复方法可能就是在达到终点时运行额外的
true

$SSH $remote "set -e; $command; true"
eval "$command; true"

您给出的示例显示,
false
是执行的最后一项,但在我给出的第一个示例中,我在这里的最后一个
echo上显示了相同的代码没有释放
-在
false和&true
之后还有另一条语句。这是奇怪的部分-如果子shell中的最后一条语句是提前终止的复合语句,那么整个子shell将返回复合语句中失败组件的值,而不是简单地不设置错误状态,如果复合语句是在shell的根上下文中执行的,情况也会如此。@JohnCalcote:Re:您的最后一句话:对不起,但是您对“shell的根上下文”的行为是错误的。尝试运行
false&&true;echo$?
;您将看到一个非零值,因为
false
。(您可能会将其与
foo | bar
之类的内容混淆,其中相关的是
bar
的返回状态。但是在
foo&&bar
中,如果
foo
返回false,则
bar
将永远不会运行,因此
foo
的退出状态将继续存在。)对不起,但我坐在这里看着它以我解释的方式发生(除非——我不否认这种可能性——我误解了正在发生的事情)。请执行我在原始帖子中的第一个示例-您将看到
hi
这里都有回声。然后移除
;回显子shell的hi
部分,然后再次运行它-shell在子shell中的
false
之后立即卸载(由于
set-e
)。你永远不会在这里看到
,就像你在子shell中保留的
echo hi
部分一样。顺便说一句,我重新编写了原始帖子,用“复合命令”替换了“管道”-谢谢你。谢谢!我知道你的解决方案是正确的我理解你的意思,但通常像
false&&true
这样的语句不会导致
set-e
跳出shell-它是这样记录的-当子命令提前失败复合命令时,
set-e
不会导致shell终止。对于
eval
和子shell,情况并非如此。复合命令中失败的子命令似乎确实会导致复合命令向shell返回非零值,但
set-e
通常会忽略该命令的结果,而
eval
或子shell则不会。这只是一个从未被考虑过的角落案例。是的,是这样的,有eval和subshell。任何不在条件语句中的失败命令都会导致shell以
set-e
退出,而这不会随eval或
(…)
而改变。退出代码
false
在这两种情况下都不会导致外壳退出。执行此操作的是
eval
(…)
的退出代码,因为它们是命令,而不是条件语句的一部分。已经被推荐;同样值得一读。(缩写:
-e
有太多的小案例和警告,其整体价值值得商榷,许多freenode#bash Greybeard建议不要使用它)。谢谢Charles-这是我见过的最有帮助的回答,我只能投赞成票,不能将其作为答案