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


Answers:


45

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 . ],它看不到任何特殊的东西(即,没有别名,没有重定向,没有关键字),因此它只是执行变量扩展。变量展开后,它将看到:

[ -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
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

原因是相同的:别名扩展发生在变量扩展和引号删除之前。


关键字与别名

现在,如果我们将别名定义为关键字,您会怎么想?

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

哦,行得通!因此,别名可以用作关键字的别名!很高兴知道。


结论:内建函数实际上就像命令一样:它们对应于使用参数执行的动作,该参数经过直接变量扩展以及分词和globing。确实就像在某个地方有一个外部命令,/bin或者/usr/bin用变量扩展等后给出的参数调用。请注意,当我说这就像拥有一个外部命令时,我的意思仅是关于参数,单词拆分,通配符,变量扩展等。内建函数可以修改外壳的内部状态!

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

现在查看内建函数和关键字的列表,并尝试找出为什么某些关键字需要使用。


!是一个关键字。似乎可以通过以下功能模仿其行为:

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

绝望。


关键字与别名?

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

您认为会发生什么?


实际上,内建函数类似于命令,只是它内置在外壳中,而关键字是允许复杂行为的东西!我们可以说这是外壳语法的一部分。


2
同意@JohnyTex,这是我在Stack网站上看到的最全面,最适当的答案之一。谢谢。一个也许不相关的问题:只是为了好奇,我试图从命令之前与找到的“临时禁用别名”功能的文档=\“使用manaproposhelp我已经没有任何运气。知道我在哪里可以找到该信息吗?通常,以便将来我可以看到其中的其他内容,因为我认为我缺少参考资料。
nc。

@nc:您不会找到明确记录的文档。此答案解释了它起作用的原因。在参考手册的“ 外壳操作 ”部分中,您会找到最接近的结果。您会在第2步中早就完成了别名扩展(我在此答案中试图强调的事情)。在稍后执行引号删除,参数扩展,globlob等。因此,要禁用别名,您可以使用某种形式的引用来禁止外壳将令牌理解为别名,例如\ll"ll"'ll'
gniourf_gniourf '16

1
我实际上确实得到了命令,别名和关键字被触发了。由于我们引用了[[yield \[[,所以不会将其解析为别名。到目前为止正确吗?我迷路的地方不是意识到反斜杠是Bash中的引号,我试图在别名和关键字下查找它,并完全迷路了……一切都很好。在“报价”部分下:>非引号反斜杠()是转义字符。
nc。

1
因此,我们可以将操作列表中的(2)视为\[[标记化,并且是类型为LiteralQuoteToken的单个标记,与之相对[[应的是OpenTestKeywordToken,并且需要使用封闭的]]CloseTestKeywordToken进行oroper编译/正确语法。稍后,在(4)中,将LiteralQuoteToken评估[[为要执行的bulitin /命令的名称,在(6)中,由于没有[[内置命令或内置命令,Bash将拒绝。真好 随着时间的流逝,我可能会忘记确切的细节,但是现在Bash运行东西的方式对我来说更加清晰了。谢谢。
nc。

9

man bash打电话给他们SHELL BUILTIN COMMANDS。因此,“ shell内置”就像普通的命令,诸如此类grep,但是它不是包含在单独的文件中,而是内置在bash本身中。这使它们比外部命令更有效地执行。

关键字也是“硬编码到击,但不像一个内建,关键字本身并不是一个命令,但命令构建体的亚基。” 我将其解释为意味着关键字本身没有功能,但是需要命令才能执行任何操作。(从链接,其他的例子是forwhiledo,和!,还有更多的我的回答对您的其他问题。)


1
有趣的事实:[[ is a shell keyword但是[ is a shell builtin。我不知道为什么。
Sparhawk

1
由于历史上的原因和POSIX标准可能尽可能地符合旧的Bourne shell,因为它[早在今天就作为单独的命令存在。[[没有由标准指定,因此开发人员可以选择将其作为关键字或内置。
Sergiy Kolodyazhnyy

1

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

仅当不引用任何字符且该词用作以下内容时,才发生这种识别:

  • 命令的第一个字

  • 除大小写,for或in之外的保留字之一之后的第一个字

  • case命令中的第三个单词(在这种情况下,仅in有效)

  • for命令中的第三个单词(在这种情况下,仅in和do有效)

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

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

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

边注

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

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

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.