POSIX sh 条件中逻辑或评估的意外结果

POSIX sh 条件中逻辑或评估的意外结果

假设我们有一个 if 语句如下:

if [ $(false) ]; then
    echo "?"
fi

然后 ”?”不打印(条件为假)。然而,在下面的情况下,“?!”打印出来了,为什么?

if [ $(false) -o $(false) ]; then
    echo "?!"
fi

答案1

$(false)计算结果不为 false,它会生成一个空字符串。因为没有引用,

if [ $(false) ]; then

评估为

if [ ]; then

这是假的,因为[空表达式是假的。

if [ $(false) -o $(false) ]; then

评估为

if [ -o ]; then

没有使用-o运算符,它计算-o为具有单个字符串的表达式;[这样的表达式为真,因此then该语句的部分if运行。

POSIX 规范test, 尤其:

用于确定运算符优先级和应生成的返回值的算法基于要测试的参数数量。 (但是,当使用“[...]”形式时,<right-square-bracket>最后一个参数不应计入该算法。)

在下面的列表中,$1、$2、$3 和 $4 代表要测试的参数:

0 个参数:
退出 false (1)。
1 个参数:
如果 $1 不为 null,则退出 true (0);否则,退出 false。

test仅当至少给出两个参数时才考虑运算符。

如果您想使用命令的退出状态作为条件,请勿将其放在命令替换或中[ ]

if false; then

if false || false; then

还要注意的是test-a和运算符-o已被弃用且不可靠;你应该使用 shell 的&&||运算符,例如

if [ "$a" = b ] || [ "$a" = c ]; then

答案2

我不会重复罚款答案中提出的观点通过@StephenKitt@ilkkachu这里,只关注写的含义if [ $(cmd) ]; then...

首先请注意,这[不是POSIXsh运算符,它是一个单独的实用程序,也称为test,这里用在语法中的////语句if部分ifthenelifelsefish语言

它的工作是将其参数作为条件表达式进行计算。例如,如果它接收[, a, =,b]作为参数,或者test, a, =and b,它将返回 false/失败退出状态,因为它将其解释为“‘a’和‘b’是同一个字符串吗?”条件表达式。

[通常用作//语句if部分或 // 或 // 语句中的唯一命令,但它不一定是这些语句也不必调用单个命令。ifthenelifelsefiwhiledodoneuntildodone[

执行[ $(cmd) ]意味着[使用[、扩展结果$(cmd)]作为单独的参数来调用命令。然后[将这些参数解释为条件表达式,并根据结果返回 true 或 false(如果无法理解表达式,则返回 false)。

在 POSIX sh 中,$(cmd)扩展为剥离所有尾随换行符的标准输出cmd,并且由于它没有被引用并且在列表上下文中,因此受到 IFS 分割,然后进行通配(如果该输出中存在 NUL 字符,则行为未指定) 。

所以if [ $(cmd) ]or 并test $(cmd)没有任何意义。

这就像问一个问题:“一旦 split+globbed , 的输出是否cmd构成结果为 true 的有效条件表达式?”

例如,如果cmd输出a,*$IFS恰好包含,并且当前工作目录包含两个文件,一个名为=,一个名为b[ $(cmd) ]则将[[, a, =, b,]作为参数进行调用。幸运的是,这恰好是一个有效的条件表达式,但返回 false 的条件表达式ais not b

$ cd "$(mktemp -d)"
$ touch = b
$ cmd() { echo 'a,*'; }
$ IFS=,
$ set -o xtrace
$ if [ $(cmd) ]; then echo yes; else echo no; fi
++ cmd
++ echo 'a,*'
+ '[' a = b ']'
+ echo no
no

现在,[ "$(cmd)" ]更有意义了。引用命令替换不会阻止删除尾随换行符,在cmd输出 NUL 时仍然未指定,但它会阻止 split+glob 和空删除。因此[将始终传递 3 个参数: 、 、删除尾随换行符[的输出和。和旁边有一个参数,当且仅当该参数不是空字符串时返回 true,它与 相同。cmd][][[ -n "$(cmd)" ]

所以这就像是在说:“确实cmd输出至少一个不是换行符(或 NUL)的字符”。对于输出文本的命令(保证文本由行组成且不包含 NUL),即“是否cmd输出至少一个非空行?”

除了短输出之外,它以一种低效的方式做到这一点,因为我们最终读取整个输出并将其存储在内存中,然后将其传递给[

if cmd | grep -q .; then...

会更有效,因为grep会退出真的一旦它找到包含至少一个字符的行。

如果cmd是的话yesif [ "$(cmd)" ]最终会使外壳崩溃内存不足错误,而if cmd | grep -q .会立即输出 yes(并且yes将通过 SIGPIPE 信号终止)。

要测试是否cmd成功,无论输出什么或不输出什么,您只需执行以下操作:

if cmd; then
  echo cmd succeeded
else
  echo cmd failed
fi

if $(cmd); then是另一个毫无意义的代码示例。但cmd存在的例子有一点不同false

再次,$(cmd)被剥离尾随换行符,受 split+glob 影响。但这一次,生成的单词不会传递给[/test实用程序,而是将这些单词中的第一个单词(如果有)视为要执行的命令,并将所有单词作为参数传递给它。

因此,如果cmd输出echo:hello:world$IFScontains :,最终以echo,echohello作为world参数运行,并假设echo设法成功写入"hello world\n"其标准输出,它将返回 true/成功,因此该then部分将运行。

现在,如果cmd不产生输出或仅产生换行符和 IFS 空白字符,那么$(cmd)将根本不产生任何单词,因此不会运行另一个命令,然后在这一点上,它cmd本身的退出状态将很重要该if声明。

在:

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

no是输出,因为由于false没有产生输出,所以没有运行任何命令,并且退出状态false决定该else部分是否运行。

在:

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

并假设$IFS不包含t, r,ue字符;true最终被运行,并且它的退出状态决定了if语句中的分支。

相关内容