假设我们有一个 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
部分if
then
elif
else
fi
sh
语言。
它的工作是将其参数作为条件表达式进行计算。例如,如果它接收[
, a
, =
,b
和]
作为参数,或者test
, a
, =
and b
,它将返回 false/失败退出状态,因为它将其解释为“‘a’和‘b’是同一个字符串吗?”条件表达式。
[
通常用作//语句if
部分或 // 或 // 语句中的唯一命令,但它不一定是这些语句也不必调用单个命令。if
then
elif
else
fi
while
do
done
until
do
done
[
执行[ $(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 的条件表达式a
is 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
是的话yes
,if [ "$(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
和$IFS
contains :
,最终以echo
,echo
和hello
作为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
,u
或e
字符;true
最终被运行,并且它的退出状态决定了if
语句中的分支。