在 Bash 中,双方括号 [[ ]] 是不是比单方括号 [ ] 更可取?
Posted
技术标签:
【中文标题】在 Bash 中,双方括号 [[ ]] 是不是比单方括号 [ ] 更可取?【英文标题】:Are double square brackets [[ ]] preferable over single square brackets [ ] in Bash?在 Bash 中,双方括号 [[ ]] 是否比单方括号 [ ] 更可取? 【发布时间】:2010-10-14 17:50:13 【问题描述】:一位同事最近在代码审查中声称,[[ ]]
构造在类似构造中优于 [ ]
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
他无法提供理由。有吗?
【问题讨论】:
灵活一些,有时允许自己听取建议而不需要深入解释 :) 至于[[
的代码很好且清晰,但请记住移植脚本的那一天在默认 shell 不是bash
或ksh
的系统上,[
更丑陋、更麻烦,但在任何情况下都可以作为AK-47
工作。
@rook 你可以在没有深入解释的情况下听取建议,当然。但是,当您要求解释却没有得到解释时,这通常是一个危险信号。 “信任,但要验证”等等。
另见:What is the difference between the Bash operators [[ vs [ vs ( vs ((? on Unix & Linux SE。
@rook 换句话说“按你说的做,不要问问题”
@rook 这毫无意义。
【参考方案1】:
[[ ]]
具有更多功能 - 我建议您查看 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,是与此问题直接相关的页面。【参考方案2】:来自Which comparator, test, bracket, or double bracket, is fastest?:
双括号是“复合 命令”作为测试和单 括号是外壳内置的(和 实际是相同的命令)。因此, 单括号和双括号 执行不同的代码。
测试和单括号是 最便携,因为它们存在 单独的和外部的命令。 但是,如果您使用任何远程 现代版 BASH,双 支持括号。
【讨论】:
shell 脚本中对 fastest 的痴迷是怎么回事?我想要它最便携,并且不在乎[[
可能带来的改进。但是,我是个老派老屁 :-)
我认为许多 shell 证明了 [
和 test
的内置版本,即使外部版本也存在。
@Jens 总的来说我同意:脚本的全部目的是(是?)可移植性(否则,我们将编码和编译,而不是脚本)......我能想到的两个例外是:(1)制表符补全(补全脚本可能会因大量条件逻辑而变得很长); (2) 超级提示(PS1=...crazy stuff...
和/或$PROMPT_COMMAND
);对于这些,我不希望在执行脚本时出现任何可察觉的延迟。
对最快的迷恋只是风格。在其他条件相同的情况下,为什么不将更高效的代码构造合并到您的默认样式中,尤其是如果该构造还提供了更高的可读性?就可移植性而言,bash 适合的许多任务本质上是不可移植的。例如,如果自上次运行以来已经超过 X 小时,我需要运行 apt-get update
。当人们可以将可移植性从已经太长的代码约束列表中剔除时,这是一种极大的解脱。【参考方案3】:
[[
的惊喜较少,通常使用起来更安全。但它不是可移植的——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 可移植的。
【参考方案4】:
简而言之,[[ 更好,因为它不会派生另一个进程。没有括号或单括号比双括号慢,因为它分叉了另一个进程。
【讨论】:
Test 和 [ 是 bash 中相同内置命令的名称。尝试使用type [
来查看。
@alberge,没错,但[[
与[
不同,是由 bash 命令行解释器解释的语法。在 bash 中,尝试输入 type [[
。 unix4linux 是正确的,尽管经典的 Bourne-shell [
测试派生出一个新进程以确定真值,但 [[
语法(通过 bash、zsh 等从 ksh 借用)却没有。
@Tim,我不确定你说的是哪个 Bourne shell,但 [
是 Bash 和 Dash 的内置(/bin/sh
在所有 Debian 派生的 Linux分布)。
哦,我明白你的意思了,这是真的。我正在考虑类似旧 Solaris 或 HP/UX 系统上的 /bin/sh 之类的东西,但当然,如果你需要与那些你不会使用 [[ 的系统兼容。
@alberge Bourne shell 不是 Bash(又名 Bourne Again SHell)。【参考方案5】:
[[ ]] 双括号在 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”来做到这一点,例如` ggrep` 如果你想要 GNU grep。【参考方案6】:您无法使用[[
的典型情况是在 autotools configure.ac 脚本中。括号有特殊和不同的含义,所以你必须使用test
而不是[
或[[
——注意test和[
是同一个程序。
【讨论】:
鉴于 autotools 不是 POSIX shell,您为什么会期望[
被定义为 POSIX shell 函数?
因为autoconf脚本看起来像一个shell脚本,它会生成一个shell脚本,大部分shell命令都在里面运行。【参考方案7】:
行为差异
Bash 4.3.11 的一些差异:
POSIX 与 Bash 扩展:
[
is POSIX
[[
is a Bash extension 灵感来自 KornShell
常规命令与魔法
[
只是一个名称怪异的常规命令。
]
只是[
的最后一个参数。
Ubuntu 16.04 实际上在coreutils 提供的/usr/bin/[
上有一个可执行文件,但Bash 内置版本优先。
Bash 解析命令的方式没有任何改变。
特别是,<
是重定向,&&
和||
连接多个命令,( )
生成子shell,除非被\
转义,并且单词扩展照常发生。
[[ X ]]
是一个使X
被神奇地解析的单一结构。 <
、&&
、||
和()
被特殊对待,分词规则不同。
还有更多区别,例如=
和=~
。
在 Bashese 中:[
是内置命令,[[
是关键字:What's the difference between shell builtin and shell keyword?
<
[[ a < b ]]
: 字典比较
[ a \< b ]
:同上。 \
需要,否则会像任何其他命令一样进行重定向。 Bash 扩展。
expr x"$x" \< x"$y" > /dev/null
或 [ "$(expr x"$x" \< x"$y")" = 1 ]
:POSIX 等效项,请参阅:How to test strings for lexicographic less than or equal in Bash?
&&
和 ||
[[ a = a && b = b ]]
: 是的,符合逻辑的和
[ a = a && b = b ]
:语法错误,&&
被解析为 AND 命令分隔符 cmd1 && cmd2
[ a = a ] && [ b = b ]
:POSIX 可靠等价物
[ a = a -a b = b ]
:几乎等同,但被 POSIX 弃用,因为它很疯狂,并且对于 a
或 b
的某些值(如 !
或 (
)会失败,这将被解释为逻辑操作
(
[[ (a = a || a = b) && a = b ]]
:错误。如果没有( )
,那将是真的,因为[[ && ]]
的优先级高于[[ || ]]
[ ( a = a ) ]
:语法错误,()
被解释为子shell
[ \( a = a -o a = b \) -a a = b ]
:等效,但 ()
、-a
和 -o
已被 POSIX 弃用。如果没有\( \)
,那将是真的,因为-a
的优先级高于-o
[ a = a ] || [ a = b ]; && [ a = b ]
不推荐使用的 POSIX 等效项。然而,在这种特殊情况下,我们可以只写:[ a = a ] || [ a = b ] && [ a = b ]
,因为 ||
和 &&
shell 运算符具有相同的优先级,不像 [[ || ]]
和 [[ && ]]
和 -o
、-a
和 @987654393 @
扩展时的分词和文件名生成(split+glob)
x='a b'; [[ $x = 'a b' ]]
:是的。不需要报价
x='a b'; [ $x = 'a b' ]
:语法错误。它扩展到[ a b = 'a b' ]
x='*'; [ $x = 'a b' ]
: 如果当前目录中有多个文件,则语法错误。
x='a b'; [ "$x" = 'a b' ]
:POSIX 等效
=
[[ ab = a? ]]
:是的,因为它确实是pattern matching(* ? [
是魔法)。不 glob 扩展到当前目录中的文件。
[ ab = a? ]
:a?
全局扩展。因此,根据当前目录中的文件,它可能是 true 或 false。
[ ab = a\? ]
: 错误,不是全局扩展
=
和 ==
在 [
和 [[
中是相同的,但 ==
是 Bash 扩展。
case ab in (a?) echo match; esac
:POSIX 等效
[[ ab =~ 'ab?' ]]
: false,在 Bash 3.2 及更高版本中使用 ''
失去魔力,并且未启用与 Bash 3.1 的兼容性(如使用 BASH_COMPAT=3.1
)
[[ ab? =~ 'ab?' ]]
:是的
=~
[[ ab =~ ab? ]]
:是的。 POSIX extended regular expression 匹配且 ?
不会全局扩展
[ a =~ a ]
:语法错误。没有 Bash 等效项。
printf 'ab\n' | grep -Eq 'ab?'
:POSIX 等效项(仅限单行数据)
awk 'BEGINexit !(ARGV[1] ~ ARGV[2])' ab 'ab?'
:POSIX 等效项。
建议:始终使用[]
我见过的每个 [[ ]]
构造都有 POSIX 等效项。
如果你使用[[ ]]
你:
[
只是一个名字怪异的常规命令,不涉及特殊语义。
感谢Stéphane Chazelas 的重要更正和补充。
【讨论】:
这是否成立?“在 POSIX 中有效的一切都在 BASH 中有效,反之则不然。” ? @Wlad Bash 极大地扩展了 POSIX,因此任何 Bash 扩展都不会是 POSIX。反过来,我不是 100%,但感觉很可能(除非 Bash 扩展覆盖 POSIX 语法,例如在 posix[[
中可能也是常规命令)。相关:askubuntu.com/questions/766270/…
解释得很透彻!
@PauloPedroso obrigado!掌握 Bash 之类的传统技术既美丽又恐怖。
你为什么不写>
这也是redirection
和greater than
【参考方案8】:
如果你喜欢关注Google's style guide:
测试、
[ … ]
和[[ … ]]
[[ … ]]
优先于[ … ]
、test
和/usr/bin/[
。
[[ … ]]
减少了错误,因为在[[
和]]
之间不会发生路径名扩展或分词。另外,[[ … ]]
允许正则表达式匹配,而[ … ]
不允许。# This ensures the string on the left is made up of characters in # the alnum character class followed by the string name. # Note that the RHS should not be quoted here. if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi # This matches the exact pattern "f*" (Does not match in this case) if [[ "filename" == "f*" ]]; then echo "Match" fi
# This gives a "too many arguments" error as f* is expanded to the # contents of the current directory if [ "filename" == f* ]; then echo "Match" fi
有关血腥细节,请参阅 E14,http://tiswww.case.edu/php/chet/bash/FAQ
【讨论】:
Google 写了“没有路径名扩展 ... 发生”但[[ -d ~ ]]
返回 true(这意味着 ~
已扩展为 /home/user
)。我认为谷歌的写作应该更准确。
@JamesThomasMoon1979 这是波浪号扩展,不是谷歌文本中提到的路径名扩展
不需要在 [[ ]] 内的“文件名”周围加上引号。【参考方案9】:
在标题中明确包含“in Bash”的标记为“bash”的问题中,我对所有回复说您应该避免使用 [[
...]]
感到有点惊讶,因为它只适用于砰!
确实,可移植性是主要的反对意见:如果你想编写一个在 Bourne 兼容的 shell 中工作的 shell 脚本,即使它们不是 bash,你应该避免使用[[
...]]
。 (如果你想在更严格的 POSIX shell 中测试你的 shell 脚本,我推荐dash
;虽然它是一个不完整的 POSIX 实现,因为它缺乏标准所需的国际化支持,它也缺乏对许多非在 bash、ksh、zsh 等中发现的 POSIX 构造)
我看到的另一个反对意见至少适用于 bash 的假设:[[
...]]
有自己必须学习的特殊规则,而 [
...]
起作用就像另一个命令一样。这又是真的(桑蒂利先生带来了显示所有差异的收据),但差异是好是坏是相当主观的。我个人发现双括号结构让我可以使用 (
...)
进行分组,&&
和 ||
用于布尔逻辑,<
和 >
用于比较,以及不带引号的参数。扩展。它就像是一个封闭的小世界,表达式的工作方式更像是在传统的非命令 shell 编程语言中。
我没有看到的一点是 [[
...]]
的这种行为与算术扩展构造 $((
...))
的行为完全一致,是 由 POSIX 指定,并且还允许不带引号的括号以及布尔和不等式运算符(这里执行数字而不是词法比较)。本质上,任何时候您看到双括号字符都会获得相同的引号屏蔽效果。
(Bash 及其现代亲属也使用((
...))
- 没有前导$
- 作为C 风格的for
循环头或执行算术运算的环境;两种语法都不是POSIX 的一部分。)
所以有一些很好的理由更喜欢[[
...]]
;也有避免它的理由,这可能适用于您的环境,也可能不适用。至于你的同事,“我们的风格指南这么说”是一个有效的理由,就目前而言,但我也会从理解风格指南为什么推荐它的人那里寻找背景故事。
【讨论】:
以上是关于在 Bash 中,双方括号 [[ ]] 是不是比单方括号 [ ] 更可取?的主要内容,如果未能解决你的问题,请参考以下文章