据我所知,[[
是 的增强版本[
,但是当我将其视为[[
关键字并[
显示为内置时,我感到很困惑。
[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword
TLDP说
内置命令可能是同名系统命令的同义词,但 Bash 在内部重新实现了它。例如,Bash echo 命令与 /bin/echo 不同,尽管它们的行为几乎相同。
和
关键字是保留字、标记或运算符。关键字对于 shell 具有特殊的含义,并且确实是 shell 语法的构建块。例如,for、while、do 和 !是关键词。与内置函数类似,关键字被硬编码到 Bash 中,但与内置函数不同的是,关键字本身不是命令,而是命令构造的子单元。 [2]
这不应该使[
and[[
成为关键字吗?我在这里缺少什么吗?还,这个链接再次确认[
和[[
应该属于同一类。
答案1
[
和之间的区别[[
是非常根本的。
[
是一个命令。它的参数的处理方式与任何其他命令参数的处理方式相同。例如,考虑:[ -z $name ]
shell 将展开
$name
并执行两个都对结果进行分词和文件名生成,就像任何其他命令一样。例如,以下操作将失败:
$ name="here and there" $ [ -n $name ] && echo not empty bash: [: too many arguments
为了使其正常工作,需要引号:
$ [ -n "$name" ] && echo not empty not empty
[[
是一个 shell 关键字,其参数根据特殊规则进行处理。例如,考虑:[[ -z $name ]]
shell 将展开
$name
,但与任何其他命令不同,它将执行两者都不对结果进行分词或文件名生成。例如,尽管 中嵌入了空格,以下内容仍将成功name
:$ name="here and there" $ [[ -n $name ]] && echo not empty not empty
概括
[
是一个命令,与 shell 执行的所有其他命令遵循相同的规则。
然而,由于[[
是一个关键字,而不是一个命令,因此 shell 对其进行了特殊处理,并且它在非常不同的规则下运行。
答案2
在V7 Unix— Bourne shell 首次亮相的地方 —[
被称为test
,并且它仅以/bin/test
.因此,您今天将编写的代码如下:
if [ "$foo" = "bar" ] ; then ...
你会写成
if test "$foo" = "bar" ; then ...
第二个符号仍然存在,我发现它更清楚地说明了发生了什么:您正在调用一个名为 的命令test
,该命令评估其参数并返回一个退出状态码用于if
决定下一步做什么。该命令可能内置于 shell 中,也可能是外部程序。
[
作为后来的替代品test
。²它可能是 的内置同义词,但它也在现代系统上为没有内置它的 shelltest
提供。/bin/[
[
并且test
可以使用相同的代码来实现。 OS X/bin/[
就是这种情况/bin/test
,其中这些是硬链接到同一个可执行文件。因此,该实现完全忽略了尾随]
:如果您将其称为 as ,则它不需要它/bin/[
,并且如果您将其称为做提供给/bin/test
.⁴
这些历史都不会产生影响[[
,因为从来没有一个名为 的原始程序[[
。它纯粹存在于那些将其实现为扩展的 shell 中POSIX外壳。
“内置”和“关键字”之间的部分区别是由于这段历史。这也反映了解析表达式的语法规则[[
不同的事实,如中指出的约翰1024的回答.⁵
脚注:
当您这样看时,就会清楚为什么必须
[
在 shell 脚本中添加空格,这与大多数其他编程语言中括号和方括号的工作方式不同。如果 shell 的命令解析器允许if["$x"...
,它也必须允许iftest"$x"...
它发生在 1980 年左右。
/bin/[
在我的副本中不存在古老的 Unix V7从 1979 年开始,也没有man test
将其记录为别名。在相应的手册页条目中,我有一个预发行版本系统三1980年的手册,它是列出。ls -i /bin/[ /bin/test
但不要指望这种行为。 Bash 内置版本
[
确实需要关闭]
,并且如果您这样做,它的内置test
实现将会抱怨做提供它。内置命令与外部命令的区别可能还因另一个原因而重要:两种实现的行为可能不同。
echo
许多系统都是这种情况。因为只有一种实现,所以不需要对关键字进行这样的区分。
答案3
[
最初只是一个外部命令,/bin/test
.但是有一些命令(例如[
和echo
)在 shell 脚本中使用得非常频繁,以至于 shell 实现者决定将代码直接复制到 shell 本身中,而不是每次使用它们时都必须运行另一个进程。这将这些命令变成了“内置命令”,尽管您仍然可以通过其完整路径调用外部程序。
[[
来得很晚。尽管内置命令是在 shell 内部实现的,但它的解析方式与外部命令一样。正如 John1024 的回答中所解释的,这意味着未加引号的变量将对其进行分词,并且像>
和这样的标记<
将作为 I/O 重定向进行处理。这使得编写复杂的比较表达式很不方便。[[
被创建为 shell 语法,因此可以对其进行特殊解析。变量内部[[
不会进行分词,<
并且>
可以用作比较运算符,=
根据下一个参数是否被引用而表现不同,等等。这些都是比传统命令/内置命令[[
更容易使用的便利。[
他们不能简单地重新编码[
为这样的语法,因为这对于数百万个脚本来说是不兼容的更改。通过使用[[
以前不存在的新语法,他们可以以向上兼容的方式完全改变其使用方式。
这类似于$((...))
算术表达式语法的演变,它基本上取代了传统expr
命令。
答案4
较新[[
的bash
是优化的[
。
经典[
的有一个很大的缺点,当它经常用于执行一项琐碎的操作时:它每次都会产生一个新进程:(
它创建一个新的地址空间只是为了比较0
和1
!每次!)
我认为添加的一个要点[[
是使内部表达式的求值[
不会产生额外的过程。但[
工作方式无法改变——这会造成很多混乱和问题。因此,优化以一个更有效的新名称来实现,即 shell 内置命令。
作为副作用,它成为 shell 语法中的关键字。
当时[
最先使用的是,这是正确的方式使用外部进程来完成此操作。