在Bash中,单方括号和双方括号有何区别?

228

我正在阅读关于if的Bash示例,但有些示例是用单方括号写的:

if [ -f $param ]
then
  #...
fi

其他使用双方括号的人:

if [[ $? -ne 0 ]]
then
    start looking for errors in yourlog
fi

有什么不同之处?


3
你可以查看这个问题的回答来得到你的答案:http://unix.stackexchange.com/questions/3831/in-bash-is-if-z-1-and-if-1-the-same - Amir Naghizadeh
请参考 https://serverfault.com/questions/52034/what-is-the-difference-between-double-and-single-square-brackets-in-bash。 - pevik
顺带提一下,关于第二个例子,也可以参考为什么测试"$?"来查看命令是否成功是一种反模式? - tripleee
请参阅sh和bash之间的区别 - tripleee
7个回答

265

[] 是 POSIX shell 兼容的条件测试。

双重 [[]] 是标准 [] 的扩展,被 bash 和其他 shell(例如 zsh、ksh)支持。它们支持额外的操作(以及标准的 posix 操作)。例如:使用 || 代替 -o,并使用 =~ 进行正则表达式匹配。更详细的差异列表可以在bash 手册中有关条件构造的部分找到。

如果您想使脚本跨 shell 可移植,请使用 []。如果您需要不受 [] 支持且不需要可移植的条件表达式,则应使用 [[]]


15
我会尽力进行翻译:如果你的脚本没有以shebang开头,明确请求支持[[ ]]的shell(例如使用 #!/bin/bash#!/usr/bin/env bash 开头的bash),那么你应该使用可移植选项。假设 /bin/sh 支持这样的扩展名的脚本在像最近的Debian和Ubuntu发行版这样的操作系统上将无法运行。 - Gordon Davisson

133

行为差异

在Bash 4.3.11中测试:

  • POSIX vs Bash扩展:

  • 正常命令 vs 魔法命令

    • [只是一个有奇怪名字的普通命令。

      ]只是[的最后一个参数。

    Ubuntu 16.04实际上在/usr/bin/[提供了一个可执行文件,由coreutils提供,但Bash内置版本优先。

    没有改变Bash解析命令的方式。

    特别地,<表示重定向,&&||连接多个命令,()生成子shell,除非通过\进行转义,并且单词扩展和通常相同。

    • [[ X ]]是一个单一的构造,使得X被魔法解析。 <&&||()被特殊处理,单词分割规则也不同。

      还有更进一步的差异,比如==~

    在Bash中,[ 是一个内置命令,而[[ 则是一个关键字: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • &&||

    • [[ a = a && b = b ]]: true, 逻辑 and
    • [ a = a && b = b ]: 语法错误,&& 被解析为 AND 命令分隔符 cmd1 && cmd2
    • [ a = a ] && [ b = b ]: POSIX 可靠等效项
  • [ a = a -a b = b ]: 几乎等效,但被POSIX弃用,因为它是不合理的,并且对于一些像!(这样的ab的值会失败,因为它们会被解释为逻辑操作
  • (

    • [[ (a = a || a = b) && a = b ]]: 错误。没有( ),将为true,因为[[ && ]][[ || ]]具有更高的优先级
    • [ ( a = a ) ]: 语法错误,()被解释为子shell
    • [ \( a = a -o a = b \) -a a = b ]: 等效,但()-a-o被POSIX弃用。没有\( \),将为true,因为-a-o具有更高的优先级
    • { [ a = a ] || [ a = b ]; } && [ a = b ] 非弃用的POSIX等效。然而,在这种特定情况下,我们可以仅写:[ a = a ] || [ a = b ] && [ a = b ],因为||&& shell运算符具有相等的优先级,不像[[ || ]][[ && ]]-o-a[
  • 扩展时的单词分割和文件名生成(split+glob)

    • x='a b'; [[ $x = 'a b' ]]: true,不需要引号
    • x='a b'; [ $x = 'a b' ]: 语法错误,扩展为[ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: 如果当前目录中有多个文件,则语法错误。
    • x='a b'; [ "$x" = 'a b' ]: POSIX等效
  • =

    • [[ ab = a? ]]: true,因为它进行了模式匹配* ? [是特殊字符)。不会将其展开为当前目录中的文件。
    • [ ab = a? ]: a?会被展开为glob。因此,根据当前目录中的文件,可能为true或false。
    • [ ab = a\? ]: false,不是glob展开
    • ===[[[中是相同的,但==是Bash的扩展。
    • case ab in (a?) echo match; esac: POSIX等效
    • [[ ab =~ 'ab?' ]]: false,Bash 3.2及以上版本中使用''会丢失特殊字符,只有启用了与bash 3.1兼容的选项(例如BASH_COMPAT=3.1)才能匹配。
    • [[ ab? =~ 'ab?' ]]: true
  • =~

    • [[ ab =~ ab? ]]: true,POSIX 扩展正则表达式匹配,?未进行通配符扩展
    • [ a =~ a ]: 语法错误。没有bash等效命令。
    • printf 'ab\n' | grep -Eq 'ab?': POSIX等效 (仅适用于单行数据)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX等效。
  • 建议: 始终使用 []

    对于我见过的每个[[ ]]结构,都有POSIX等效命令。

    如果您使用[[ ]],则:

    • 失去可移植性
    • 强制读者学习另一个bash扩展的复杂性。[只是一个常规命令,具有奇怪的名称,不涉及任何特殊语义。

    感谢Stéphane Chazelas进行重要的更正和补充。


    5
    在我看过的所有[[ ]]结构中,都有相应的POSIX等效物。地球上任何图灵完备的编程语言也可以这样说。 - A. Rick
    4
    @A.Rick那将是所有“如何在语言Y中做X”的SO问题的有效答案. 当然,这个陈述中隐含着一个“方便”的意思。 - Ciro Santilli OurBigBook.com
    13
    非常棒的总结。感谢您的努力。然而,我不同意这个建议。这是可移植性与更加一致和强大的语法之间的权衡。如果您的环境可以使用 bash >4,则建议使用 [[ ]] 语法。 - Bernard
    12
    很好的回答,但我认为“建议:总是使用 []”应该理解为“我的偏好:如果您不想失去可移植性,请使用 []”。如在此处所述(http://mywiki.wooledge.org/BashFAQ/031):“如果可移植性/符合POSIX或BourneShell是一个问题,则应使用旧语法。另一方面,如果脚本需要BASH、Zsh或KornShell,则新语法通常更灵活,但不一定向后兼容。” 如果没有关于向后兼容的顾虑,我会选择 [[ ab =~ ab? ]] 而不是 printf 'ab' | grep -Eq 'ab?' - MauricioRobayo
    1
    很好的总结。此外,在[[ ]]内部不会发生文件名扩展。 - Jeff Learman
    显示剩余3条评论

    21

    来自bash@libera.irc的团队负责管理bash FAQ、Greg wiki、bash-hackers和shellcheck.net。无论如何,他们将“我们”替换为“I”。 - Gilles Quénot
    来自bash@libera.irc的团队负责管理bash FAQ、Greg wiki、bash-hackers和shellcheck.net。无论如何,他们将“我们”替换为“我”。 - Gilles Quénot

    17

    在条件测试的单括号中(即 [... ]),一些运算符,如单个 = ,由所有 shell 支持,而使用运算符 == 不受一些旧 shell 支持。

    在条件测试的双括号中(即 [[ ... ]]),在旧版或新版 shell 中使用 = 或 == 没有区别。

    编辑:我还应该注意到:在 bash 中,如果可能,请始终使用双括号 [[ ... ]],因为它比单括号更安全。我将通过以下示例说明原因:

    if [ $var == "hello" ]; then
    

    如果$var为空/空值,那么脚本看到的就是这样:

    if [ == "hello" ]; then
    

    这会使您的脚本出现错误。解决方法是使用双括号,或者始终记得在变量周围加引号 ("$var")。双括号是更好的防御性编码实践。


    5
    除非你有非常充分的理由不这样做,否则在所有变量的读取周围加上引号是一种更好的防御性编码实践,因为它适用于所有变量的读取,而不仅仅是在条件语句中。曾经有一个 iTunes 安装程序漏洞,如果硬盘名称包含空格(或类似字符),就会删除用户的文件。这种方法还解决了你提到的问题。 - Chai T. Rex

    6
    • [ 是像 printf 一样的内置命令。Bash 语法期望在与命令相同的位置看到它。而 ] 对于 Bash 来说除了作为 [ 内置命令使用外没有其他作用。(man bash / SHELL BUILTIN COMMANDS)
    • [[ 是像 if 一样的关键字。Bash 语法开始也期望在与命令相同的位置看到它,但是它不会被执行,而是进入条件上下文。而 ]] 也是一个关键字,用于结束这个上下文。(man bash / SHELL GRAMMAR / Compound Commands)

    Bash 按顺序尝试解析:语法关键字 > 用户别名 > 内置函数 > 用户函数 > 在 $PATH 中的命令。

    type [  # [ is a shell builtin
    type [[  # [[ is a shell keyword
    type ]  # bash: type: ]: not found
    type ]]  # ]] is a shell keyword
    compgen -k  # Keywords: if then else ...
    compgen -b  # Builtins: . : [ alias bg bind ...
    which [  # /usr/bin/[
    
    • [比较慢,因为它执行更多的解析代码。我猜测是这样的。但是我知道它调用相同数量的系统调用(已经测试过)。
    • [[在语法上更容易解析,即使对于人类来说也是如此,因为它开始了一个上下文。对于算术条件,考虑使用((
    time for i in {1..1000000}; do [ 'a' = 'b' ] ; done  # 1.990s
    time for i in {1..1000000}; do [[ 'a' == 'b' ]] ; done  # 1.371s
    
    time for i in {1..1000000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi ; done  # 3.512s
    time for i in {1..1000000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi; done  # 2.143s
    
    strace -cf  bash -c "for i in {1..100000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi  ; done;"  # 399
    strace -cf  bash -c "for i in {1..100000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi  ; done;"  # 399
    

    我建议使用[[:如果您不明确关心posix兼容性,那么意味着您不关心获得更多兼容性的脚本。

    3

    您可以使用双方括号进行轻量级正则表达式匹配,例如:

    if [[ $1 =~ "foo.*bar" ]] ; then

    (只要您使用的bash版本支持这种语法)


    6
    除非你将该模式用引号引起来,否则它会被视为字面字符串。 - ormaaj
    非常正确。有时候这让我很恼火 :) - asf107

    1

    Bash手册指出:

    当与[[一起使用时,‘<’和‘>’操作符会按照当前区域设置进行词典排序。测试命令使用ASCII 排序。

    (测试命令与[ ]相同)


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