我正在阅读关于if
的Bash示例,但有些示例是用单方括号写的:
if [ -f $param ]
then
#...
fi
其他使用双方括号的人:
if [[ $? -ne 0 ]]
then
start looking for errors in yourlog
fi
有什么不同之处?
我正在阅读关于if
的Bash示例,但有些示例是用单方括号写的:
if [ -f $param ]
then
#...
fi
其他使用双方括号的人:
if [[ $? -ne 0 ]]
then
start looking for errors in yourlog
fi
有什么不同之处?
[]
是 POSIX shell 兼容的条件测试。
双重 [[]]
是标准 []
的扩展,被 bash 和其他 shell(例如 zsh、ksh)支持。它们支持额外的操作(以及标准的 posix 操作)。例如:使用 ||
代替 -o
,并使用 =~
进行正则表达式匹配。更详细的差异列表可以在bash 手册中有关条件构造的部分找到。
如果您想使脚本跨 shell 可移植,请使用 []
。如果您需要不受 []
支持且不需要可移植的条件表达式,则应使用 [[]]
。
[[ ]]
的shell(例如使用 #!/bin/bash
或 #!/usr/bin/env bash
开头的bash),那么你应该使用可移植选项。假设 /bin/sh
支持这样的扩展名的脚本在像最近的Debian和Ubuntu发行版这样的操作系统上将无法运行。 - Gordon Davisson行为差异
在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 < b ]]
: 词典比较[ a \< b ]
: 同上。需要\
,否则会像其他命令一样进行重定向。Bash 扩展。expr x"$x" \< x"$y" > /dev/null
或 [ "$(expr x"$x" \< x"$y")" = 1 ]
: POSIX 等效项,参见:How to test strings for lexicographic less than or equal in Bash?&&
和 ||
[[ a = a && b = b ]]
: true, 逻辑 and[ a = a && b = b ]
: 语法错误,&&
被解析为 AND 命令分隔符 cmd1 && cmd2
[ a = a ] && [ b = b ]
: POSIX 可靠等效项[ a = a -a b = b ]
: 几乎等效,但被POSIX弃用,因为它是不合理的,并且对于一些像!
或(
这样的a
或b
的值会失败,因为它们会被解释为逻辑操作(
[[ (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等效命令。
如果您使用[[ ]]
,则:
[
只是一个常规命令,具有奇怪的名称,不涉及任何特殊语义。感谢Stéphane Chazelas进行重要的更正和补充。
[]
”应该理解为“我的偏好:如果您不想失去可移植性,请使用 []
”。如在此处所述(http://mywiki.wooledge.org/BashFAQ/031):“如果可移植性/符合POSIX或BourneShell是一个问题,则应使用旧语法。另一方面,如果脚本需要BASH、Zsh或KornShell,则新语法通常更灵活,但不一定向后兼容。” 如果没有关于向后兼容的顾虑,我会选择 [[ ab =~ ab? ]]
而不是 printf 'ab' | grep -Eq 'ab?'
。 - MauricioRobayo[[ ]]
内部不会发生文件名扩展。 - Jeff Learman[[
是类似于(但比)[
命令更强大的bash关键字。
请参阅
http://mywiki.wooledge.org/BashFAQ/031 和 http://mywiki.wooledge.org/BashGuide/TestsAndConditionals
除非你在编写POSIX sh,否则我建议使用[[
。
在条件测试的单括号中(即 [... ]),一些运算符,如单个 = ,由所有 shell 支持,而使用运算符 == 不受一些旧 shell 支持。
在条件测试的双括号中(即 [[ ... ]]),在旧版或新版 shell 中使用 = 或 == 没有区别。
编辑:我还应该注意到:在 bash 中,如果可能,请始终使用双括号 [[ ... ]],因为它比单括号更安全。我将通过以下示例说明原因:
if [ $var == "hello" ]; then
如果$var为空/空值,那么脚本看到的就是这样:
if [ == "hello" ]; then
这会使您的脚本出现错误。解决方法是使用双括号,或者始终记得在变量周围加引号 ("$var"
)。双括号是更好的防御性编码实践。
[
是像 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兼容性,那么意味着您不关心获得更多兼容性的脚本。您可以使用双方括号进行轻量级正则表达式匹配,例如:
if [[ $1 =~ "foo.*bar" ]] ; then
(只要您使用的bash版本支持这种语法)