如何在shell脚本中声明和使用布尔变量?

1443

我试着在Shell脚本中使用以下语法声明一个布尔变量:

variable=$false

variable=$true

这是正确的吗?如果我想要更新那个变量,我会使用相同的语法吗?最后,使用布尔变量作为表达式的以下语法是否正确?

if [ $variable ]

if [ !$variable ]
27个回答

1714

修订后的回答(2014年2月12日)

the_world_is_flat=true
# ...do something interesting...
if [ "$the_world_is_flat" = true ] ; then
    echo 'Be careful not to fall off!'
fi

最初的回答

注意事项:https://dev59.com/j3A85IYBdhLWcg3wEPVL#21210966

the_world_is_flat=true
# ...do something interesting...
if $the_world_is_flat ; then
    echo 'Be careful not to fall off!'
fi

来自:在Bash中使用布尔变量

将最初的回答包含在此处的原因是因为2014年2月12日修改之前的评论仅涉及最初的回答,并且与修订后的回答相关的许多评论是错误的。例如,Dennis Williamson在2010年6月2日关于bash内置true的评论仅适用于最初的回答,而不适用于修订版。


47
为了解释正在发生的事情:if语句正在执行变量的内容,该变量是Bash内置命令true。任何命令都可以设置为变量的值,并且其退出值将被评估。 - Dennis Williamson
8
@pms,“-o”和“-a”操作符仅适用于“test”命令(也称为“[]”)。相反,这是“if + command”,没有“test”。 (例如,“if grep foo file; then ...”。)因此,请使用常规的&&||运算符:“#t1 = true;t2 = true; f1 = false;#如果$ t1 || $ f1; 然后echo is_true; else echo is_false; fi;(返回“true”,因为t1 = true)_ #如果$ t1 && $ f1 || $ t2; 然后echo is_true; else echo is_false; fi`(返回“true”,因为t2 = true)_。再次强调,这仅适用于“true” / “false”是bash内置命令(返回真/假)。除非var是一个cmd(即true或false),否则不能使用“if $ var ...”。 - michael
20
请参见我的回答以获取解释:https://dev59.com/j3A85IYBdhLWcg3wEPVL#21210966。 - Dennis
3
这里有许多错误的信息。/bin/true 没有被有效地使用。请参考Dennis的回答。 - ajk
1
@ajk 很多评论看起来不正确的原因是因为miku最初的回答在2014年2月12日被另一个用户修改了,因此使大多数先前的评论无效。 - wisbucky
显示剩余14条评论

1186

简而言之

my_bool=true

if [ "$my_bool" = true ]

Miku的(original)答案存在问题

不建议采纳这个答案1。它的语法很漂亮,但是它有一些缺陷。

假设我们有以下条件。

if $var; then
  echo 'Muahahaha!'
fi

在以下情况2下,该条件将评估为true并执行嵌套命令。
# Variable var not defined beforehand. Case 1
var=''  # Equivalent to var="".      # Case 2
var=                                 # Case 3
unset var                            # Case 4
var='<some valid command>'           # Case 5

通常情况下,当你的"布尔"变量(例如此处的var)被明确设置为true时,你只希望条件表达式评估为true。其他所有情况都具有危险的误导性!特别是最后一个案例(#5),因为它将执行包含在变量中的命令(这也就是为什么该条件对于有效的命令会评估为true3, 4)。以下是一个无害的示例:
var='echo this text will be displayed when the condition is evaluated'
if $var; then
  echo 'Muahahaha!'
fi

# Outputs:
# this text will be displayed when the condition is evaluated
# Muahahaha!

引用变量更加安全,例如if "$var"; then。在上述情况下,您应该会收到命令未找到的警告。但我们可以做得更好(请参见我在底部的建议)。

还可以查看Mike Holt对Miku原回答的解释。

Hbar的回答存在的问题

这种方法也有意外的行为。

var=false
if [ $var ]; then
  echo "This won't print, var is false!"
fi

# Outputs:
# This won't print, var is false!

您会期望上述条件评估为false,因此不会执行嵌套语句。但出乎意料的是!引用值("false"),引用变量("$var"),或者使用test或[[而不是[都没有区别。

我建议您这样做:

以下是我建议您检查布尔值的方式。它们按预期工作。

my_bool=true

if [ "$my_bool" = true ]; then
if [ "$my_bool" = "true" ]; then

if [[ "$my_bool" = true ]]; then
if [[ "$my_bool" = "true" ]]; then
if [[ "$my_bool" == true ]]; then
if [[ "$my_bool" == "true" ]]; then

if test "$my_bool" = true; then
if test "$my_bool" = "true"; then

它们几乎都是等效的。与其他答案中的方法相比,您需要输入更多的按键5,但您的代码将更具防御性。


脚注

  1. Miku的回答已经被编辑过,不再包含(已知)的错误。
  2. 这不是详尽列表。
  3. 在这里,有效的命令意味着存在这个命令。无论命令的用法正确与否都没有关系。例如,即使没有woman页面,man woman仍将被视为有效命令。
  4. 对于无效(不存在的)命令,Bash会简单地抱怨找不到该命令。
  5. 如果您关心长度,那么第一项建议最短。

13
在使用[test时,使用==是不可移植的。考虑到可移植性是[/test相对于[[的唯一优势,所以建议使用= - chepner
2
@Scott 我使用fish作为我的主要shell,我认为它的脚本语言比bash更加合理。 - Dennis
3
是的,我在评论中找不到对这个隐藏笑话的赞赏,所以不得不指出它 =) - Kranach
11
对我来说,如果我使用bool="true"这个方法,从概念上理解会更容易。这样清楚表明它只是一个字符串,而不是某种特殊值或内置值。 - wisbucky
1
如果var是一个本地变量,其中分配在您的程序中完全受控制,则使用@miku的答案而不带引号没有风险。 - dolmen
显示剩余6条评论

204

这里似乎有一些关于Bash内置命令true的误解,更具体地说,是关于Bash如何扩展和解释括号内表达式的误解。

miku's answer中的代码与Bash内置命令true/bin/true或任何其他版本的true命令完全无关。在这种情况下,true只是一个简单的字符字符串,并且变量赋值和条件表达式的求值都没有调用true命令/内置命令。

以下代码在功能上与miku答案中的代码相同:

the_world_is_flat=yeah
if [ "$the_world_is_flat" = yeah ]; then
    echo 'Be careful not to fall off!'
fi

这里唯一的区别就是进行比较的四个字符为"y"、"e"、"a"和"h",而不是"t"、"r"、"u"和"e"。仅此而已。并没有尝试调用名为 yeah 的命令或内置命令,在 miku 的示例中也没有对 Bash 解析标记true时进行任何特殊处理。它只是一个字符串,而且是完全随意的。
更新(2014-02-19):在查看了miku答案中的链接后,我现在知道一些混淆是从哪里来的。Miku的答案使用单括号,但他链接到的代码片段没有使用括号。它只是:
the_world_is_flat=true
if $the_world_is_flat; then
  echo 'Be careful not to fall off!'
fi

这两段代码片段的行为是一样的,但括号完全改变了底层的运行方式。

以下是Bash在每种情况下的操作:

没有括号:

  1. 将变量$the_world_is_flat扩展为字符串"true"
  2. 尝试将字符串"true"解析为命令。
  3. 查找并运行true命令(内置或/bin/true,取决于Bash版本)。
  4. true命令的退出代码(始终为0)与0进行比较。请记住,在大多数shell中,退出代码为0表示成功,其他任何代码表示失败。
  5. 由于退出代码为0(成功),执行if语句的then子句

括号:

  1. 将变量$the_world_is_flat扩展为字符串"true"
  2. 解析现在完全展开的条件表达式,其形式为string1 = string2=运算符是bash的字符串比较运算符。所以...
  3. "true""true"进行字符串比较。
  4. 是的,这两个字符串相同,因此条件的值为真。
  5. 执行if语句的then子句。

没有括号的代码可以工作,因为true命令返回一个退出代码0,表示成功。有括号的代码可以工作,因为$the_world_is_flat的值与=右侧的字符串文字true相同。

为了强调这一点,请考虑以下两个代码片段:

此代码(如果以root特权运行)将重新启动计算机:

var=reboot
if $var; then
  echo 'Muahahaha! You are going down!'
fi

这段代码只会打印出“尝试不错”。并没有调用重新启动命令。
var=reboot
if [ $var ]; then
  echo 'Nice try.'
fi
更新(2014年4月14日):回答评论中关于===之间区别的问题:据我所知,它们没有区别。 ==运算符是Bash特定的=同义词,并且就我所见,在所有上下文中它们的工作方式完全相同。

但请注意,我特指在[ ][[ ]]测试中使用的===字符串比较运算符。我并不是在暗示===在bash中可以互换使用。

例如,您显然不能使用==进行变量赋值,例如var == "foo"(技术上您可以这样做,但是Bash在此处看到的不是==运算符,而是一个=(赋值)运算符,后跟字面值="foo",这只会成为"=foo"的值。)

同时,尽管===是可以互换的,但是你需要记住这些测试的工作方式取决于你是否在[ ]或者[[ ]]内使用以及操作数是否被引用。你可以阅读更多关于此的信息在高级Bash脚本指南:7.3 其他比较运算符(向下滚动到===讨论部分)。

不使用括号的方法还有一个优点,就是让你能够编写干净、清晰(在我看来)的一行代码,比如 $the_world_is_flat && echo "you are in flatland!" - ajk
10
可以的。真的。虽然,我既不倡导支持任何一种方法,也不反对它们。我只是想澄清一些被投赞同票的错误信息,这样以后碰巧看到这个话题的人就不会带着一堆关于这个问题的误解离开了。 - Mike Holt
1
混淆的原因是miku的原始答案持续了4年。所有对内置true的引用都是针对原始答案进行的。(2014年2月12日修订后的答案不是由miku提交的。)我已经编辑了答案,包括原始和修订版。这样人们的评论就有意义了。 - wisbucky
1
从这里提供的答案中,我得到的印象是实际上没有使用真正的true。有办法吗?我怀疑许多程序员习惯于更严格的语言,他们会查看此答案以帮助他们混合一些“bash”胶水,使他们的生活变得更轻松,他们想要一个===运算符,以便字符串和“布尔值”实际上不可互换。他们应该坚持使用0和1,并像Quolonel Question答案中建议的那样使用(( $maybeIAmTrue ))吗? - Seldom 'Where's Monica' Needy
3
针对SeldomNeedy的评论,是的,你可以使用真正的true,但通常不作为与变量进行比较的东西,因为真正的true本身没有值。它所做的只是将退出状态设置为0,表示成功。值得注意的是,这基本上相当于所谓的“空命令”或:。至于使用01,这是我现在所有脚本中需要布尔类型时所做的。我使用(( ))运算符来进行求值,而不是[[ ]]。因此,例如,如果我有flag=0,那么我可以这样做:if (( flag )); then ... - Mike Holt
显示剩余4条评论

93

简而言之:

Bash中没有布尔类型

truefalse命令

Bash确实有布尔表达式,用于比较和条件。但是,在Bash中您可以声明和比较的只有字符串和数字。

在Bash中,无论何时看到truefalse,它都是一个字符串或一个仅用于其退出代码的命令/内置命令。

这种语法...

if true; then ...

is essentially...

if COMMAND; then ...

该命令为true。只要该命令返回退出码0,条件就为真。truefalse是Bash内建命令,有时也是独立的程序,除了返回相应的退出码外并不做任何事情。


if..then..fi中的条件

在使用方括号或者test命令时,您需要依赖于这些构造的退出码。请记住,[ ][[ ]]也只是像其他命令/内建命令一样的命令/内建命令。所以...

if [[ 1 == 1 ]]; then echo yes; fi

对应于

if COMMAND; then echo yes; fi

这里的COMMAND[[,参数为1 == 1 ]]

if..then..fi结构只是语法糖。您可以始终通过使用双和号分隔的命令来实现相同的效果:

[[ 1 == 1 ]] && echo yes

在这些测试结构中使用truefalse时,实际上只是将字符串"true""false"传递给测试命令。下面是一个例子:
信不信由你,但是这些条件都会产生相同的结果
if [[ false ]]; then ...
if [[ "false" ]]; then ...
if [[ true ]]; then ...
if [[ "true" ]]; then ...

TL;DR: 总是将其与字符串或数字进行比较

为了让未来的读者更加清楚,建议始终在truefalse周围使用引号:

DO

if [[ "${var}" == "true" ]]; then ...
if [[ "${var}" == "false" ]]; then ...
if [[ "${var}" == "yes" ]]; then ...
if [[ "${var}" == "USE_FEATURE_X" ]]; then ...
if [[ -n "${var:-}" ]]; then echo "var is not empty" ...

不要这样做

# Always use double square brackets in bash!
if [ ... ]; then ...
# This is not as clear or searchable as -n
if [[ "${var}" ]]; then ...
# Creates impression of Booleans
if [[ "${var}" != true ]]; then ...
# `-eq` is for numbers and doesn't read as easy as `==`
if [[ "${var}" -eq "true" ]]; then ...

也许
# Creates impression of Booleans.
# It can be used for strict checking of dangerous operations.
# This condition is false for anything but the literal string "true".
if [[ "${var}" != "true" ]]; then ... 

2
我不同意“在bash中始终使用双括号”的说法。实际上,在我编写的几乎所有脚本中,我都使用单括号,除非需要进行模式匹配。我认为人们应该理解[(即test)和[[之间的区别,并使用适合自己需求的那个。 - Weijun Zhou
@WeijunZhou,能否详细说明在哪些情况下使用单括号更好? - Hubert Grzeskowiak
1
@WeijunZhou,你的例子是反对使用单方括号的有力论据。它使代码更难理解,并为错误敞开了大门。双方括号更加严格,鼓励编写更清晰的代码。 - Hubert Grzeskowiak
1
这个答案比其他所有答案都帮助我理解了一些核心概念。[ 是一个内置命令,根据其表达式的求值结果返回一个退出码,这个想法非常重要;而对我来说,“啊哈”时刻是内置命令 true 和 false 是相同的。因此,如果你直接将 true 或 false 赋值给变量并在使用时不加括号,你可以进行简单的布尔检查:if $my_bool ; then 或者 `$my_bool && echo "打印这个"。从这里你只能得到退出码 0 和退出码 1,这点我明白了。谢谢! - trisweb
1
这个答案比其他所有答案都帮助我理解了一些核心概念。[ 是一个内置命令,根据其表达式的求值返回一个退出代码的概念很重要;对我来说,“啊哈”时刻是内置命令 true 和 false 是一样的。因此,如果你直接给变量赋值 true 或 false 并且在不使用括号的情况下使用它们,你可以进行简单的布尔检查:if $my_bool ; then 或者 $my_bool && echo "打印这个"。退出代码 0 和退出代码 1,这就是你从 true/false 这里得到的一切。对我来说恍然大悟。谢谢! - undefined
显示剩余5条评论

92

使用算术表达式。

#!/bin/bash

false=0
true=1

((false)) && echo false
((true)) && echo true
((!false)) && echo not false
((!true)) && echo not true

输出:

true(真)
not false(不是假)


4
优点:(1.) 行为类似于C语言处理bool的方式,(2.) 语法非常简洁/最小化(不需要右手变量和像'='或'=='这样的运算符),(3.) <主观>对我而言,我能够理解发生了什么,不需要冗长的解释......与Miku和Dennis的答案相比,它们都需要冗长的解释</主观>。 - Trevor Boyd Smith
7
为什么你不直接说,“优点:一切,缺点:无”。这样可以在长期使用中节省键盘和显示器的折旧成本。 - Quolonel Questions
7
对于交互使用,比如一行代码,请确保在 ! 后留一个空格,否则它会进行历史记录扩展。((! foo)) 可以工作,! ((foo)) 也可以。顺便说一下,我喜欢这个解决方案。终于有了一种简洁的方法来实现布尔变量。((foo || bar)) 如预期般工作。 - Peter Cordes
7
(()) 会递归地展开变量,这点我没有预料到。foo=bar; bar=baz; ((foo)) && echo echo 不会打印任何内容,但是当 baz=1 时结果为真。因此,你可以通过执行 true=1 来支持 foo=truefoo=false 以及0或1。请注意,在翻译过程中不要改变原文的含义。 - Peter Cordes
5
那反而是好事,因为现在你有一种可以识别代码漏洞的机制了。 - Quolonel Questions
显示剩余13条评论

25

很久以前,当我们只有sh时,布尔值是通过依赖于test程序的惯例处理的,其中如果没有任何参数运行,则test返回错误的退出状态。

这使得可以将未设置的变量视为false,将设置为任何值的变量视为true。今天,test是Bash的一个内置命令,并且通常被称为其一个字符的别名[(或者在缺少它的shell中使用可执行文件,正如dolmen所指出的那样):

FLAG="up or <set>"

if [ "$FLAG" ] ; then
    echo 'Is true'
else
    echo 'Is false'
fi

# Unset FLAG
#    also works
FLAG=

if [ "$FLAG" ] ; then
    echo 'Continues true'
else
    echo 'Turned false'
fi

由于引用约定,脚本作者更喜欢使用复合命令[[来模拟test,但其语法更好:带有空格的变量无需加引号;可以使用&&||作为具有奇怪优先级的逻辑运算符,并且没有关于术语数量的POSIX限制。

例如,要确定FLAG是否设置并且COUNT是大于1的数字:

FLAG="u p"
COUNT=3

if [[ $FLAG  && $COUNT -gt '1' ]] ; then
    echo 'Flag up, count bigger than 1'
else
    echo 'Nope'
fi

当需要处理多个shell,并涉及到空格、零长度字符串和空变量时,这些内容可能会让人困惑。


3
[ 不仅仅是 bash 中的别名。这个别名其实也存在于二进制文件中(或者作为链接存在),因此可以在裸露的 sh 环境下使用。请运行命令 ls -l /usr/bin/\[ 进行验证。如果你使用的是 bash/zsh,则应该改用更加强大的真正内部命令 [[ - dolmen
2
@dolmen, 根据Bash手册页面,[test也是Bash SHELL BUILTIN COMMAND,因此性能应该没有问题。同样的道理适用于如Dash等其他shell。(/bin/sh可能只是指向/bin/dash的符号链接)。要使用可执行文件,必须使用完整路径,即/usr/bin/\[ - jarno

14

如何在shell脚本中声明和使用布尔变量?

与许多其他编程语言不同,Bash不会按“类型”分类其变量。[1]

因此,答案很明显。在Bash中没有任何布尔变量

但是:

使用declare语句,我们可以将值分配限制为变量。[2]

#!/bin/bash
declare -ir BOOL=(0 1) # Remember BOOL can't be unset till this shell terminates
readonly false=${BOOL[0]}
readonly true=${BOOL[1]}

# Same as declare -ir false=0 true=1
((true)) && echo "True"
((false)) && echo "False"
((!true)) && echo "Not True"
((!false)) && echo "Not false"

declarereadonly 中的 r 选项用于明确声明变量为 只读。希望目的清晰明了。


1
为什么不直接使用 declare -ir false=0 true=1 呢?使用数组有什么优势吗? - Benjamin W.
1
@BenjaminW。我只是想提一下关于r选项和readonly命令的事情。我会按照你在我的脚本中建议的方式来做。 - sjsam
也许我错过了什么,但为什么这种方式声明的 true 和 false 没有使用美元符号?$true $false - qodeninja
只是简单地复制我的答案,并使其更糟。 - Quolonel Questions
@QuolonelQuestions Bash变量没有“类型”,因此说“声明和使用布尔变量”没有意义。我们可以通过多种方式模拟/假定变量具有“类型”。我在你的回答中没有看到这一点提到。 - sjsam
@qodeninja 在bash中,声明时永远不使用$符号 -- 只有在求值时才使用。 - Jonathan Cross

14

与其伪造一个布尔值,为未来的读者留下陷阱,为什么不使用比true和false更好的值呢?

例如:

build_state=success
if something-horrible; then
  build_state=failed
fi

if [[ "$build_state" == success ]]; then
  echo go home; you are done
else
  echo your head is on fire; run around in circles
fi

为什么不用整数? - phil294
7
由于不同语言对整数的使用方式不同,因此需要进行翻译。在一些语言中,数字 0 转化为 false,而 1 转化为 true。在程序退出代码方面(Bash 在历史上使用),0 表示正常结果或 true,而其他任何值都表示负面/错误或 false。请注意,翻译后的内容应保持原意,并且更加通俗易懂。 - Hubert Grzeskowiak

12

我的发现和建议与其他帖子有些不同。我发现我可以基本上像使用任何“常规”语言一样使用“布尔值”,而无需进行建议的“跳过障碍”...

没有必要使用[]或明确的字符串比较...我尝试了多个Linux发行版。我测试了Bash、Dash和BusyBox。结果总是一样的。我不确定最初得票最高的帖子在说什么。也许时代已经改变,就这样?

如果将变量设置为true,则在条件语句中它随后会被评估为“肯定”的。将其设置为false,则会被评估为“否定”。非常简单!唯一的注意事项是,一个未定义的变量也被评估为true!如果它执行与大多数语言相反的操作(如大多数语言所做的那样),那将很好,但这就是诀窍-您只需要明确初始化您的布尔值为true或false。

为什么会这样工作呢?答案有两个方面。A)shell中的true/false实际上意味着“无错误”与“错误”(即0与任何其他值)。B)true/false不是值-而是shell脚本中的语句!关于第二点,在单独一行上执行truefalse会将你所在块的返回值设置为该值,即false是一个声明“遇到错误”,而true“清除”它。将其与分配给变量一起使用将“返回”该变量。在条件语句中,未定义变量评估为true,因为它等同于0或“未遇到错误”。

请参见下面的示例Bash行和结果。如果您想确认,请自己测试...

#!/bin/sh

# Not yet defined...
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

myBool=true
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

myBool=false
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

产量

when set to
it evaluates to true
when set to true
it evaluates to true
when set to false
it evaluates to false

9

POSIX(可移植操作系统接口)

这里遗漏了一个重点,即可移植性。这就是为什么我的标题中包含POSIX的原因。

基本上,所有得票数最高的答案都是正确的,但它们过于依赖于Bash

基本上,我只想增加有关可移植性的更多信息。


  1. [ and ] brackets like in [ "$var" = true ] are not necessary, and you can omit them and use the test command directly:

    test "$var" = true && yourCodeIfTrue || yourCodeIfFalse
    

    Important note: I no longer recommend this as it's being slowly deprecated and more difficult to combine multiple statements.

  2. Imagine what those words true and false mean to the shell, test it yourself:

    echo $(( true ))
    
    0
    
    echo $(( false ))
    
    1
    

    But using quotes:

    echo $(( "true" ))
    
    bash: "true": syntax error: operand expected (error token is ""true"")
    sh (dash): sh: 1: arithmetic expression: expecting primary: ""true""
    

    The same goes for:

    echo $(( "false" ))
    

    The shell can't interpret it other than a string. I hope you are getting the idea of how good it is using proper keyword without quotes.

    But no one said it in previous answers.

  3. What does this mean? Well, several things.

    • You should get used to the Boolean keywords are actually treated like numbers, that is true = 0 and false = 1, remember all non-zero values are treated like false.

    • Since they are treated as numbers, you should treat them like that too, i.e. if you define variable say:

      var_bool=true
      echo "$var_bool"
      
       true
      

      you can create an opposite value of it with:

      var_bool=$(( 1 - $var_bool ))  # same as $(( ! $var_bool ))
      echo "$var_bool"
      
      1
      

    As you can see for yourself, the shell does print true string for the first time you use it, but since then, it all works via number 0 representing trueor 1 representing false, respectively.

最终,您应该如何处理所有这些信息

  • First, one good habit would be assigning 0 instead of true; 1 instead of false.

  • Second good habit would be to test if the variable is / isn't equal to zero:

    if [ "$var_bool" -eq 0 ]; then
         yourCodeIfTrue
    else
         yourCodeIfFalse
    fi
    

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