【问题标题】:Difference between single and double square brackets in BashBash中单方括号和双方括号的区别
【发布时间】:2012-11-12 15:07:31
【问题描述】:

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

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

其他带双方括号的:

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

有什么区别?

【问题讨论】:

标签: bash if-statement


【解决方案1】:

您可以使用方括号进行简单的正则表达式匹配,例如:

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

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

【讨论】:

  • 除非您引用了该模式,因此它现在被视为文字字符串。
  • 非常正确。有时这让我很烦:)
【解决方案2】:

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

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

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

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

如果 $var 恰好为 null / 空,​​那么这就是脚本所看到的:

if [ == "hello" ]; then

这会破坏你的脚本。解决方案是使用双括号,或者始终记住在变量周围加上引号 ("$var")。双括号是更好的防御性编码实践。

【讨论】:

  • 在所有读取的变量中加上引号,除非您有充分的理由不这样做,这是一种非常更好的防御性编码实践,因为它适用于所有变量的读取,而不仅仅是条件中的变量。如果硬盘驱动器名称包含空格(或类似的东西),iTunes 安装程序错误会删除人们的文件。它也解决了你提到的问题。
【解决方案3】:

单个 [] 是符合 posix shell 的条件测试。

[[]] 是标准 [] 的扩展,并且受到 bash 和其他 shell(例如 zsh、ksh)的支持。它们支持额外的操作(以及标准的 posix 操作)。例如:|| 代替 -o 和正则表达式匹配 =~。可以在bash manual section on conditional constructs 中找到更完整的差异列表。

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

【讨论】:

  • 我要补充一点,如果您的脚本不是以明确请求支持[[ ]] 的shell 的shebang 开头(例如带有#!/bin/bash#!/usr/bin/env bash 的bash),您应该使用便携式选项。假设 /bin/sh 支持此类扩展的脚本将在操作系统(例如最近的 Debian 和 Ubuntu 版本)上中断,但情况并非如此。
【解决方案4】:

[[ 是一个 bash 关键字,类似于(但比)[ 命令更强大。

http://mywiki.wooledge.org/BashFAQ/031http://mywiki.wooledge.org/BashGuide/TestsAndConditionals

除非您正在为 POSIX sh 编写代码,否则我们建议您使用 [[

【讨论】:

    【解决方案5】:

    行为差异

    在 Bash 4.3.11 中测试:

    • POSIX 与 Bash 扩展:

    • 常规命令与魔法

      • [ 只是一个名字奇怪的常规命令。

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

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

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

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

      • [[ 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 ] && [ 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 的重要更正和补充。

    【讨论】:

    • 我见过的每个 [[ ]] 构造都有 POSIX 等效项。” 任何Turing Complete 语言都可以这样说行星。
    • @A.Rick 这将是所有“如何用语言 Y 做 X”SO 问题的有效答案 :-) 当然,该声明中隐含着“方便”。跨度>
    • 精彩的总结。感谢您的努力。然而,我不同意该建议。它的可移植性与更一致和更强大的语法相比。如果您可以在您的环境中要求 bash >4,那么建议使用 [[ ]] 语法。
    • @Downvoters 请解释一下,以便我学习和改进信息:-)
    • 很好的答案,但我认为 推荐:始终使用 [] 应该被理解为 我的偏好:如果您不想失去可移植性,请使用[]。如here 所述:如果考虑到 POSIX 或 BourneShell 的可移植性/一致性,则应使用旧语法。另一方面,如果脚本需要 BASH、Zsh 或 KornShell,则新语法通常更灵活,但不一定向后兼容。 如果可以且不担心,我宁愿使用 [[ ab =~ ab? ]]printf 'ab' | grep -Eq 'ab?'向后兼容
    【解决方案6】:

    Bash manual 说:

    当与 [[ 一起使用时,“”运算符按字典顺序排序 使用当前语言环境。测试命令使用 ASCII 订购。

    (测试命令等同于 [ ] )

    【讨论】:

      【解决方案7】:
      • [ 是一个 builtin,类似于 printf。 Bash 语法期望在与命令相同的位置看到它。 ] 对 Bash 来说没什么,除了 [ 内置函数所期望的。 (man bash / SHELL BUILTIN COMMANDS)
      • [[keyword 就像 if。 Bash 语法开始也期望它与命令在同一位置,但不是执行它,而是进入条件上下文。而]] 也是结束此上下文的关键字。 (man bash / SHELL GRAMMAR / 复合命令)

      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 兼容性,则表示您不关心,所以不要关心让不兼容的脚本“更”兼容。

      【讨论】:

        猜你喜欢
        • 2012-09-27
        • 2014-07-22
        • 1970-01-01
        • 2010-10-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-22
        • 2022-11-19
        相关资源
        最近更新 更多