【问题标题】:Why does `if $(true) ; then ... fi` succeed?为什么 `if $(true) ;那么……fi`成功了吗?
【发布时间】:2012-02-16 11:07:12
【问题描述】:

灵感来自this question:

当条件是命令替换且命令不产生输出时,if 语句应该做什么?

注意:示例是if $(true); then ...,而不是if true ; then ...

例如,给定:

if $(true) ; then echo yes ; else echo no ; fi

我认为$(true) 应该替换为true 命令的输出,这没什么。然后它应该等同于:

if "" ; then echo yes ; else echo no ; fi

打印no,因为没有名称是空字符串的命令,或者到这个:

if ; then echo yes ; else echo no ; fi

这是一个语法错误。

但实验表明,如果命令没有输出,if 语句会根据命令的状态而不是输出将其视为真或假。

这是一个演示该行为的脚本:

#!/bin/bash

echo -n 'true:          ' ; if true          ; then echo yes ; else echo no ; fi
echo -n 'false:         ' ; if false         ; then echo yes ; else echo no ; fi
echo -n '$(echo true):  ' ; if $(echo true)  ; then echo yes ; else echo no ; fi
echo -n '$(echo false): ' ; if $(echo false) ; then echo yes ; else echo no ; fi
echo -n '$(true):       ' ; if $(true)       ; then echo yes ; else echo no ; fi
echo -n '$(false):      ' ; if $(false)      ; then echo yes ; else echo no ; fi
echo -n '"":            ' ; if ""            ; then echo yes ; else echo no ; fi
echo -n '(nothing):     ' ; if               ; then echo yes ; else echo no ; fi

这是我得到的输出(Ubuntu 11.04,bash 4.2.8):

true:          yes
false:         no
$(echo true):  yes
$(echo false): no
$(true):       yes
$(false):      no
"":            ./foo.bash: line 9: : command not found
no
./foo.bash: line 10: syntax error near unexpected token `;'
./foo.bash: line 10: `echo -n '(nothing):     ' ; if               ; then echo yes ; else echo no ; fi'

前四行的行为符合我的预期; $(true)$(false) 行令人惊讶。

进一步的实验(此处未显示)表明,如果$() 之间的命令产生输出,则其退出状态不会影响if 的行为。

我看到 bashkshzshashdash 的行为相似(但在某些情况下出现不同的错误消息)。

我在 bash 文档或 POSIX“Shell 命令语言”规范中没有看到任何解释这一点的内容。

(或者我可能遗漏了一些明显的东西。)

编辑:根据接受的答案,这是另一个行为示例:

command='' ; if $command ; then echo yes ; else echo no ; fi

或者,等效地:

command=   ; if $command ; then echo yes ; else echo no ; fi

【问题讨论】:

  • 这很有趣。无论如何,我认为你的问题有错误。您说if "" ; then echo yes ; else echo no ; fi 返回 no 因为“没有名称为空字符串的命令”,但按照同样的逻辑,它应该会失败,并出现类似“: command not found”的错误。 if "" ; then echo yes ; else echo no ; fiif "nonexistant" ; then echo yes ; else echo no ; fi 之间还有一些其他区别
  • @AaronMcDaid:我认为您的评论有误。 if "nonexistent" ; then … 产生错误消息bash: nonexistent: command not found,然后是no。相比之下,if "" ; then … 产生错误消息bash: : command not found,然后是no。这是完全相同的行为,除了 nonexistent 已被删除(即,替换为 nothing)。如果您只输入""(后跟),您会收到相同的消息。你能举一个例子,bash 说类似<empty string> 而不是只写一个空字符串吗? …(续)
  • (续)...  你能找出if "" ; then …if "nonexistent" ; then … 之间的任何实际区别吗?
  • @KeithThompson:我同意这是一个有趣的问题。鉴于此页面上提供的所有具有启发性的示例,我很惊讶没有人提供与 if "" ; then … 相同的 if "$(true)" ; then …command='' ; if "$command" ; then …(带引号)。

标签: bash ksh zsh sh


【解决方案1】:

请参阅 language spec 的第 2.9.1 节。第一部分的最后一句话是:

如果有命令名,执行将按照命令搜索和执行中的描述继续执行。如果没有命令名称,但该命令包含命令替换,则该命令应以执行的最后一个命令替换的退出状态完成。否则,该命令将以零退出状态完成。

$(true) 正在扩展为空字符串。 shell解析空字符串,发现没有给出命令,遵循上述规则。

【讨论】:

  • 太棒了!这很容易错过(我做到了)。
  • 嘿,我是对的!感谢您在规范中找到相关部分
  • 标准中是否有保证“if $(true) $(false)”将最后评估false?
  • @WilliamPursell:您问,“标准中是否保证if $(true) $(false) 将评估false 最后?”答案就在Section 2.9.1的第二句:“当需要执行给定的简单命令时……,以下扩展、赋值、重定向都应从头开始执行命令文本的末尾”(强调)。
【解决方案2】:

可能有 两个 退出代码需要考虑。首先,另外两个实验应该会有所帮助:

# if $(echo true; false) ; then echo yes ; else echo no ; fi
yes

inner 命令由于false 而退出失败。但这无关紧要,因为命令的输出是非空的,因此会执行输出 ("true") 并且其退出代码具有优先权。

# if $(echo false; true) ; then echo yes ; else echo no ; fi
no

同样,$( ) 里面的命令行是成功的,但是输出是 no,因为输出 ("false") 优先。

$( ) 中的命令的退出状态是相关的当且仅当输出为空或只有空格。如果输出为空,则没有要执行的命令列表,因此看起来 shell 将退回到 inner 命令的退出状态。

【讨论】:

  • 与其说是优先级,倒不如说是 bash 只保留一个退出码,其值为最后执行命令的退出码。 $(true)$(false) 被替换为不计入执行的空命令,因此 true 和/或 false 的退出代码仍然存在。
  • "$() 中的命令的退出状态是相关的当且仅当输出为空。"情况似乎是这样,但是为什么?您能指出任何可以从中推断出这种行为的文档吗?
  • @KeithThompson,我不知道任何此类文档。我很想将此称为“未记录”功能:-) 正如@SiegeX 所说,我认为它使用实际执行的最后一个命令的退出状态,无论该命令在什么“上下文”中执行。即在$( ) 内部或外部,最后执行的命令是什么?
  • “当且仅当输出为空时,$() 中的命令的退出状态才相关”:不完全;输出全部为空格就足够了。见William Pursell's answer
【解决方案3】:

似乎正在发生的事情是bash 保持最后执行的命令的退出状态

这可以解释为什么$(true)$(false)if 测试中具有不同的行为。它们都产生不计为执行的空命令,但它们具有不同的退出代码。

一旦您对具有输出的命令使用命令替换,$() 就会尝试将该输出作为命令执行,而 尝试的退出代码现在是用于if测试

【讨论】:

    【解决方案4】:

    当条件是命令替换且命令不产生输出时,if 语句应该做什么?

    输出无关紧要。重要的是退出代码

       if list; then list; [ elif list; then list; ] ... [ else
       list; ] fi
              The if list is executed.  If its exit status is zero,
              the then list is executed.  Otherwise, each elif list
              is executed in turn, and if its exit status is zero,
              the corresponding then list is executed and the
              command completes.  Otherwise, the else list is
              executed, if present.  The exit status is the exit
              status of the last command executed, or zero if no
              condition tested true.
    

    如果您将 $(echo true)$(echo false) 替换为其他内容,您可能会看到发生了什么:

    $ if $(echo false) ; then echo yes ; else echo no ; fi
    no
    $ if $(echo command-does-not-exist) ; then echo yes ; else echo no ; fi
    command-does-not-exist: command not found
    no
    $ 
    

    $(..) 运行命令,然后if 执行结果(在这种情况下,只是truefalsedoes-not-exist)。一个空的$() 启动一个子shell,它成功运行到完成并返回一个退出代码0

    $ if $() ; then echo yes ; else echo no ; fi
    yes
    

    Aaron 提出了一些有趣的观点:

    $ if $(echo true; false) ; then echo yes ; else echo no ; fi
    yes
    $ if $(echo 'true ; false') ; then echo yes ; else echo no ; fi
    yes
    $ if true ; false ; then echo yes ; else echo no ; fi
    no
    
    $ if $(echo false ; true) ; then echo yes ; else echo no ; fi
    no
    $ if $(echo 'false ; true') ; then echo yes ; else echo no ; fi
    no
    $ if false ; true ; then echo yes ; else echo no ; fi
    yes
    $ 
    

    似乎在执行通过$() 子shell 构造的命令时,第一个命令的退出状态很重要。

    【讨论】:

    • 如果输出为非空,$( ) 内的命令退出代码无关紧要。 if $(echo true; false) ; then echo yes ; else echo no ; fiyes。所以false 被忽略了,因为 outputed 命令(“true”)被执行了。
    • @Aaron,这是一个很酷的例子。我不得不更进一步地玩它,现在我比开始时更困惑。 :)
    • 似乎在执行通过 $() 子shell构造的命令时,第一个命令的退出状态很重要。 如果是这种情况,那么下面应该产生@ 987654339@:if $(false;echo 'true ; false') ; then echo yes ; else echo no ; fi
    • @sarnold 所以再看了一下,if $(echo 'true ; false'); then ...if true ; false; then ... 给出不同结果的原因是因为命令替换只经历了分词和全局扩展,仅此而已。因此$(echo 'true ; false') 扩展为true \; false,即true 使用两个参数调用:;false,而不是两个单独的命令
    • @SiegeX 的解释可以通过if $(echo 'ls -l false ; true') ; then … 进一步演示,它输出false: No such file;: No such filetrue: No such file(除非你有这些名称的文件),然后是no。这表明 ; 被解释为参数而不是命令分隔符。另一方面,if $(echo 'eval false ; true') ; then … 输出yes,因为eval 导致shell 将; 解释为分隔符,并执行false 然后 true。 (但不要养成使用eval的习惯;相反,你应该尽量避免。)
    猜你喜欢
    • 2013-08-10
    • 1970-01-01
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    • 2012-05-13
    • 1970-01-01
    • 2015-05-06
    相关资源
    最近更新 更多