ChatGPT解决这个技术问题 Extra ChatGPT

在 Bash 中,双方括号 [[ ]] 是否优于单方括号 [ ]?

一位同事最近在代码审查中声称,在类似的结构中,[[ ]] 构造优于 [ ]

if [ "`id -nu`" = "$someuser" ] ; then
     echo "I love you madly, $someuser"
fi

他无法提供理由。有吗?

保持灵活,有时允许自己听取建议而不需要深入解释 :) 至于 [[ 的代码是好的和清晰的,但请记住那天你将使用默认 shell 将脚本移植到系统上不是 bashksh 等。[ 更丑陋、更麻烦,但在任何情况下都可以用作 AK-47
@rook 当然,您可以在没有深入解释的情况下听取建议。但是,当您要求解释但不明白时,这通常是一个危险信号。 “信任,但要验证”等等。
@rook 换句话说“做你被告知的事情,不要问问题”
@rook 这没有任何意义。

c
codeforester

[[ 的惊喜较少,通常使用起来更安全。但它不是可移植的——POSIX 没有指定它的作用,只有一些 shell 支持它(除了 bash,我听说 ksh 也支持它)。例如,你可以做

[[ -e $b ]]

测试文件是否存在。但是对于 [,您必须引用 $b,因为它会拆分参数并扩展诸如 "a*" 之类的内容(其中 [[ 是字面意思)。这也与 [ 如何成为外部程序并像其他所有程序一样正常接收其参数有关(尽管它也可以是内置程序,但它仍然没有这种特殊处理)。

[[ 还具有其他一些不错的功能,例如与 =~ 匹配的正则表达式以及在类 C 语言中已知的运算符。这是一个很好的页面:What is the difference between test, [ and [[ ?Bash Tests


考虑到如今 bash 无处不在,我倾向于认为它非常便携。对我来说唯一常见的例外是在busybox平台上——但无论如何你可能都想为它做出特别的努力,因为它运行在硬件上。
@guns:确实。我建议您的第二句话反驳您的第一句话;如果您将软件开发视为一个整体,“bash 无处不在”和“例外是busybox 平台”是完全不兼容的。 busybox 广泛用于嵌入式开发。
@guns:即使 bash 安装在我的盒子上,它也可能不会执行脚本(我可能将 /bin/sh 符号链接到一些破折号变体,这是 FreeBSD 和 Ubuntu 上的标准)。 Busybox 也有一些奇怪的地方:现在使用标准编译选项,它解析 [[ ]] 但将其解释为与 [ ] 相同的含义。
@dubiousjim:如果你使用 bash-only 结构(比如这个),你应该有#!/bin/bash,而不是#!/bin/sh。
这就是为什么我让我的脚本使用 #!/bin/sh,然后在我依赖某些 BASH 特定功能时将它们切换为使用 #!/bin/bash,以表示它不再是 Bourne shell 可移植的。
P
Peter Mortensen

行为差异

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 的重要更正和补充。


这是否成立“在 POSIX 中有效的一切都在 BASH 中有效,但反之则不然。” ?
@Wlad Bash 极大地扩展了 POSIX,因此任何 Bash 扩展都不会是 POSIX。反过来,我不是 100%,但感觉很可能(除非 Bash 扩展覆盖 POSIX 语法,例如,在 posix [[ 中可能也是常规命令)。相关:askubuntu.com/questions/766270/…
@PauloPedroso obrigado!掌握像 Bash 这样的传统技术既有美感,也有恐怖。
你为什么不写 > 也是 redirectiongreater than
[[ 相当明智的行为相比,阅读 [ 的所有怪异行为很有趣,并且仅基于可移植性(如今很少关注)和人们提出使用 [ 的建议必须学习 bash 版本的“复杂性”。这更像是您必须了解旧版本的荒谬错综复杂。 bash 版本可以满足您对大多数其他编程语言的期望。
P
Peter Mortensen

[[ ]] 具有更多功能 - 我建议您查看 Advanced Bash Scripting Guide 以了解更多信息,特别是 Chapter 7. Tests 中的 extended test command 部分。

顺便提一下,正如指南所指出的,[[ ]] 是在 ksh88(KornShell 的 1988 年版本)中引入的。


这远非 bashism,它最初是在 Korn shell 中引入的。
@Thomas,ABS 实际上在许多圈子中被认为是一个非常糟糕的参考;虽然它有大量准确的信息,但它往往很少注意避免在其示例中展示不良做法,并且在其生命中的大部分时间都处于无人维护的状态。
@CharlesDuffy 感谢您的评论,您能说出一个好的选择吗?我不是 shell 脚本专家,我正在寻找可以参考的指南,以便大约每半年编写一次脚本。
@Thomas,Wooledge BashFAQ 和相关的 wiki 是我使用的;它们由 Freenode #bash 频道的居民积极维护(虽然有时很棘手,但他们往往非常关心正确性)。 BashFAQ #31,mywiki.wooledge.org/BashFAQ/031,是与这个问题直接相关的页面。
P
Peter Mortensen

来自 Which comparator, test, bracket, or double bracket, is fastest?

双括号是一个“复合命令”,其中 test 和单括号是 shell 内置的(实际上是相同的命令)。因此,单括号和双括号执行不同的代码。测试和单括号是最便携的,因为它们作为单独的外部命令存在。但是,如果您使用任何远程现代版本的 BASH,则支持双括号。


shell 脚本中对 fastest 的痴迷是怎么回事?我希望它最便携,并且不在乎 [[ 可能带来的改进。但是,我是个老派老屁:-)
我认为许多 shell 证明了 [test 的内置版本,即使也存在外部版本。
@Jens 总的来说我同意:脚本的全部目的是(是?)可移植性(否则,我们将编写代码和编译,而不是脚本)......我能想到的两个例外是:(1)制表符完成(其中完成脚本可能会因大量条件逻辑而变得非常长); (2) 超级提示(PS1=...crazy stuff... 和/或 $PROMPT_COMMAND);对于这些,我不希望在执行脚本时出现任何可察觉的延迟
对最快的一些痴迷只是风格。在其他条件相同的情况下,为什么不将更有效的代码构造合并到您的默认样式中,尤其是如果该构造还提供了更高的可读性?就可移植性而言,bash 适合的许多任务本质上是不可移植的。例如,如果自上次运行以来已超过 X 小时,我需要运行 apt-get update。当人们可以将可移植性从已经太长的代码约束列表中剔除时,这是一种极大的解脱。
l
li ki

如果您关注 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


Google 写了“没有路径名扩展 ... 发生”但 [[ -d ~ ]] 返回 true(这意味着 ~ 已扩展为 /home/user)。我认为谷歌的写作应该更准确。
@JamesThomasMoon1979 这是波浪号扩展,而不是谷歌文本中提到的路径名扩展
无需在 [[ ]] 内的“文件名”周围加上引号。
M
Mark Reed

在标题中明确包含“in Bash”的标记为“bash”的问题中,我对所有回复说您应该避免使用 [[...]] 感到有点惊讶,因为它只适用于 bash!

确实,可移植性是主要的反对意见:如果您想编写一个在 Bourne 兼容的 shell 中工作的 shell 脚本,即使它们不是 bash,您应该避免使用 [[...]]。 (如果你想在更严格的 POSIX shell 中测试你的 shell 脚本,我推荐 dash;虽然它是一个不完整的 POSIX 实现,因为它缺乏标准所需的国际化支持,但它也缺乏对许多非在 bash、ksh、zsh 等中发现的 POSIX 结构)

我看到的另一个反对意见至少适用于 bash 的假设:[[...]] 有自己必须学习的特殊规则,而 [...] 的行为就像另一个命令。这又是真的(桑蒂利先生带来了显示所有差异的收据),但差异是好是坏是相当主观的。我个人发现双括号结构让我可以使用 (...) 进行分组,使用 &&|| 进行布尔逻辑,使用 <> 进行比较以及不带引号的参数,这让我感到很自由扩展。它就像它自己的小封闭世界,表达式的工作方式更像是在传统的非命令 shell 编程语言中。

我没有看到的一点是 [[...]] 的这种行为与算术扩展构造 $((...)) 的行为完全一致, 由 POSIX 指定,并且还允许不带引号的括号以及布尔和不等式运算符(这里执行数字而不是词法比较)。本质上,每当您看到双括号字符时,您都会获得相同的引号屏蔽效果。

(Bash 及其现代亲戚也使用 ((...)) - 没有前导 $ - 作为 C 样式的 for 循环头或执行算术运算的环境;这两种语法都不是 POSIX 的一部分.)

所以有一些很好的理由更喜欢 [[...]];也有理由避免它,这可能适用于您的环境,也可能不适用。至于你的同事,“我们的风格指南这么说”是一个有效的理由,就它而言,但我也会从理解风格指南为什么推荐它的人那里寻找背景故事。


P
Peter Mortensen

您无法使用 [[ 的典型情况是在 autotools configure.ac 脚本中。括号有特殊和不同的含义,所以你必须使用 test 而不是 [[[ -- 请注意 test 和 [ 是同一个程序。


鉴于 autotools 不是 POSIX shell,您为什么会期望 [ 被定义为 POSIX shell 函数?
因为 autoconf 脚本看起来像一个 shell 脚本,它会生成一个 shell 脚本,大多数 shell 命令都在其中运行。
P
Peter Mortensen

[[ ]] 双括号在某些版本的 SunOS 下不受支持,并且在函数声明中完全不受支持:

GNU Bash,版本 2.02.0(1)-release (sparc-sun-solaris2.6)


非常真实,而且一点也不无关紧要。必须考虑跨旧版本的 bash 可移植性。人们说“bash 无处不在且可移植,除了可能(在此处插入深奥的操作系统)”——但根据我的经验,solaris 是必须特别注意可移植性的平台之一:不仅要考虑旧的 bash 版本较新的操作系统、带有数组、函数等的问题/错误;但即使是 tr、sed、awk、tar 之类的实用程序(在脚本中使用)在 solaris 上也有你必须解决的奇怪和特性。
你是对的......在Solaris上有这么多非POSIX的实用程序,只需看看“df”输出和参数......对Sun感到羞耻。希望它会一点一点地消失(加拿大除外)。
Solaris 2.6 严重吗?它于 1997 年发布,并于 2006 年结束支持。我想如果您仍在使用它,那么您还有其他问题!顺便说一句,它使用了 Bash v2.02,它引入了双括号,因此即使在这么古老的东西上也应该可以工作。 2005 年的 Solaris 10 使用 Bash 3.2.51,而 2011 年的 Solaris 11 使用 Bash 4.1.11。
重新编写脚本的可移植性。在 Linux 系统上,每个工具通常只有一个版本,即 GNU 版本。在 Solaris 上,您通常可以选择 Solaris 原生版本或 GNU 版本(例如 Solaris tar 与 GNU tar)。如果你依赖于 GNU 特定的扩展,那么你必须在你的脚本中声明它是可移植的。在 Solaris 上,您可以通过添加前缀“g”来做到这一点,例如,如果您想要 GNU grep,请使用 `ggrep`。
u
unix4linux

简而言之, [[ 更好,因为它不会分叉另一个进程。没有括号或单括号比双括号慢,因为它分叉了另一个进程。


Test 和 [ 是 bash 中相同内置命令的名称。尝试使用 type [ 来查看。
@alberge,没错,但是 [[[ 不同,是由 bash 命令行解释器解释的语法。在 bash 中,尝试输入 type [[。 unix4linux 是正确的,尽管经典的 Bourne-shell [ 测试派生了一个新进程以确定真值,但 [[ 语法(从 ksh 借用 bash、zsh 等)却没有。
@Tim,我不确定您说的是哪个 Bourne shell,但 [ 内置于 Bash 和 Dash(所有 Debian 派生的 Linux 发行版中的 /bin/sh)。
哦,我明白你的意思了,这是真的。我正在考虑类似旧 Solaris 或 HP/UX 系统上的 /bin/sh 之类的东西,但当然,如果您需要与那些您也不会使用 [[ 的系统兼容。
@alberge Bourne shell 不是 Bash(又名 Bourne Again SHell)。