为什么`[`是shell内置的,而`[[`是shell的关键字?


64

据我所知,[[是的增强版本[,但是当我看到它[[作为关键字并[显示为内置函数时,我感到困惑。

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP

内建函数可能是同名系统命令的同义词,但是Bash在内部重新实现它。例如,Bash echo命令与/ bin / echo不同,尽管它们的行为几乎相同。

关键字是保留字,令牌或运算符。关键字对shell具有特殊的含义,并且确实是shell语法的组成部分。举例来说,for和while都做!是关键字。类似于内置命令,关键字被硬编码到Bash中,但是与内置命令不同,关键字本身不是命令,而是命令构造的子单元。[2]

那不是既可以作为关键字[又可以[[作为关键字吗?我在这里想念什么吗?同样,此链接再次确认[[[应该属于同一种类。



9
/ bin / [在我的机器上。
2015年

2
由于两者之间的一个区别的一个简单的例子:if "[" $x -eq 3 ]按预期工作(因为猛砸查找称为命令[,并存在),但if "[[" $x -eq 3 ]]不能正常工作(因为再次猛砸搜索相应的名称的命令,但没有[[命令)。
凯尔·斯特兰德

1
@Joshua确实如此/usr/bin/echo,但这并不意味着它不是内置的
乔纳森·莱因哈特

如果它们不是内置的,那么也存在于外部的所有bulitins都会解析参数。
2015年

Answers:


80

[和之间的区别[[是非常根本的。

  • [是一个命令。其参数的处理方式与其他命令参数的处理方式相同。例如,考虑:

    [ -z $name ]

    外壳将扩大$name并进行双方对结果分词和文件名的产生,只是因为它会为任何其他命令。

    例如,以下操作将失败:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments

    为使此功能正常运行,必须使用引号:

    $ [ -n "$name" ] && echo not empty
    not empty
  • [[是shell关键字,其参数根据特殊规则进行处理。例如,考虑:

    [[ -z $name ]]

    外壳将扩大$name,但是,不同于其他任何命令,它将执行没有分词,也没有对结果文件名生成。例如,尽管嵌入了空格,以下操作仍将成功name

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty

摘要

[ 是一个命令,并且与外壳程序执行的所有其他命令遵循相同的规则。

因为[[是关键字而不是命令,所以Shell会对其进行特殊对待,并且它在非常不同的规则下运行。


+1。谢谢。您能否提供命令和关键字在什么规则下操作的源(参考)?
蒂姆(Tim)

@Tim有关命令操作的规则,请参见man bash。尤其参见标题为“简单命令扩展”和“命令执行”的部分。此外[[,其他的bash 的关键词包括ifthenwhile,和“案例”。关键字没有一般规则:每个关键字都是特殊情况。 man bash 包括每个细节。
John1024 '16

62

V7 Unix(Bourne shell首次亮相的地方)中[被称为test,并且仅以形式存在/bin/test。因此,您今天编写的代码为:

if [ "$foo" = "bar" ] ; then ...

你会写成

if test "$foo" = "bar" ; then ...

第二种表示法仍然存在,我发现发生了什么更清楚:您正在调用名为的命令test,该命令将评估其参数并返回退出状态代码,该代码if用于决定下一步要做什么。该命令可以内置在外壳中,也可以是外部程序。¹

[作为替代test来了later.²这可能是一个内置的代名词test,但它也提供了如/bin/[对于不具备它作为内置弹现代系统。

[test可以使用相同的代码实现。在OS X /bin/[/bin/testOS X上都是这种情况,它们是指向同一可执行文件的硬链接。³因此,该实现完全忽略了结尾]:如果将其称为/bin/[,则不需要它,并且不会抱怨如果确实提供给/bin/test.⁴

这段历史都没有影响[[,因为从来没有所谓的原始程序[[。它纯粹存在于将其实现为POSIX Shell扩展的那些Shell中

“内置”和“关键字”之间的部分区别是由于这一历史。这也反映了一个事实,分析语法规则[[表达式是不同的,如指出John1024的回答 .⁵


脚注:

  1. 以这种方式查看时,很清楚为什么您必须[在shell脚本中加上空格,这与括号和方括号在大多数其他编程语言中的工作方式不同。如果shell的命令解析器允许if["$x"...,则还必须允许iftest"$x"...

  2. 它发生在1980年左右。/bin/[我在1979年的Ancient Unix V7中不存在该man test文件,也没有将其记录为别名。在相应的手册页条目我在一个预发布版的系统III从1980年手册,它上市。

  3. ls -i /bin/[ /bin/test

  4. 但是不要指望这种行为。Bash的内置版本[确实需要关闭],并且其内置的test实施将抱怨,如果你提供。

  5. 内置命令与外部命令之间的区别也可能由于另一个原因而起作用:这两种实现可能表现不同。echo在许多系统上都是这种情况。因为只有一种实现,所以不需要对关键字进行这种区分。


谢谢@Warren Young,但是为什么和何时之间的builtinand keyword区别提供相同的功能(除了功能比的事实多)?[[[[[[
2015年

2
@sree [[作为关键字使bash可以做一些不可能的事情[-例如,不需要很多时间使用引号,因为shell知道它是一个变量。也就是说,当使用关键字时,命令行处理会受到影响,而当使用内置函数时,命令行处理不会受到影响-发生的时间要晚得多。
muru 2015年

1
请注意,V7 Bourne shell的代码显示为[内置代码,但是该代码已被注释掉。
斯特凡Chazelas

cd是内置的,但不会遮挡任何内容(cd无法作为外部程序实现)。
圣保罗Ebermann

2
这是一个很棒的历史总结,但是上面的muru和John1024在回答中却忽略了(IMO)关键的细节,即制作[[关键字允许shell对其参数使用特殊的解析规则。因此,可惜,我的投票赞成John1024。
Ilmari Karonen 2015年

3

[最初只是一个外部命令,是的另一个名称/bin/test。但是一些命令(例如[和)echo在shell脚本中使用得如此频繁,以至于shell实现者决定将代码直接复制到shell本身,而不是每次使用时都必须运行另一个进程。尽管您仍然可以通过其完整路径调用外部程序,但这些命令却变成了“ buildins”。

[[来得晚了 尽管内置函数是在Shell内部实现的,但它的解析方式类似于外部命令。正如John1024的答案中所解释的,这意味着未加引号的变量将在它们上进行单词拆分,并且标记像><一样作为I / O重定向进行处理。这使得编写复杂的比较表达式变得不便。[[是作为shell语法创建的,因此可以在意识形态上进行解析。[[变量内部不会进行单词拆分,<并且>可以用作比较运算符,=根据下一个参数是否带引号等可以有不同的表现。这些都是[[比传统[命令/内置函数更易于使用的便利。

他们不能简单地将其重新编码[为这样的语法,因为这对数百万个脚本而言是不兼容的更改。通过使用[[以前不存在的新语法,他们可以以向上兼容的方式完全改进它的使用方式。

这类似于导致$((...))算术表达式语法的发展,该发展已基本取代了传统expr命令。


0

较新[[bash优化[

当经典代码[经常用于执行琐碎的操作时,它有一个很大的缺点:它每次都会产生一个新的进程:(
它创建了一个新的地址空间,仅用于进行比较01!每次!)

我认为添加的主要目的[[是使内部表达式的求值[不会产生额外的过程。但是[工作方式无法改变-这会造成很多混乱和问题。因此,优化是通过更有效的方式以新名称实施的,即shell内置命令。
作为副作用,它在Shell语法中成为关键字。

[最初使用该时间时,这是使用外部过程进行处理的正确方法


5
请注意,尽管[它最初是一个外部命令,但很早就作为内建命令添加到了外壳程序中,可能是在Unix System III发行之时,而且肯定是在Unix System V发行之时。因此,“额外流程”多年来一直不是问题。但是,语法保持不变-将其视为外部命令。
乔纳森·勒夫勒

@JonathanLeffler哦,谢谢,我错过了[两者-这意味着要进行一些更改……
Volker Siegel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.