一位同事最近在代码审查中声称,在类似的结构中,[[ ]]
构造优于 [ ]
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
他无法提供理由。有吗?
[[
的代码是好的和清晰的,但请记住那天你将使用默认 shell 将脚本移植到系统上不是 bash
或 ksh
等。[
更丑陋、更麻烦,但在任何情况下都可以用作 AK-47
。
[[
的惊喜较少,通常使用起来更安全。但它不是可移植的——POSIX 没有指定它的作用,只有一些 shell 支持它(除了 bash,我听说 ksh 也支持它)。例如,你可以做
[[ -e $b ]]
测试文件是否存在。但是对于 [
,您必须引用 $b
,因为它会拆分参数并扩展诸如 "a*"
之类的内容(其中 [[
是字面意思)。这也与 [
如何成为外部程序并像其他所有程序一样正常接收其参数有关(尽管它也可以是内置程序,但它仍然没有这种特殊处理)。
[[
还具有其他一些不错的功能,例如与 =~
匹配的正则表达式以及在类 C 语言中已知的运算符。这是一个很好的页面:What is the difference between test, [
and [[
? 和 Bash Tests
行为差异
Bash 4.3.11 的一些区别:
POSIX 与 Bash 扩展:[ 是 POSIX [[ 是受 KornShell 启发的 Bash 扩展
是 POSIX
[[ 是一个受 KornShell 启发的 Bash 扩展
常规命令与魔术 [ 只是一个名称奇怪的常规命令。 ] 只是 [ 的最后一个参数。 Ubuntu 16.04 实际上在 /usr/bin/[ 由 coreutils 提供了一个可执行文件,但 Bash 内置版本优先。 Bash 解析命令的方式没有任何改变。特别是, < 是重定向, && 和 ||连接多个命令, ( ) 生成子shell,除非被 \ 转义,并且单词扩展照常发生。 [[ X ]] 是一个使 X 被神奇地解析的单一结构。 <, &&, ||和 () 被特殊对待,分词规则不同。还有其他区别,例如 = 和 =~。在 Bashese 中:[ 是内置命令,而 [[ 是关键字:shell builtin 和 shell 关键字有什么区别?
只是一个带有奇怪名称的常规命令。 ] 只是 [ 的最后一个参数。 Ubuntu 16.04 实际上在 /usr/bin/[ 由 coreutils 提供了一个可执行文件,但 Bash 内置版本优先。 Bash 解析命令的方式没有任何改变。特别是, < 是重定向, && 和 ||连接多个命令, ( ) 生成子shell,除非被 \ 转义,并且单词扩展照常发生。
[[ 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' ]]:是的。不需要引号 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' ]]:是的。不需要报价
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? ]: 一个?球体扩大。因此,根据当前目录中的文件,它可能是 true 或 false。 [ 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? ]: 一个?球体扩大。因此,根据当前目录中的文件,它可能是 true 或 false。
[ 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? ]]: 真的。 POSIX 扩展正则表达式匹配和?不全局展开 [ a =~ a ]:语法错误。没有 Bash 等价物。 printf 'ab\n' | grep -Eq 'ab?':POSIX 等效(仅单行数据) awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?':POSIX 等效。
[[ ab =~ ab? ]]: 真的。 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 的重要更正和补充。
[[
中可能也是常规命令)。相关:askubuntu.com/questions/766270/…
>
也是 redirection
和 greater than
[[
相当明智的行为相比,阅读 [
的所有怪异行为很有趣,并且仅基于可移植性(如今很少关注)和人们提出使用 [
的建议必须学习 bash 版本的“复杂性”。这更像是您必须了解旧版本的荒谬错综复杂。 bash 版本可以满足您对大多数其他编程语言的期望。
[[ ]]
具有更多功能 - 我建议您查看 Advanced Bash Scripting Guide 以了解更多信息,特别是 Chapter 7. Tests 中的 extended test command 部分。
顺便提一下,正如指南所指出的,[[ ]]
是在 ksh88(KornShell 的 1988 年版本)中引入的。
来自 Which comparator, test, bracket, or double bracket, is fastest?:
双括号是一个“复合命令”,其中 test 和单括号是 shell 内置的(实际上是相同的命令)。因此,单括号和双括号执行不同的代码。测试和单括号是最便携的,因为它们作为单独的外部命令存在。但是,如果您使用任何远程现代版本的 BASH,则支持双括号。
[[
可能带来的改进。但是,我是个老派老屁:-)
[
和 test
的内置版本,即使也存在外部版本。
PS1=...crazy stuff...
和/或 $PROMPT_COMMAND
);对于这些,我不希望在执行脚本时出现任何可察觉的延迟。
apt-get update
。当人们可以将可移植性从已经太长的代码约束列表中剔除时,这是一种极大的解脱。
如果您关注 Google's style guide:
Test、[ … ] 和 [[ … ]] [[ … ]] 优于 [ … ]、test 和 /usr/bin/[。 [[ … ]] 减少了错误,因为 [[ 和 ]] 之间没有路径名扩展或分词。此外,[[ … ]] 允许正则表达式匹配,而 [ … ] 不允许。 # 这确保左边的字符串由 # alnum 字符类中的字符组成,后跟字符串名称。 # 请注意,此处不应引用 RHS。 if [[ "文件名" =~ ^[[:alnum:]]+name ]];然后 echo "Match" fi # 这匹配精确的模式 "f*" (在这种情况下不匹配) if [[ "filename" == "f*" ]];然后 echo "Match" fi # 这给出了一个“参数太多”的错误,因为 f* 被扩展为当前目录的 # 内容 if [ "filename" == f* ];然后 echo "Match" fi 有关血腥的详细信息,请参阅 http://tiswww.case.edu/php/chet/bash/FAQ 上的 E14
[[ -d ~ ]]
返回 true(这意味着 ~
已扩展为 /home/user
)。我认为谷歌的写作应该更准确。
在标题中明确包含“in Bash”的标记为“bash”的问题中,我对所有回复说您应该避免使用 [[
...]]
感到有点惊讶,因为它只适用于 bash!
确实,可移植性是主要的反对意见:如果您想编写一个在 Bourne 兼容的 shell 中工作的 shell 脚本,即使它们不是 bash,您应该避免使用 [[
...]]
。 (如果你想在更严格的 POSIX shell 中测试你的 shell 脚本,我推荐 dash
;虽然它是一个不完整的 POSIX 实现,因为它缺乏标准所需的国际化支持,但它也缺乏对许多非在 bash、ksh、zsh 等中发现的 POSIX 结构)
我看到的另一个反对意见至少适用于 bash 的假设:[[
...]]
有自己必须学习的特殊规则,而 [
...]
的行为就像另一个命令。这又是真的(桑蒂利先生带来了显示所有差异的收据),但差异是好是坏是相当主观的。我个人发现双括号结构让我可以使用 (
...)
进行分组,使用 &&
和 ||
进行布尔逻辑,使用 <
和 >
进行比较以及不带引号的参数,这让我感到很自由扩展。它就像它自己的小封闭世界,表达式的工作方式更像是在传统的非命令 shell 编程语言中。
我没有看到的一点是 [[
...]]
的这种行为与算术扩展构造 $((
...))
的行为完全一致,是 由 POSIX 指定,并且还允许不带引号的括号以及布尔和不等式运算符(这里执行数字而不是词法比较)。本质上,每当您看到双括号字符时,您都会获得相同的引号屏蔽效果。
(Bash 及其现代亲戚也使用 ((
...))
- 没有前导 $
- 作为 C 样式的 for
循环头或执行算术运算的环境;这两种语法都不是 POSIX 的一部分.)
所以有一些很好的理由更喜欢 [[
...]]
;也有理由避免它,这可能适用于您的环境,也可能不适用。至于你的同事,“我们的风格指南这么说”是一个有效的理由,就它而言,但我也会从理解风格指南为什么推荐它的人那里寻找背景故事。
您无法使用 [[
的典型情况是在 autotools configure.ac 脚本中。括号有特殊和不同的含义,所以你必须使用 test
而不是 [
或 [[
-- 请注意 test 和 [
是同一个程序。
[
被定义为 POSIX shell 函数?
[[ ]] 双括号在某些版本的 SunOS 下不受支持,并且在函数声明中完全不受支持:
GNU Bash,版本 2.02.0(1)-release (sparc-sun-solaris2.6)
简而言之, [[ 更好,因为它不会分叉另一个进程。没有括号或单括号比双括号慢,因为它分叉了另一个进程。
type [
来查看。
[[
与 [
不同,是由 bash 命令行解释器解释的语法。在 bash 中,尝试输入 type [[
。 unix4linux 是正确的,尽管经典的 Bourne-shell [
测试派生了一个新进程以确定真值,但 [[
语法(从 ksh 借用 bash、zsh 等)却没有。
[
内置于 Bash 和 Dash(所有 Debian 派生的 Linux 发行版中的 /bin/sh
)。
[[ ]]
但将其解释为与[ ]
相同的含义。#!/bin/sh
,然后在我依赖某些 BASH 特定功能时将它们切换为使用#!/bin/bash
,以表示它不再是 Bourne shell 可移植的。