在Bash Shell中,感叹号用于测试变量是否为真。

8

我知道在shell中,感叹号可以反转条件的结果。这里我想使用它来测试变量是真还是假。

#! /bin/bash
bat=false
if [ ! $bar ]; then
    echo 'bar is false'
else
   echo 'bar is true'
fi

我原本期望的是'bar is false',但结果却相反了。然后我使用"$bar" == "false",这是正确的。

那么感叹号的使用技巧是什么?它只能在测试文件时才能翻转结果吗?


2
Shell并没有布尔值,只有字符串变量。truefalse都只是字符串,与kitten这样的任何其他单词本质上没有区别。 - that other guy
3个回答

16

tl;dr

[ ! $bar ]$bar视为字符串,而任何非空字符串都被视为"true" - 包括字面上的false;换句话说:[ ! 'true' ][ ! 'false' ] 评估为"false",因为在这两种情况下,操作数都是非空字符串,而!对结果进行否定。

因此,您必须使用字符串比较:

bar=false
if [ ! "$bar" = 'true' ]; then  # same as: [ "$bar" != 'true' ]
    echo 'bar is false'
else
    echo 'bar is true'
fi

在Bash中,你也可以使用[[ ! $bar == 'true' ]][[ $bar != 'true' ]];除非你需要保持POSIX兼容性,我建议使用[[ ... ]]
[ ... ][[ ... ]](特定于Bash)的上下文中,默认情况下变量值是字符串,而且通常类似POSIX的shell没有显式的布尔数据类型
一元运算符!将其操作数隐式解释为布尔值,在布尔上下文中,字符串被解释如下:
  • 只有空字符串被认为是"false"(退出码1(!))
  • 任何非空字符串 - 包括字面量false - 被认为是"true"(退出码0(!))
因此,! $bar计算结果为"false",因为$bar - 包含非空字符串'false' - 计算结果为"true",而!将其取反。

!也可以在条件语句之外直接用来否定命令的执行结果(包括[)。
由于falsetrue也作为命令名称存在(通常作为shell内置命令),你可以按照以下方式操作,但请注意,这只适用于变量值为字面值falsetrue的情况:

bar=false
if ! "$bar"; then
   echo 'bar is false'
else
   echo 'bar is true'
fi

背景信息

类似POSIX的shell只有两种基本(标量)数据类型:

  • 字符串(默认情况下):

    • 在条件语句 [ ... ][[ ... ]] 中,运算符 =(在 Bash 中为 ==)、<<=>>= 用于进行字符串比较。
  • 整数:

    • 在条件语句 [ ... ][[ ... ]] 中,必须使用不同的算术运算符(-eqltlegtge)进行数值比较。
    • 或者,在算术扩展($(( ... )))中,==<<=>>= 具有它们通常的数值意义。
      在 Bash 中,您还可以使用 (( ... )) 作为算术条件语句。
注意:根据POSIX标准,你无法将变量“类型”声明为整数(因为没有办法“声明”一个shell变量),所以在使用算术运算符进行比较时,它只会被隐式地视为整数,在算术上下文中也是如此。
然而,在Bash中,你可以使用“declare -i” / “local -i”声明整数变量,但在条件语句 / 算术上下文中,仍然是操作符 / 算术上下文的选择决定该值是被视为字符串还是整数。
在shell中,布尔值被隐式地表示为退出码,这是对通常的布尔到整数映射的逆转:
- "true"映射到退出码0(!) - "false"被表示为退出码1(!)或任何其他非零退出码。

非常感谢。现在我的困惑得到了解决。另一个快速的问题是,$bar周围的双引号是否是必需的?我刚刚尝试了if [ $bar],也像预期的那样工作。 - nathan
@nathan:简而言之,为了安全起见,请使用双引号。有些情况下你不一定非得这么做,比如这里,但规则并不容易记住。每当你想要一个变量的值按原样使用时,使用双引号是安全的。欲了解更多信息,请参见此问题 - mklement0

3
请参阅POSIX规范中test的详细内容,了解[test实用程序的可移植使用方法。
如果您使用[ ! $bar ],那么您就很危险。
  • 如果$bar是空字符串,则它会退化为测试的单参数形式,该参数为!,并成功退出,因为!不是空字符串。

  • 如果$bar不是空字符串,则参数是!$bar的值,这将建立非空字符串是非空的事实,然后反转条件,因此以失败状态退出。

通常应编写[ ! "$bar" ],这样即使$bar为空也能正确工作。您可以(有人可能会认为应该)使用显式测试:
  • [ -z "$bar" ]以测试空(零长度)字符串,以及
  • [ -n "$bar" ]以测试非空字符串
但是引号是必需的,以避免混淆。非空(non-empty)和零长(zero-length)是助记词。
$ bar=
$ [ ! $bar ] || echo Test failed
$ bar=xyz
$ [ ! $bar ] || echo Test failed
Test failed
$ [ ! "$bar" ] || echo Test failed
Test failed
$ bar=
$ [ ! "$bar" ] || echo Test failed
$ [ -n "$bar" ] || echo Test failed
Test failed
$ [ -z "$bar" ] || echo Test failed
$ 

有些人更喜欢使用Bash(Korn shell,...)运算符[[,其行为不同。您可以尝试对此代码主题进行变化:

set -x
bar=
[ ! $bar ] || echo Test failed
bar=xyz
[ ! $bar ] || echo Test failed
[ ! "$bar" ] || echo Test failed
bar=
[ ! "$bar" ] || echo Test failed
[ -n "$bar" ] || echo Test failed
[ -z "$bar" ] || echo Test failed
bar=xyz
[ -n "$bar" ] || echo Test failed
[ -z "$bar" ] || echo Test failed

bar=
[[ ! $bar ]] || echo Test failed
bar=xyz
[[ ! $bar ]] || echo Test failed
[[ ! "$bar" ]] || echo Test failed
bar=
[[ ! "$bar" ]] || echo Test failed
[[ -n "$bar" ]] || echo Test failed
[[ -z "$bar" ]] || echo Test failed
bar=xyz
[[ -n "$bar" ]] || echo Test failed
[[ -z "$bar" ]] || echo Test failed
bar=
[[ -n $bar ]] || echo Test failed
[[ -z $bar ]] || echo Test failed
bar=xyz
[[ -n $bar ]] || echo Test failed
[[ -z $bar ]] || echo Test failed
set +x  # Unnecessary if you save this in a script file and don't dot (source) it

它(可能)会帮助你理解发生了什么。但输出内容相当冗长。


2

if 语句用于测试命令的结果,不能直接测试变量的值。在这种情况下,您需要与 test/[ 命令一起使用,这是一个普通的命令,可以在任何地方使用。

[ -f ./foo.txt ] && echo "foo.txt exists"

或等价于
test -f ./foo.txt && echo "foo.txt exists"

两种方法都可以测试路径./foo.txt下的文件是否存在。

根据你所说的“真实”,你可以使用test[ shell内置命令来确定变量是否等于另一个变量,以及用于测试目录或文件是否存在等其他功能。

您可以使用if [ -z "$variable" ]; then ...; fi来检查变量是否为空字符串。

如果您想检查变量是否被设置为任何值,甚至是空值,您可以使用参数扩展[ -z "${var+x}" ]。只有在var未定义时,它才评估为true(意味着具有退出状态零)。

如果您想将变量与固定字符串进行比较,则可以使用[ "$var" = "some-string" ]或使用case

case "$var" in
   some-string)
     dostuff
   ;;
esac

请注意,您可以使用 !test/[ 的结果进行否定。
if ! [ -z "$var" ]; then
    do-something
fi

对条件取反。

但是,在test/[中,!也可以作为参数使用。

if [ ! -z "$var" ]; then
    do-something
fi

意思是相同的。


感谢详细的解释。我有一个关于 "It doesn't make sense as an argument to test or [." 的问题。我确实看到 "if [ ! -f regularfile ]; then" 起作用了。这是因为 -f 运算符吗? - nathan
@nathan:在 [ … ] / [[ … ]] 中或作为 test 操作数时,! 是完全有效的。您只需要了解它的操作数如何被评估: $bar 不是按您期望的方式进行评估的:它是一个 _字符串_,并且由于它是一个 非空 字符串 - 无论其 内容 如何 - 它被认为是“true”。 - mklement0
@Nathan,我之前说错了。!test命令的一个有效参数。当且仅当"$somestring"扩展为非空字符串时,test "$somestring"将退出0。test ! "$something"则相反。话虽如此,通常更典型的是使用test -z "$something"test -n "$something"来测试是否为空,因为这可以处理$something是像-z这样的奇怪值的边缘情况。 - Greg Nisbet
@GregoryNisbet 非常感谢。 - nathan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接