shell builtin和shell关键字有什么区别?

当我运行这两个命令时,我得到了

$ type cd cd is a shell builtin $ type if if is a shell keyword 

很明显, cd是shell内置的, if是shell关键字。 那么shell builtin和关键字有什么区别?

内置函数和关键字之间存在很大差异,就像Bash解析代码一样。 在我们讨论差异之前,让我们列出所有关键字和内置:

内建的:

 $ compgen -b . : [ alias bg bind break builtin caller cd command compgen complete compopt continue declare dirs disown echo enable eval exec exit export false fc fg getopts hash help history jobs kill let local logout mapfile popd printf pushd pwd read readarray readonly return set shift shopt source suspend test times trap true type typeset ulimit umask unalias unset wait 

关键词:

 $ compgen -k if then else elif fi case esac for select while until do done in function time { } ! [[ ]] coproc 

请注意,例如[是内置的, [[是一个关键字。 我将使用这两个来说明下面的区别,因为它们是众所周知的运算符:每个人都知道它们并定期(或应该)使用它们。

Bash在解析时很早就会扫描并理解关键字。 这允许例如以下内容:

 string_with_spaces='some spaces here' if [[ -n $string_with_spaces ]]; then echo "The string is non-empty" fi 

这很好用,Bash很乐意输出

 The string is non-empty 

请注意,我没有引用$string_with_spaces 。 以下内容:

 string_with_spaces='some spaces here' if [ -n $string_with_spaces ]; then echo "The string is non-empty" fi 

表明Bash不高兴:

 bash: [: too many arguments 

为什么它与关键字一起使用而不与内置函数一起使用? 因为当Bash解析代码时,它会看到[[这是一个关键字,并且很早就明白它是特殊的。 因此它将寻找结束]]并将以特殊方式处理内部。 内置(或命令)被视为将使用参数调用的实际命令。 在最后一个例子中,bash理解它应该运行命令[带参数(每行显示一个):

 -n some spaces here ] 

因为变量扩展,引用删除,路径名扩展和单词拆分发生。 命令[原来是在shell中构建的,因此它使用这些参数执行它,这会导致错误,因此投诉。

实际上,您会发现这种区别允许使用内置(或命令)无法实现的复杂行为。

仍在实践中,您如何区分内置关键字和关键字? 这是一个有趣的实验:

 $ a='[' $ $a -d . ] $ echo $? 0 

当Bash解析行$a -d . ] $a -d . ] ,它没有看到任何特殊的东西(即没有别名,没有重定向,没有关键字),所以它只是执行变量扩展。 变量扩展后,它会看到:

 [ -d . ] 

所以执行命令(内置) [带参数-d , . 当然这是真的(这只测试.是一个目录)。

现在看:

 $ a='[[' $ $a -d . ]] bash: [[: command not found 

哦。 那是因为当Bash看到这一行时,它看不到什么特别的东西,因此会扩展所有变量,并最终看到:

 [[ -d . ]] 

此时,别名扩展和关键字扫描已经执行了,并且不再执行,因此Bash尝试找到名为[[ ,找不到它,并且抱怨。

沿着同样的路线:

 $ '[' -d . ] $ echo $? 0 $ '[[' -d . ]] bash: [[: command not found 

 $ \[ -d . ] $ echo $? 0 $ \[[ -d . ]] bash: [[: command not found 

别名扩展也很特别。 你们至少完成了以下一次:

 $ alias ll='ls -l' $ ll ....  .... $ \ll bash: ll: command not found $ 'll' bash: ll: command not found 

原因是相同的:别名扩展在变量扩展和引用删除之前很久就会发生。


关键字vs别名

现在,如果我们将别名定义为关键字,您认为会发生什么?

 $ alias mytest='[[' $ mytest -d . ]] $ echo $? 0 

哦,它有效! 所以别名可以用来别名关键字! 很高兴知道。


结论:内置函数的行为与命令类似:它们对应于使用直接变量扩展和单词拆分以及globbing的参数执行的操作。 它实际上就像在/bin/usr/bin中的某个地方使用变量扩展后给出的参数调用外部命令等。请注意,当我说它真的就像有一个外部命令时我只是关于参数的意思,单词拆分,通配,变量扩展等。内置可以修改shell的内部状态!

另一方面,关键字很早就被扫描和理解,并允许复杂的shell行为:shell将能够禁止单词拆分或路径名扩展等。

现在查看内置和关键字列表,并尝试找出为什么有些需要成为关键字。


! 是一个关键字。 似乎可以用函数模仿它的行为:

 not() { if "$@"; then return false else return true fi } 

但这会禁止像下面这样的结构:

 $ ! ! true $ echo $? 0 

要么

 $ ! { true; } echo $? 1 

time相同:拥有一个关键字使其更加强大,以便能够通过重定向为复杂的复合命令和管道计时:

 $ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null 

如果time只是一个命令(甚至内置),它只会看到参数grep^#/home/gniourf/.bashrc ,计算时间,然后它的输出将通过管道的其余部分。 但是使用关键字,Bash可以处理所有事情! 它可以time整个管道,包括重定向! 如果time仅仅是一个命令,我们就做不到:

 $ time { printf 'hello '; echo world; } 

试试吧:

 $ \time { printf 'hello '; echo world; } bash: syntax error near unexpected token `}' 

尝试修复(?)它:

 $ \time { printf 'hello '; echo world; time: cannot run {: No such file or directory 

绝望。


关键字vs别名?

 $ alias mytime=time $ alias myls=ls $ mytime myls 

你觉得怎么样?


实际上, 内置函数就像一个命令,除了它是在shell中构建的,而关键字是允许复杂行为的东西! 我们可以说它是shell语法的一部分。

man bash称他们为SHELL BUILTIN COMMANDS 。 所以,“shell builtin”就像普通命令一样,比如grep等,但它不是包含在一个单独的文件中,而是内置于bash本身 。 这使它们比外部命令更有效地执行。

关键字也“硬编码到Bash中,但与内置函数不同,关键字本身不是命令,而是命令构造的子单元。” 我解释这意味着关键字没有单独的function,但需要命令来做任何事情。 (从链接中,其他示例是forwhiledo ,和! , 我对你的另一个问题的答案还有更多。)

Ubuntu附带的命令行手册没有给出关键字的定义,但是在线手册 (参见旁注)和POSIX Shell命令语言标准规范将这些称为“保留字”,并且都提供了这些列表。 从POSIX标准:

只有在没有引用任何字符且该单词用作以下字符时,才会发生此识别:

  • 命令的第一个字

  • 除了case,for或in之外的其中一个保留字之后的第一个单词

  • 案例命令中的第三个单词(仅在此情况下有效)

  • for命令中的第三个单词(仅在in和do中有效)

这里的关键是关键字/保留字具有特殊含义,因为它们有助于shell语法,用于表示某些代码块,如循环,复合命令,分支(if / case)语句等。它们允许形成命令语句,但是他们自己 – 不做任何事情,事实上,如果你输入关键字,例如foruntilcase – shell会期望一个完整的语句,否则 – 语法错误:

 $ for bash: syntax error near unexpected token `newline' $ 

在源代码级别,bash的保留字在parese.y中定义,而内置函数则具有专用于它们的整个目录 。

边注

GNU索引显示[作为保留字,但它实际上是内置命令。 [[相比之下是一个保留字。

另请参阅: 关键字,保留字和内置之间的差异?