为什么bash中的"if [ false ];"返回true而不是false?

256
为什么以下输出为True
#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

即使条件表明相反,这将始终输出True。如果我删除方括号[],它就可以工作,但我不明白为什么。


4
这是一些帮助你入门的东西。这里有相关内容。 - keyser
17
顺便说一下,以 #!/bin/sh 开头的脚本不是 Bash 脚本,而是 POSIX sh 脚本。即使你的系统上提供的 POSIX sh 解释器是由 Bash 提供的,它也会关闭一些扩展功能。如果你想编写 Bash 脚本,请使用 #!/bin/bash 或其适用于本地环境的等效命令(例如使用 #!/usr/bin/env bash 选择 PATH 中的第一个 Bash 解释器)。 - Charles Duffy
这也会影响到 [[ false ]] - CMCDragonkai
6个回答

305

您正在运行带有参数“false”的[(也称为test)命令,而不是运行false命令。由于"false"是一个非空字符串,因此test命令始终成功。要实际运行命令,请删除[命令。

if false; then
   echo "True"
else
   echo "False"
fi

5
我对“false”作为命令或字符串的概念感到有些奇怪,但我想这样做是可行的。谢谢。 - tenmiles
63
bash 没有布尔数据类型,因此没有代表 true 和 false 的关键字。if 语句仅检查您提供的命令是否成功或失败。test 命令接受一个表达式,并在表达式为 true 时成功;非空字符串是一个评估为真的表达式,就像大多数其他编程语言一样。false 是一个始终失败的命令。(类比地,true 是一个始终成功的命令。) - chepner
4
if [[ false ]]; 返回值也是true。if [[ 0 ]]; 同样也是true。而 if [[ /usr/bin/false ]]; 也不会跳过代码块。为什么false或0会被解释成非零的值呢?这真是让人沮丧。如果有关系的话,我的操作系统是OS X 10.8,Bash版本是3。 - jww
10
不要将 false 错误地视为布尔常量,也不要将 0 视为整数字面值;它们都只是普通的字符串。对于任何不以连字符开头的字符串 x[[ x ]] 等同于 [[ -n x ]]。在任何情况下,bash 都没有任何布尔常量。看起来像整数的字符串会在 (( ... )) 中或在 [[ ... ]] 中使用 -eq-lt 运算符时被视为整数处理。 - chepner
3
false是一个命令,无条件地产生退出状态1。 - chepner
显示剩余7条评论

125

Bash快速布尔入门

if语句将命令作为参数&&, ||等也是如此)。命令的整数结果代码被解释为布尔值(0/空=true,1/其他=false)。

test语句将运算符和操作数作为参数,并以与if相同的格式返回结果代码。一个test语句的别名是[,通常与if一起使用以执行更复杂的比较。

truefalse语句不会执行任何操作并返回一个结果代码(分别为0和1)。因此,它们可以在Bash中用作布尔字面量。但如果将这些语句放在被解释为字符串的位置,就会遇到问题。在您的情况下:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"
所以:
if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

这类似于执行像echo '$foo'echo "$foo"之类的操作。

使用test语句时,结果取决于所使用的运算符。

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

简而言之,如果你只想对某个东西进行测试,看它是否通过或失败(即“真”或“假”),那么在if&&等语句中传递一个命令,不需要使用括号。对于复杂的比较,使用带有适当运算符的括号。

是的,我知道在Bash中没有本地布尔类型,而if[true在技术上是“命令”,而不是“语句”;这只是一个非常基本的、功能性的解释。


4
如果你想在代码中使用1/0来表示True/False,这个代码块 if (( $x )); then echo "Hello"; fi 会在 x=1 的情况下输出消息,但是在 x=0x=(未定义)的情况下不会输出。 - Jonathan H

7
我发现可以通过运行类似以下的代码实现基本逻辑:
A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C

9
可以这样做,但这是一个非常糟糕的想法。( $A && $B ) 是一个效率令人惊讶的操作--您将通过调用 fork() 来产生子进程,然后将 $A 的内容字符串分割为元素列表(在这种情况下大小为一),并评估每个元素作为通配符表达式(在这种情况下是一个表达自身的通配符表达式),然后运行结果作为命令(在这种情况下是一个内置命令)。 - Charles Duffy
3
另外,如果你的“true”和“false”字符串来自其他地方,存在风险它们实际上可能包含除“true”或“false”以外的内容,并且你可能会遇到意料之外的行为,甚至包括任意命令执行。 - Charles Duffy
9
使用 A=1; B=1if (( A && B )) 进行数字处理,或者使用 [[ $A && $B ]],将空字符串视为假,非空字符串视为真,都比较安全。请注意,这里强调了“much, much safer”,即相对于其他方式更加安全。 - Charles Duffy
5
(请注意,我在上面只使用全大写的变量名是为了遵循答案所建立的约定 - POSIX实际上明确指定环境变量和shell内置使用全大写名称,并保留小写名称供应用程序使用;为了避免与未来的操作系统环境冲突,特别是考虑到设置shell变量会覆盖任何同名的环境变量,最好在自己的脚本中遵守这个约定。) - Charles Duffy

4

使用true/false可以减少一些括号的混乱...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit

我发现这很有帮助,但在Ubuntu 18.04中,$0是“-bash”,basename命令会抛出错误。将该测试更改为[[ "$0" =~ "bash" ]]使脚本对我起作用。 - WiringHarness

4

正如@tripleee所指出的,这充其量只是相关的。

不过,万一你像我一样搜索到了这种内容,这里是我的解决方案。

为了处理用户可访问的配置文件,我使用这个函数:

function isTrue() {
    if [[ "${@^^}" =~ ^(TRUE|OUI|Y|O$|ON$|[1-9]) ]]; then return 0;fi
    return 1
    }

这可以像这样使用

if isTrue "$whatever"; then..

您可以更改正则表达式中的“真值列表”,此示例中的列表是与法语兼容的,将像“Yeah,yup,on,1,Oui,y,true”这样的字符串视为“True”。
请注意,“^^”提供了不区分大小写的功能。

这个最多只是次要的。你应该引用 "${@^^}" 和参数 "$whatever" - tripleee

1

为了提供更多的清晰度,下面是一些相关的背景信息。对于一个新手来说,BaSH中的真/假语句可能会感到有些奇怪。看以下简单例子及其结果。

这个语句将返回"true":

foo=" "; if [ "$foo" ]; then echo "true"; else echo "false"; fi

但是这将返回“false”:
foo=" "; if [ $foo ]; then echo "true"; else echo "false"; fi

你看明白了吗?第一个例子有一个带引号的 "" 字符串,这会导致 BaSH 字面上处理它。因此,从字面意义上讲,空格不是 null。而在非字面意义上(如上述第二个例子),BaSH 将空格(作为 $foo 中的值)视为“无”,因此它等同于 null(在此解释为“假”)。

这些语句都将返回文本字符串 "false":

foo=; if [ $foo ]; then echo "true"; else echo "false"; fi
foo=; if [ "$foo" ]; then echo "true"; else echo "false"; fi
foo=""; if [ $foo ]; then echo "true"; else echo "false"; fi
foo=""; if [ "$foo" ]; then echo "true"; else echo "false"; fi

有趣的是,这种条件语句总是会返回 true:

以下语句都将返回结果 "true":

foo=""; if [ foo ]; then echo "true"; else echo "false"; fi

注意区别:在条件语句中,变量名之前省略了$符号。无论在方括号中插入什么单词,BaSH都会将此语句视为真,即使使用一个之前从未与同一shell中的变量相关联的单词。请保留HTML标签。
if [ sooperduper ]; then echo "true"; else echo "false"; fi

同样地,将其定义为未声明的变量可以确保它始终返回false:
if [ $sooperduper ]; then echo "true"; else echo "false"; fi

关于BaSH,这与编写以下内容相同:

sooperduper="";if [ $sooperduper ]; then echo "true"; else echo "false"; fi

我是一名有用的助手,可以为您翻译文本。

还有一个提示...

括号与无括号

更加令人困惑的是,这些IF/THEN条件语句的变体都能工作,但返回相反的结果。

以下语句返回false:

if [ $foo ]; then echo "true"; else echo "false"; fi
if [ ! foo ]; then echo "true"; else echo "false"; fi

然而,以下内容将返回true结果:
if $foo; then echo "true"; else echo "false"; fi
if [ foo ]; then echo "true"; else echo "false"; fi
if [ ! $foo ]; then echo "true"; else echo "false"; fi

当然,这会返回一个语法错误(以及“false”的结果):
if foo; then echo "true"; else echo "false"; fi

你感到困惑了吗?对于初学者来说,将其弄清楚可能会相当具有挑战性,特别是如果你已经习惯了其他更高级的编程语言。


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