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

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

其他带有双方括号的:

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

有什么不同?


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

如果[[$1 =~ "*bar"]];然后

(只要您使用的bash版本支持此语法)


在单括号内用于条件测试(即[…]]),所有shell都支持一些操作符,例如单个=,而一些较老的shell不支持使用operator ==。

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

编辑:我还应该注意:在bash中,总是使用双括号[[…]]],因为它比单括号更安全。我将用下面的例子来说明原因:

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

如果$var恰好是空的,那么这是脚本看到的:

if [ == "hello" ]; then

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


单个[]是posix shell兼容的条件测试。

Double[[]]是标准[]的扩展,bash和其他shell(例如zsh, ksh)都支持它。它们支持额外的操作(以及标准的posix操作)。例如:||代替-o, regex匹配=~。在bash手册中关于条件构造的部分中可以找到更完整的差异列表。

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


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

See

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

除非您正在编写POSIX sh,否则我们推荐[[。


行为差异

在Bash 4.3.11中测试:

POSIX vs Bash extension: [ is POSIX [[ is a Bash extension inspired from Korn shell regular command vs magic [ is just a regular command with a weird name. ] is just the last argument of [. Ubuntu 16.04 actually has an executable for it at /usr/bin/[ provided by coreutils, but the bash built-in version takes precedence. Nothing is altered in the way that Bash parses the command. In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \, and word expansion happens as usual. [[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~. In Bashese: [ is a built-in command, and [[ is a keyword: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword < [[ a < b ]]: lexicographical comparison [ a \< b ]: Same as above. \ required or else does redirection like for any other command. Bash extension. expr x"$x" \< x"$y" > /dev/null or [ "$(expr x"$x" \< x"$y")" = 1 ]: POSIX equivalents, see: How to test strings for lexicographic less than or equal in Bash? && and || [[ a = a && b = b ]]: true, logical and [ a = a && b = b ]: syntax error, && parsed as an AND command separator cmd1 && cmd2 [ a = a ] && [ b = b ]: POSIX reliable equivalent [ a = a -a b = b ]: almost equivalent, but deprecated by POSIX because it is insane and fails for some values of a or b like ! or ( which would be interpreted as logical operations ( [[ (a = a || a = b) && a = b ]]: false. Without ( ), would be true because [[ && ]] has greater precedence than [[ || ]] [ ( a = a ) ]: syntax error, () is interpreted as a subshell [ \( a = a -o a = b \) -a a = b ]: equivalent, but (), -a, and -o are deprecated by POSIX. Without \( \) would be true because -a has greater precedence than -o { [ a = a ] || [ a = b ]; } && [ a = b ] non-deprecated POSIX equivalent. In this particular case however, we could have written just: [ a = a ] || [ a = b ] && [ a = b ] because the || and && shell operators have equal precedence unlike [[ || ]] and [[ && ]] and -o, -a and [ word splitting and filename generation upon expansions (split+glob) x='a b'; [[ $x = 'a b' ]]: true, quotes not needed x='a b'; [ $x = 'a b' ]: syntax error, expands to [ a b = 'a b' ] x='*'; [ $x = 'a b' ]: syntax error if there's more than one file in the current directory. x='a b'; [ "$x" = 'a b' ]: POSIX equivalent = [[ ab = a? ]]: true, because it does pattern matching (* ? [ are magic). Does not glob expand to files in current directory. [ ab = a? ]: a? glob expands. So may be true or false depending on the files in the current directory. [ ab = a\? ]: false, not glob expansion = and == are the same in both [ and [[, but == is a Bash extension. case ab in (a?) echo match; esac: POSIX equivalent [[ ab =~ 'ab?' ]]: false, loses magic with '' in Bash 3.2 and above and provided compatibility to bash 3.1 is not enabled (like with BASH_COMPAT=3.1) [[ ab? =~ 'ab?' ]]: true =~ [[ ab =~ ab? ]]: true, POSIX extended regular expression match, ? does not glob expand [ a =~ a ]: syntax error. No bash equivalent. printf 'ab\n' | grep -Eq 'ab?': POSIX equivalent (single line data only) awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX equivalent.

建议:始终使用[]

我所见过的每个[[]]结构都有POSIX等价物。

如果你使用[[]],你:

失去的可移植性 强迫读者学习另一个bash扩展的复杂性。[只是一个有奇怪名字的常规命令,没有涉及特殊的语义。

感谢Stéphane Chazelas的重要更正和补充。


Bash手册说:

当与[[一起使用时,' < '和' > '操作符将按字典顺序排序 使用当前区域设置。test命令使用ASCII码 排序。

(test命令与[]相同)


像printf一样是内置的。Bash语法希望在与命令相同的位置看到它。并且]对Bash来说没什么,除了[内置的。(man bash / SHELL内置命令) [[是类似if的关键字。Bash语法开始时也期望它与命令位于同一位置,但它不是执行它,而是进入条件上下文。[]也是结束此上下文的关键字。(man bash / SHELL语法/复合命令)

格式关键字>用户别名>内置函数>用户函数> $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兼容性,这意味着你不关心,所以不要关心获得“更多”兼容性的脚本。