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


573

一位同事最近在一次代码审查中声称[[ ]][ ]在诸如以下的构造中,该构造优于

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

他无法提供理由。有一个吗?


19
要灵活一些,有时让您自己听建议而不需要其深入的解释即可:) [[就其而言,代码是简洁明了的,但是请记住那天,您会使用默认的shell(不是bash或)将脚本作品移植到系统上ksh。等[比较丑陋,繁琐,但AK-47在任何情况下都可以使用。

178
@rook当然,您可以在不做深入解释的情况下听取建议。但是,当您要求解释但不理解时,通常是一个危险信号。“相信,但要验证”等等。
Josip Rodin


1
@rook换句话说:“做你所要告诉的事情,不要问问题”
字形

@rook这没有任何意义。
拉基布

Answers:


606

[[具有更少的惊喜,并且通常更安全。但是它不是可移植的-POSIX没有指定它的功能,只有一些shell支持它(除了bash,我听说ksh也支持它)。例如,您可以

[[ -e $b ]]

测试文件是否存在。但是[,您必须用引号引起来$b,因为它会拆分参数并扩展类似的内容"a*"(按[[字面意义进行处理)。这也与如何[成为外部程序并像其他程序一样正常地接收其自变量有关(尽管它也可以是内置函数,但是仍然没有这种特殊处理)。

[[还具有其他一些不错的功能,例如正则表达式匹配=~以及类似C语言中已知的运算符。这是一个很好的页面:test [和之间有什么区别[[和重击测试


21
考虑到bash如今无处不在,我倾向于认为它非常可移植。对我而言,唯一的常见例外是在busybox平台上-但考虑到运行它的硬件,您可能还是要为此付出特别的努力。

14
@枪:的确。我建议你的第二句话反驳你的第一句话。如果从整体上考虑软件开发,则“ bash无处不在”和“ busybox平台例外”是完全不兼容的。busybox在嵌入式开发中非常普遍。
Lightness Races in Orbit

11
@guns:即使bash安装在我的盒子上,它也可能没有执行脚本(我可能将/ bin / sh符号链接到一些破折号上,这在FreeBSD和Ubuntu上是标准的)。Busybox也有其他怪异之处:如今,使用标准的编译选项,它可以解析[[ ]]但解释为与含义相同[ ]
dubiousjim

59
@dubiousjim:如果您使用仅bash的构造(如该构造),则应使用#!/ bin / bash,而不是#!/ bin / sh。
Nick Matteo

16
这就是为什么我要使用脚本,#!/bin/sh但是#!/bin/bash一旦我依靠某些BASH特定功能,便立即切换使用,以表示该脚本不再是Bourne shell可移植的。
安东尼

151

行为差异

Bash 4.3.11的一些区别:

  • POSIX vs Bash扩展:

  • 常规命令与魔术

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

      ]只是其中的一个论点而[无法使用进一步的论点。

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

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

      特别<是重定向,&&||连接多个命令,( )除非通过进行转义\,否则将生成子外壳,并且单词扩展照常发生。

    • [[ X ]]是一个X可以神奇地解析的单一构造。<&&||()经特殊处理,以及分词规则是不同的。

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

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

  • <

  • &&||

    • [[ a = a && b = b ]]:真实,逻辑
    • [ a = a && b = b ]:语法错误,&&解析为AND命令分隔符cmd1 && cmd2
    • [ a = a -a b = b ]:等效,但已被POSIX³弃用
    • [ a = a ] && [ b = b ]:POSIX和可靠的等效产品
  • (

    • [[ (a = a || a = b) && a = b ]]:错误
    • [ ( a = a ) ]:语法错误,()被解释为子shell
    • [ \( a = a -o a = b \) -a a = b ]:等效,但()已被POSIX弃用
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX等效⁵
  • 扩展时分词和生成文件名(split + glob)

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

    • [[ ab = a? ]]:是的,因为它确实进行模式匹配* ? [很神奇)。不会将glob扩展到当前目录中的文件。
    • [ ab = a? ]a?glob扩展。根据当前目录中的文件,可能是true还是false。
    • [ ab = a\? ]:错误,不是全局扩展
    • ===在双方的同[[[,不过==是一个bash扩展。
    • case ab in (a?) echo match; esac:POSIX等效
    • [[ ab =~ 'ab?' ]]:假⁴,失去魔法 ''
    • [[ 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扩展的复杂性。[只是一个带有奇怪名称的常规命令,不涉及任何特殊的语义。

¹源自[[...]]Korn外壳中的等效构造

²,但对于aor的某些值b(例如+index)失败,并且如果ab看起来像十进制整数,则进行数字比较。expr "x$a" '<' "x$b"都可以解决。

³也失败了的一些值ab喜欢!(

b在bash 3.2及更高版本中并没有提供与bash 3.1的兼容性(与一样BASH_COMPAT=3.1

⁵尽管由于和壳操作符(与和运算符或/ 运算符相对)具有相同的优先级,所以不需要分组(此处使用{...;}命令组,而不是(...)将运行不必要的子shell)。因此将是等效的。||&&||&& [[...]]-o-a [[ a = a ] || [ a = b ] && [ a = b ]


4
很好的解释。我[出于相同的原因使用。
哥顿

2
用Bashese :)哈。很好地阐明了使用POSIX的区别和理由。
乔纳森·科马尔

56

[[ ]]具有更多功能-建议您阅读Advanced Bash脚本指南以获取更多信息,尤其是第7章“测试”中扩展测试命令部分。

顺便说一句,如指南所述,它[[ ]]是在ksh88(1988年版本的Korn shell)中引入的。


5
这远不是一种bashism,它是在Korn shell中首次引入的。
Henk Langeveld

6
@ Thomas,ABS实际上在许多圈子中被认为是非常差的参考;尽管它具有大量准确的信息,但它往往很少注意避免在其示例中展示不良做法,并且花费了大量生命来维持生命。
查尔斯·达菲

@CharlesDuffy感谢您的评论,您能提出一个好的选择吗?我不是shell脚本编写方面的专家,我正在寻找一份指南,每半年大约编写一次脚本即可查阅。
2014年

9
我使用@ Thomas,Wooledge BashFAQ和相关的Wiki;它们由Freenode #bash频道的居民积极维护(尽管有时有些刺痛,但他们往往更在意正确性)。BashFAQ#31,mywiki.wooledge.org / BashFAQ / 031是与该问题直接相关的页面。
Charles Duffy 2014年

13

哪个比较器,测试,方括号或双方括号最快?http://bashcurescancer.com

双括号是一个“复合命令”,其中,test和单括号是shell内置命令(实际上是同一命令)。因此,单括号和双括号执行不同的代码。

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


7
最快的shell脚本的痴迷是怎么回事?我希望它具有最大的便携性,并且不会在乎[[可能带来的改进。但是,那我是个老派老屁:-)
詹斯(Jens)2012年

1
我想很多贝壳证明内建的版本[test即使外部的版本也存在。
dubiousjim

4
总的来说,@ Jens我同意:脚本的整个目的是(是?)可移植性(否则,我们将进行代码和编译,而不是脚本)...我能想到的两个例外是:(1)制表符补全(其中使用许多条件逻辑,完成脚本会变得很长);和(2)超级提示(PS1=...crazy stuff...和/或$PROMPT_COMMAND);对于这些,我不希望脚本执行过程中有任何明显的延迟
迈克尔,

1
对最快的迷恋只是简单的风格。在其他所有条件都相同的情况下,为什么不将更高效的代码结构合并到您的默认样式中,特别是如果该结构还提供了更高的可读性?就可移植性而言,bash所适合的许多任务本质上是不可移植的。例如,apt-get update如果自上次运行以来已超过X个小时,我需要运行。当人们可以将可移植性从已经太长的代码约束列表中剔除时,这是一个很大的解脱。
罗恩·伯克

5

如果您要遵循Google的风格指南

测试,[然后[[

[[ ... ]]减少了错误,因为在[[和之间没有路径名扩展或单词拆分]],并且[[ ... ]]允许在[ ... ]不存在的情况下进行正则表达式匹配。

# 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.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

1
Google编写了“ 没有路径名扩展...发生 ”,但[[ -d ~ ]]返回true(这意味着~已扩展为/home/user)。我认为Google的文字应该更加精确。
JamesThomasMoon1979年

2

无法使用的典型情况[[是在autotools configure.ac脚本中,括号具有特殊且不同的含义,因此您必须使用test代替[[[-注意测试和[是同一程序。


2
给定自动工具不是POSIX Shell,为什么您会期望[将其定义为POSIX Shell函数?
哥顿

因为autoconf脚本看起来像shell脚本,并且会生成一个shell脚本,并且大多数shell命令都在其中运行。
vy32

-1

[[]]双括号在某些版本的SunOS下不支持,而在以下函数声明中则完全不支持:GNU bash,版本2.02.0(1)-release(sparc-sun-solaris2.6)


1
非常真实,一点也不无关紧要。bash跨旧版本的可移植性必须予以考虑。人们说“ bash无处不在且可移植,除了(可能在此处插入深奥的OS) ”-但以我的经验来看,solaris是必须特别注意可移植性的那些平台之一:不仅要考虑abash的较旧bash版本。较新的操作系统,带有数组,函数等的问题/错误;但即使是tr,sed,awk,tar之类的实用程序(在脚本中使用),在solaris上也有一些怪异之处,您必须解决。
迈克尔,

2
您是正确的...在Solaris上有太多非POSIX的实用程序,只需查看“ df”输出和参数即可。希望它会逐渐消失(加拿大除外)。
清道夫

7
Solaris 2.6认真吗?它于1997年发布,并于2006年终止了支持。我想如果您仍在使用该功能,那么您还会遇到其他问题!顺便说一句,它使用了Bash v2.02,它引入了双括号,因此即使在这么老的版本上也可以使用。2005年起的Solaris 10使用的是Bash 3.2.51,2011年起的Solaris 11使用的是Bash 4.1.11。
peterh 2013年

1
重新脚本可移植性。在Linux系统上,每个工具通常只有一个版本,那就是GNU版本。在Solaris上,通常可以在Solaris本地版本或GNU版本之间进行选择(例如Solaris tar与GNU tar)。如果您依赖于GNU特定的扩展,那么必须在脚本中说明该扩展才能移植。在Solaris上,如果要使用GNU grep,可以在前缀上加上“ g”,例如ggrep。
peterh 2013年

-2

概括地说,[[更好,因为它不会派生其他进程。没有方括号或单方括号比双方括号要慢,因为它分叉了另一个进程。


8
Test和[是bash中相同内置命令的名称。尝试使用type [以查看此内容。
AB

1
@alberge是对的,但[[与截然不同的[是bash命令行解释器解释的语法。在bash中,尝试输入type [[。unix4linux是正确的,尽管经典的Bourne-shell [测试会派生一个新进程来确定真值,但[[语法(由bash,zsh等从ksh借用)却没有。
蒂姆·吉尔伯特

2
@Tim,我不确定您在谈论哪个Bourne shell,但[它内置于Bash和Dash(/bin/sh在所有Debian衍生的Linux发行版中)。
AB

1
哦,我明白你的意思,是的。我在考虑在较旧的Solaris或HP / UX系统上使用/ bin / sh之类的东西,但是,当然,如果您需要与那些不使用[[要么。
蒂姆·吉尔伯特

1
@alberge Bourne外壳不是Bash(又名Bourne Again SHell)。
kiamlaluno
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.