【问题标题】:Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?在 Bash 中,双方括号 [[ ]] 是否优于单方括号 [ ]?
【发布时间】:2010-10-14 17:50:13
【问题描述】:

一位同事最近在代码审查中声称,[[ ]] 构造比 [ ] 在像

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

他无法提供理由。有吗?

【问题讨论】:

  • 灵活一些,有时允许自己听取建议而不需要深入解释 :) 至于 [[ 的代码很好且清晰,但请记住移植脚本的那一天在默认 shell 不是bashksh 的系统上,[ 更丑陋、更麻烦,但在任何情况下都可以作为AK-47 工作。
  • @rook 你可以在没有深入解释的情况下听取建议,当然。但是,当您要求解释但不明白时,这通常是一个危险信号。 “信任,但要验证”等等。
  • @rook 换句话说“按你说的做,不要问问题”
  • @rook 这没有任何意义。

标签: bash if-statement syntax


【解决方案1】:

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

[[ -e $b ]]

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

[[ 还具有其他一些不错的功能,例如与 =~ 匹配的正则表达式以及类似 C 语言中已知的运算符。这是一个很好的页面:What is the difference between test, [ and [[ ?Bash Tests

【讨论】:

  • 考虑到如今 bash 无处不在,我倾向于认为它非常便携。对我来说唯一常见的例外是在busybox平台上——但考虑到它运行的硬件,你可能还是想为它做出特别的努力。
  • @guns:确实。我建议您的第二句话反驳您的第一句话;如果您将软件开发视为一个整体,“bash 无处不在”和“例外是busybox 平台”是完全不兼容的。 busybox 广泛用于嵌入式开发。
  • @guns:即使我的机器上安装了 bash,它也可能不会执行脚本(我可能将 /bin/sh 符号链接到一些破折号变体,这是 FreeBSD 和 Ubuntu 上的标准) . Busybox 还有一个奇怪的地方:现在使用标准编译选项,它解析[[ ]],但将其解释为与[ ] 相同的含义。
  • @dubiousjim:如果你使用 bash-only 结构(比如这个),你应该有#!/bin/bash,而不是#!/bin/sh。
  • 这就是为什么我让我的脚本使用#!/bin/sh,然后在我依赖某些 BASH 特定功能时将它们切换为使用#!/bin/bash,以表示它不再是 Bourne shell 可移植的。
【解决方案2】:

行为差异

Bash 4.3.11 的一些差异:

  • POSIX 与 Bash 扩展:

  • 常规命令与魔法

    • [ 只是一个名称怪异的常规命令。

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

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

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

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

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

      还有更多区别,例如==~

    在 Bashese 中:[ 是内置命令,[[ 是关键字:What's the difference between shell builtin and shell keyword?

  • <

  • &&||

    • [[ 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 ]]:错误。如果没有( ),则为真,因为[[ && ]] 的优先级高于[[ || ]]
    • [ ( 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[ 不同
  • 扩展时的分词和文件名生成(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? ]]:是的,因为它确实是pattern matching* ? [ 是魔法)。不 glob 扩展到当前目录中的文件。
    • [ ab = a? ]a? 全局扩展。所以可能是真或假,具体取决于当前目录中的文件。
    • [ ab = a\? ]: 错误,不是全局扩展
    • ===[[[ 中是相同的,但 == 是 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 extended regular expression 匹配,? 不全局扩展
    • [ 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 的重要更正和补充。

【讨论】:

  • 很好的解释。出于同样的原因,我使用[
  • 在 Bashese :) 哈。很好地阐明了坚持使用 POSIX 的区别和理由。
  • POSIX 规则! (我只是在这里发表评论,以便在将来需要它时轻松找到这篇文章):D
  • 这是否成立?“在 POSIX 中有效的一切都在 BASH 中有效,反之则不然。” ?
  • @Wlad Bash 极大地扩展了 POSIX,因此任何 Bash 扩展都不会是 POSIX。反过来,我不是 100%,但感觉很可能(除非 Bash 扩展覆盖 POSIX 语法,例如在 posix [[ 中可能也是常规命令)。相关:askubuntu.com/questions/766270/…
【解决方案3】:

[[ ]] 具有更多功能 - 我建议您查看 Advanced Bash Scripting Guide 了解更多信息,特别是 Chapter 7. Tests 中的 extended test command 部分。

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

【讨论】:

  • 这远非 bashism,它最初是在 Korn shell 中引入的。
  • @Thomas,ABS 实际上在许多圈子中被认为是一个非常糟糕的参考;虽然它有大量准确的信息,但它往往很少注意避免在其示例中展示不良做法,并且大部分时间都没有维护。
  • @CharlesDuffy 感谢您的评论,您能说出一个好的替代方案吗?我不是 shell 脚本专家,我正在寻找一个可以参考的指南,以便我每半年编写一次脚本。
  • @Thomas,Wooledge BashFAQ 和相关的 wiki 是我使用的;它们由 Freenode #bash 频道的居民积极维护(虽然有时很棘手,但他们往往非常关心正确性)。 BashFAQ #31,mywiki.wooledge.org/BashFAQ/031,是与此问题直接相关的页面。
【解决方案4】:

来自Which comparator, test, bracket, or double bracket, is fastest? (http://bashcurescancer.com)

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

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

【讨论】:

  • shell 脚本中对 fastest 的痴迷是怎么回事?我想要它最便携,并且不在乎[[ 可能带来的改进。但是,我是个老派老屁 :-)
  • 我认为许多 shell 证明了 [test 的内置版本,即使也存在外部版本。
  • @Jens 总的来说我同意:脚本的全部目的是(是?)可移植性(否则,我们将编码和编译,而不是脚本)......我能想到的两个例外是:(1)制表符补全(补全脚本可能会因大量条件逻辑而变得很长); (2) 超级提示(PS1=...crazy stuff... 和/或$PROMPT_COMMAND);对于这些,我不希望在执行脚本时出现任何可察觉的延迟
  • 对最快的迷恋只是风格。在其他条件相同的情况下,为什么不将更高效的代码构造合并到您的默认样式中,尤其是如果该构造还提供了更高的可读性?就可移植性而言,bash 适合的许多任务本质上是不可移植的。例如,如果自上次运行以来已经超过 X 小时,我需要运行 apt-get update。当人们可以将可移植性从已经太长的代码约束列表中剔除时,这是一种极大的解脱。
【解决方案5】:

如果你喜欢关注Google's style guide

测试、[ … ][[ … ]]

[[ … ]] 优先于 [ … ]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 ]]; 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

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

【讨论】:

  • Google 写了“没有路径名扩展 ... 发生”但 [[ -d ~ ]] 返回 true(这意味着 ~ 已扩展为 /home/user)。我认为谷歌的写作应该更准确。
  • @JamesThomasMoon1979 这是波浪号扩展,不是谷歌文本中提到的路径名扩展
  • 不需要在 [[ ]] 内的“文件名”周围加上引号。
【解决方案6】:

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

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

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

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

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

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

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • 鉴于 autotools 不是 POSIX shell,您为什么会期望 [ 被定义为 POSIX shell 函数?
    • 因为autoconf脚本看起来像一个shell脚本,它会生成一个shell脚本,大部分shell命令都在里面运行。
    【解决方案8】:

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

    【讨论】:

    • 非常正确,而且一点也不无关紧要。必须考虑跨旧版本的 bash 可移植性。人们说“bash 无处不在且可移植,除了可能 (在此处插入深奥的操作系统)”——但根据我的经验,solaris 是必须特别注意可移植性的平台之一:不仅在较新的操作系统上考虑较旧的 bash 版本、带有数组、函数等的问题/错误;但即使是 tr、sed、awk、tar 之类的实用程序(在脚本中使用)在 solaris 上也有一些你必须解决的问题。
    • 你说的太对了……在 Solaris 上有这么多非 POSIX 的实用程序,只要看看“df”输出和参数……对 Sun 感到羞耻。希望它会一点一点地消失(加拿大除外)。
    • Solaris 2.6 是认真的吗?它于 1997 年发布,并于 2006 年结束支持。我想如果您仍在使用它,那么您还有其他问题!顺便说一句,它使用了 Bash v2.02,它引入了双括号,因此即使在这么古老的东西上也应该可以工作。 2005 年的 Solaris 10 使用 Bash 3.2.51,而 2011 年的 Solaris 11 使用 Bash 4.1.11。
    • 重新编写脚本的可移植性。在 Linux 系统上,每个工具通常只有一个版本,即 GNU 版本。在 Solaris 上,您通常可以选择 Solaris 原生版本或 GNU 版本(比如 Solaris tar 与 GNU tar)。如果你依赖于 GNU 特定的扩展,那么你必须在你的脚本中声明它是可移植的。在 Solaris 上,您可以通过前缀“g”来做到这一点,例如` ggrep` 如果你想要 GNU grep。
    【解决方案9】:

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

    【讨论】:

    • Test 和 [ 是 bash 中相同内置命令的名称。尝试使用type [ 来查看。
    • @alberge,没错,但[[[ 不同,是由 bash 命令行解释器解释的语法。在 bash 中,尝试输入 type [[。 unix4linux 是正确的,尽管经典的 Bourne-shell [ 测试派生出一个新进程以确定真值,但 [[ 语法(通过 bash、zsh 等从 ksh 借用)却没有。
    • @Tim,我不确定你说的是哪个 Bourne shell,但 [ 是 Bash 和 Dash 的内置(/bin/sh 在所有 Debian 派生的 Linux分布)。
    • 哦,我明白你的意思了,这是真的。我正在考虑类似旧 Solaris 或 HP/UX 系统上的 /bin/sh 之类的东西,但当然,如果你需要与那些你不会使用 [[ 的系统兼容。
    • @alberge Bourne shell 不是 Bash(又名 Bourne Again SHell)。
    猜你喜欢
    • 2010-10-14
    • 2012-11-12
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 2021-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多