在Bash中,双方括号 [[ ]] 比单方括号[]更可取吗?

一位同事最近在一次代码审查中声称,在诸如

if [ "`id -nu`" = "$someuser" ] ; thenecho "I love you madly, $someuser"fi

他说不出理由。有理由吗?

362097 次浏览

[[ ]]有更多的功能-我建议你看看高级Bash脚本指南以获取更多信息,特别是第7章.试验中的扩展测试命令部分。

顺便说一句,正如指南所指出的,[[ ]]是在ksh88(1988年版本的KornShell)中引入的。

哪个比较器,测试,括号,或双括号,是最快的?

双括号是一个“复合命令”其中作为测试和单个支架是外壳内置的(和在实际是同一个命令)。因此,单支架和双支架执行不同的代码。

测试和单括号是最便携的,因为它们存在单独的和外部的命令。但是,如果您使用任何远程现代版的BASH,双支持括号。

[[的惊喜较少,通常使用起来更安全。但它不可移植——POSIX没有指定它做什么,只有一些shell支持它(除了bash,我听说ksh也支持它)。例如,你可以这样做

[[ -e $b ]]

测试文件是否存在。但是对于[,您必须引用$b,因为它拆分参数并扩展"a*"之类的东西([[按字面意思理解)。这也与[如何成为外部程序并像其他程序一样正常接收其参数有关(尽管它也可以是内置程序,但它仍然没有这种特殊处理)。

[[还有一些其他不错的特性,比如与=~匹配的正则表达式以及类似C语言中已知的运算符。这是关于它的一个很好的页面:测试#2和#0有什么区别?bash测试

简而言之,[[更好,因为它不会分叉另一个进程。没有括号或单括号比双括号慢,因为它分叉另一个进程。

[[]]双括号在某些版本的SunOS下不受支持,并且在函数声明中完全不受支持:

GNU Bash,版本2.02.0(1)-发布(spac-sun-solaris2.6)

不能使用[[的典型情况是在自动工具configure.ac脚本中。括号有特殊和不同的含义,因此您必须使用test而不是[[[-请注意,test和[是同一个程序。

行为差异

Bash 4.3.11的一些差异:

  • POSIX vs Bash扩展:

  • 常规命令vs魔法

    • [只是一个具有奇怪名称的常规命令。

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

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

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

      特别是,<是重定向,&&||连接多个命令,( )生成子shell,除非被\转义,并且单词扩展照常发生。

    • [[ X ]]是一个使X被神奇地解析的单一构造。<&&||()被特殊对待,分词规则不同。

      还有更多的差异,如==~

    在Bashese中:[是内置命令,[[是关键字:shell内置和shell关键字有什么区别?

  • <

  • &&||

    • [[ a = a && b = b ]]:真实,逻辑
    • [ 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 ]]:false。如果没有( ),它将是true,因为[[ && ]][[ || ]]具有更大的优先级
    • [ ( a = a ) ]:语法错误,()被解释为子shell
    • [ \( a = a -o a = b \) -a a = b ]:等效,但()-a-o被POSIX弃用。如果没有\( \),这将是真的,因为-a-o具有更大的优先级
    • { [ a = a ] || [ a = b ]; } && [ a = b ]非弃用的POSIX等价物。然而,在这个特定的情况下,我们可以只写:[ a = a ] || [ a = b ] && [ a = b ],因为||&& shell运算符具有相同的优先级,与[[ || ]][[ && ]]以及-o-a[不同
  • 扩展时的单词拆分和文件名生成(拆分+全局)

    • 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?全局扩展。因此它可能为真或假,具体取决于当前目录中的文件。
    • [ ab = a\? ]:false,不是全局扩展
    • ===[[[中是相同的,但==是Bash扩展。
    • case ab in (a?) echo match; esac:POSIX等效
    • [[ ab =~ 'ab?' ]]:false,在Bash 3.2及更高版本中''失去魔力,并且未启用与Bash 3.1的兼容性(如BASH_COMPAT=3.1
    • [[ ab? =~ 'ab?' ]]:正确
  • =~

    • [[ 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éphaneChazelas的重要更正和添加。

如果你在下面的Google的风格指南

测试,[ … ][[ … ]]

[[ … ]]优于[ … ]test/usr/bin/[

[[ … ]]减少了错误,因为[[]]之间没有路径名扩展或单词拆分。此外,[[ … ]]允许正则表达式匹配,而[ … ]不允许。

# This ensures the string on the left is made up of characters in# the alnum character class followed by the string name.# Note that the RHS should not be quoted here.if [[ "filename" =~ ^[[:alnum:]]+name ]]; thenecho "Match"fi
# This matches the exact pattern "f*" (Does not match in this case)if [[ "filename" == "f*" ]]; thenecho "Match"fi
# This gives a "too many arguments" error as f* is expanded to the# contents of the current directoryif [ "filename" == f* ]; thenecho "Match"fi

有关血腥的细节,请参阅E14http://tiswww.case.edu/php/chet/bash/FAQ

在标题中明确带有“in Bash”的标记为“bash”的问题中,我对所有回复都说你应该避免[[]]感到有点惊讶,因为它只适用于bash!

确实,可移植性是主要的反对意见:如果你想编写一个在Bourne兼容的shell中工作的外壳脚本,即使它们不是bash,你应该避免[[]]。(如果你想在更严格的POSIX shell中测试你的shell脚本,我推荐dash;虽然它是一个不完整的POSIX实现,因为它缺乏标准所需的国际化支持,它也缺乏对bash、ksh、zsh等中许多非POSIX结构的支持。)

我看到的另一个反对意见至少适用于bash的假设:[[]]有自己的特殊规则,你必须学习,而[]就像另一个命令一样。这也是事实(桑蒂利先生带来了显示所有差异的收据),但差异是好是坏是相当主观的。我个人觉得双括号结构让我可以使用()进行分组,&&||用于布尔逻辑,<>用于比较,以及未引用的参数扩展。这就像它自己的封闭世界,表达式的工作方式更像传统的非命令shell编程语言。

我没有看到有人提出的一点是,[[]]的这种行为与POSIX指定的算术扩展构造$(())的行为完全一致,并且还允许未加引号的括号以及布尔和不等式运算符(在这里执行数字而不是词法比较)。本质上,任何时候你看到双括号字符,你都会得到同样的引号屏蔽效果。

(Bash及其现代亲属也使用(())-没有前导$-作为C风格的for循环标头或执行算术运算的环境;这两种语法都不是POSIX的一部分。)

所以有一些很好的理由更喜欢[[]];也有一些理由要避免它,这可能适用于你的环境,也可能不适用。至于你的同事,“我们的风格指南这么说”是一个有效的理由,就它而言,但我也会向理解风格指南推荐它的原因的人寻求背景故事。

我很惊讶我没有看到这个问题更早提出,但请考虑:

$ n=5$ [[ $n -gt 0 ]] && echo $n is positive5 is positive$ n=foo$ [[ $n -gt 0 ]] && echo $n is positive$ [ "$n" -gt 0 ] && echo $n is positivebash: [: foo: integer expression expected

缺少来自[[ $n -gt 0 ]]行的错误消息使[[完全不可用。也许我太苛刻了,正确的反应是简单地避免在[[中进行整数比较,但出于这个原因,我强烈建议不要使用[[