原文:Differences Between Single and Double Brackets in Bash

概述

当我们在 Bash 中做变量比较时,通常可以交换地使用单括号 [] 和双括号 [[]]。比如,我们可以使用表达式 [ 3 -eq 3 ][[ 3 -eq 3 ]] 来比较 3 是否等于 3。两个表达式都会执行成功,那两者的区别是什么呢?

在本文中,我们会讨论单括号和双括号之间的一些区别。

主要区别

在本节中,我们会讨论单括号和双括号之间的主要区别。

单括号

在 Unix 和 Linux 中,[ 是用于执行表达式的内置命令,我们可以使用 type 命令进行验证:

$ type [
[ is a shell builtin

[test 内置命令的替代,两者可以交换使用:

$ [ 3 -eq 3 ] && echo "Numbers are equal"
Numbers are equal
$ test 3 -eq 3 && echo "Numbers are equal"
Numbers are equal

[test 的唯一区别在于:使用 [ 时,需要以 ] 结尾,并且括号前后需要包含空格。

双括号

[[]] 是由 Korn Shell 第一次引入,其增强了 [] 在脚本中做比较、测试的功能,我们可以认为它是 [] 的增强版。

在 Bash 和 zsh 中,我们可以使用 [[]] 的方式,但脚本可能不向后兼容 POSIX

[[ 是 shell 的一个关键字,让我们再次使用 type 命令进行验证:

$ type [[
[[ is a shell keyword

其它区别

在本节中,我们会讨论单括号和双括号之间的其它区别。

比较操作符

[[]] 中可以使用比较操作符,如 >< 等,如下:

$ [[ 1 < 2 ]] && echo "1 is less than 2"
1 is less than 2

上述命令中,我们使用 < 符号来检查 1 是否小于 2,命令是可以运行。但如果使用 [] 时,命令会报错:

$ [ 1 < 2 ] && echo "1 is less than 2"
bash: 2: No such file or directory

在这种情况下,Bash 会认为 < 是一个重定向符。因此,我们需要使用转义符 \ 进行转义:

$ [ 1 \< 2 ] && echo "1 is less than 2"
1 is less than 2

现在,使用 [] 也可以执行成功。

类似的,对于 > 符号也需要使用转义符 \ 进行转义。

对整数比较符 -eq-ne-gt-lt-ge-le[][[]] 都可以正常比较,如下:

$ [[ 1 -lt 2 ]] && echo "1 is less than 2"
1 is less than 2
$ [ 1 -lt 2 ] && echo "1 is less than 2"
1 is less than 2

布尔操作符

[[]] 中,我们可以使用逻辑运算 &&||,如下:

$ [[ 3 -eq 3 && 4 -eq 4 ]] && echo "Numbers are equal"
Numbers are equal

但是在 [] 中,我们必须使用 -a-o 分别代替 &&||,如下:

$ [ 3 -eq 3 -a 4 -eq 4 ] && echo "Numbers are equal"
Numbers are equal

聚合表达式

[[]] 中,我们可以使用括号 () 来聚合多个表达式,() 的使用可以让脚本更具可读性,如下:

$ [[ 3 -eq 3 && (2 -eq 2 && 1 -eq 1) ]] && echo "Parentheses can be used"
Parentheses can be used

上述命令中,我们使用了 () 对表达式 2 -eq 2 && 1 -eq 1 做了聚合,然后将其作为第 2 个表达式参与到 && 运算中,最后结果为验证为真。

但如果在 [] 中使用 (),则会报语法错误,如下:

$ [ 3 -eq 3 -a (2 -eq 2 -a 1 -eq 1) ] && echo "Parentheses can be used"
bash: syntax error near unexpected token `('

上述 [] 中,我们使用 -a 来代替 && 但还是得到了一个报错。

在这里,我们必须使用转义符对括号进行转义,并且前后要保留一个空格,如下:

$ [ 3 -eq 3 -a \( 2 -eq 2 -a 1 -eq 1 \) ] && echo "Parentheses can be used"
Parentheses can be used

通过上面的改造,命令就可以成功执行。

模式匹配

[[]] 中,我们还可以使用通配符进行模式匹配,如下:

$ name=Alice
$ [[ $name = *c* ]] && echo "Name includes c"
Name includes c
$ echo $?
0
  • name=Alice:完成变量赋值
  • 使用 $name = *c* 检查变量中是否包含了 c
  • $? 为 0 表示成功执行

如果把 [[]] 换成 [] 就无法成功执行,如下:

$ name=Alice
$ [ $name = *c* ] && echo "Name includes c"
bash: [: too many arguments
$ echo $?
2

在上述命令中,我们用 [] 代替了 [[]],运行就会出错,这是因为 [ 本身是内置的 Shell 命令,而该命令不支持这么多的参数。

正则表达式

正则表达式是另一种字符串模式匹配的方式,在 [[]] 中,我们可以使用正则表达式来做模式匹配的工作。

$ name=Alice
$ [[ $name =~ ^Ali ]] && echo "Regular expressions can be used"
Regular expressions can be used

首先,我们将值 “Alice” 赋值给 name 变量。然后,我们使用正则表达式来检查变量是否以 Ali 开头,其中 =~ 操作符可以用于正则表达式的匹配、^ 符号表示开头。最后,终端输出了第 2 个命令的执行结果。

那如果在 [] 中使用正则表达式呢?

$ name=Alice
$ [ $name =~ ^Ali ] && echo "Regular expressions can be used"
bash: [: =~: binary operator expected

我们得到了一个错误,所以得到结论:[] 中不能使用正则表达式

单词分割

[[]] 中,Bash 不会对值中的单词进行分割,比如变量的值是一个包含空格的字符串,Bash 不会将其分割成多个单词。

$ filename="none existent file"
$ [[ ! -e $filename ]] && echo -n "File doesn't exist;" && echo $filename
File doesn't exist;none existent file

上面命令中,我们用一个不存在的文件作为示例,检查文件是否存在,但是值包含字符串。在 [[]] 中,我们可以直接使用 filename 变量,那在 [] 又如何?

$ filename="none existent file"
$ [ ! -e $filename ] && echo -n "File doesn't exist;" && echo $filename
[: too many arguments

上面命令中,我们得到了一个错误。如果希望在 [] 也能使用带空格的字符串,需要加上双引号,如下:

$ filename="none existent file"
$ [ ! -e "$filename" ] && echo -n "File doesn't exist;" && echo $filename
File doesn't exist;none existent file

结论

在本文中,我们讨论了单方括号和双方括号在 Bash 中的区别。

[ 是内置的命令,并且其历史要久于 [[,作为后继者的 [[]][] 的增强版本。如果希望脚本更具兼容性,推荐使用 [],如果希望脚本更具可读性,推荐使用 [[]]

在示例中,我们看到在 [][[]] 中都可以使用比较操作符、布尔操作符以及聚合表达式,但也有一些区别。另外,正则表达式和单词分割只有能在 [[]] 中使用。

示例代码可在 Github 地址 中查看。