我正在阅读有关 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 manual section on conditional constructs 中找到更完整的差异列表。
每当您希望脚本可跨 shell 移植时,请使用 []
。如果您想要 []
不支持且不需要可移植的条件表达式,请使用 [[]]
。
行为差异
在 Bash 4.3.11 中测试:
POSIX 与 Bash 扩展:[ 是 POSIX [[ 是受 Korn shell 启发的 Bash 扩展
是 POSIX
[[ 是一个受 Korn shell 启发的 Bash 扩展
常规命令与魔术 [ 只是一个名称奇怪的常规命令。 ] 只是 [ 的最后一个参数。 Ubuntu 16.04 实际上在 coreutils 提供的 /usr/bin/[ 中有一个可执行文件,但 bash 内置版本优先。 Bash 解析命令的方式没有任何改变。特别是, < 是重定向, && 和 ||连接多个命令, ( ) 生成子shell,除非被 \ 转义,并且单词扩展照常发生。 [[ X ]] 是一个使 X 被神奇地解析的单一结构。 <, &&, ||和 () 被特殊对待,分词规则不同。还有其他区别,例如 = 和 =~。在 Bashese 中: [ 是内置命令, [[ 是关键字:https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword
[ 只是一个带有奇怪名称的常规命令。 ] 只是 [ 的最后一个参数。
[[ X ]] 是一个使 X 被神奇地解析的单一结构。 <, &&, ||和 () 被特殊对待,分词规则不同。还有其他区别,例如 = 和 =~。
< [[ a < b ]]:字典比较 [ a \< b ]:同上。 \ 需要,否则会像任何其他命令一样进行重定向。 Bash 扩展。 expr x"$x" \< x"$y" > /dev/null 或 ["$(expr x"$x" \< x"$y")" = 1 ]:POSIX 等价物,请参阅:如何测试Bash中字典小于或等于的字符串?
[[ a < b ]]:字典比较
a \< b ]:同上。 \ 需要,否则会像任何其他命令一样进行重定向。 Bash 扩展。
expr x"$x" \< x"$y" > /dev/null 或 ["$(expr x"$x" \< x"$y")" = 1 ]:POSIX 等价物,请参阅:如何测试Bash中字典小于或等于的字符串?
&& 和 || [[ a = a && b = b ]]: true, logical and [ a = a && b = b ]: 语法错误,&& 被解析为 AND 命令分隔符 cmd1 && cmd2 [ a = a ] && [ b = b ] : POSIX 可靠等价物 [ a = a -ab = b ]:几乎等价,但被 POSIX 弃用,因为它是疯狂的,并且对于 a 或 b 的某些值失败,例如!或 ( 将被解释为逻辑运算
[[ a = a && b = b ]]:真实,逻辑和
a = a && b = b ]:语法错误,&& 被解析为 AND 命令分隔符 cmd1 && cmd2
[ a = a ] && [ b = b ]:POSIX 可靠等价物
a = a -ab = b ]:几乎等效,但被 POSIX 弃用,因为它很疯狂,并且对于 a 或 b 的某些值(例如!或 ( 将被解释为逻辑运算
[[ (a = a || a = b) && a = b ]]: false。如果没有 ( ),则为真,因为 [[ && ]] 的优先级高于 [[ || ]] [ ( a = a ) ]:语法错误,() 被解释为子 shell [ \( a = a -oa = b \) -aa = b ]:等效,但 ()、-a 和 -o 被 POSIX 弃用。没有 \ ( \) 将是正确的,因为 -a 的优先级高于 -o { [ a = a ] || [ a = b ]; } && [ a = b ] 不推荐使用的 POSIX 等效项。然而,在这种特殊情况下,我们可以刚刚写了: [ a = a ] || [ a = b ] && [ a = b ] 因为 || 和 && shell 运算符具有相同的优先级,不像 [[ || ]] 和 [[ && ]] 和 -o, -a 和 [
[[ (a = a || a = b) && a = b ]]: 错误。如果没有 ( ),则为真,因为 [[ && ]] 的优先级高于 [[ || ]]
[ ( a = a ) ]: 语法错误, () 被解释为子shell
\( a = a -oa = b \) -aa = b ]:等效,但 ()、-a 和 -o 已被 POSIX 弃用。没有 \( \) 将是真的,因为 -a 比 -o 具有更高的优先级
[ 一 = 一 ] || [一=乙]; } && [ 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' ]: 语法错误,展开为 [ ab = 'a b' ] x='*'; [ $x = 'a b' ]:如果当前目录中有多个文件,则语法错误。 x='a b'; [ "$x" = 'a b' ]: POSIX 等价物
x='a b'; [[ $x = 'a b' ]]: true,不需要引号
x='a b'; [ $x = 'a b' ]:语法错误,扩展为 [ ab = 'a b' ]
x='*'; [ $x = 'a b' ]:如果当前目录中有多个文件,则语法错误。
x='a b'; [ "$x" = 'a b' ]: POSIX 等价物
[[ ab = a? ]]: true,因为它会进行模式匹配(* ? [ 是魔法)。不 glob 扩展到当前目录中的文件。 [ ab = a? ]: 一个?球体扩大。所以可能是真或假,具体取决于当前目录中的文件。 [ ab = a\? ]: false,不是全局扩展 = 和 == 在 [ 和 [[ 中是相同的,但 == 是 Bash 扩展。 (a?) 回显匹配中的 case ab; esac: POSIX 等价物 [[ ab =~ 'ab?' ]]: false,在 Bash 3.2 及更高版本中使用 '' 失去魔力,并且未启用对 bash 3.1 的兼容性(如 BASH_COMPAT=3.1)[[ ab? =〜'ab? ]]: 真的
[[ ab = a? ]]: true,因为它会进行模式匹配(* ? [ 是魔法)。不 glob 扩展到当前目录中的文件。
ab = a? ]: 一个?球体扩大。所以可能是真或假,具体取决于当前目录中的文件。
[ ab = a\? ]: false,不是全局扩展
和 == 在 [ 和 [[ 中是相同的,但 == 是 Bash 扩展。
(a?) 回显匹配中的 case ab; esac:POSIX 等价物
[[ ab =~'ab?' ]]: false,在 Bash 3.2 及更高版本中使用 '' 失去魔力,并且未启用对 bash 3.1 的兼容性(如 BASH_COMPAT=3.1)
[[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 等效项。
[[ 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 扩展的复杂性。 [ 只是一个名称怪异的常规命令,不涉及特殊语义。
感谢 Stéphane Chazelas 的重要更正和补充。
[]
应理解为 My preference :如果您不想失去可移植性,请使用 []
。如 here 所述:如果考虑到 POSIX 或 BourneShell 的可移植性/一致性,则应使用旧语法。另一方面,如果脚本需要 BASH、Zsh 或 KornShell,则新语法通常更灵活,但不一定向后兼容。 如果可以,我宁愿使用 [[ ab =~ ab? ]]
,而且不用担心比 printf 'ab' | grep -Eq 'ab?'
向后兼容
在用于条件测试的单括号内(即 [ ... ]),所有 shell 都支持某些运算符,例如单个 =
,而某些较旧的 shell 不支持使用运算符 ==
。
在条件测试的双括号内(即 [[ ... ]]),在新旧 shell 中使用 =
或 ==
没有区别。
编辑:我还应该注意:在 bash 中,如果可能,请始终使用双括号 [[ ... ]],因为它比单括号更安全。我将通过以下示例说明原因:
if [ $var == "hello" ]; then
如果 $var 恰好为 null / 空,那么这就是脚本所看到的:
if [ == "hello" ]; then
这会破坏你的脚本。解决方案是使用双括号,或者始终记住在变量周围加上引号 ("$var"
)。双括号是更好的防御性编码实践。
[[
是一个 bash 关键字,类似于(但比)[
命令更强大。
看
http://mywiki.wooledge.org/BashFAQ/031 和 http://mywiki.wooledge.org/BashGuide/TestsAndConditionals
除非您正在编写 POSIX sh,否则我们建议您使用 [[
。
[ 是类似 printf 的内置函数。 Bash 语法期望在与命令相同的位置看到它。并且 ] 对 Bash 来说没什么,只是它是 [ 内置函数所期望的。 (man bash / SHELL BUILTIN 命令)
[[ 是一个类似 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 兼容性,则意味着您不是,所以不要关心获得“更多”兼容的脚本不是。
您可以使用方括号进行轻度正则表达式匹配,例如:
if [[ $1 =~ "foo.*bar" ]] ; then
(只要您使用的 bash 版本支持此语法)
[[ ]]
的 shell(例如带有#!/bin/bash
或#!/usr/bin/env bash
的 bash)的 shebang 开头,您应该使用可移植选项。假设 /bin/sh 支持此类扩展的脚本将在操作系统(如最近的 Debian 和 Ubuntu 版本)上中断,但情况并非如此。