原文: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 地址 中查看。
评论