当我运行这两个命令时,我得到
$ type cd
cd is a shell builtin
$ type if
if is a shell keyword
Run Code Online (Sandbox Code Playgroud)
清楚地表明这cd
是一个shell内置并且if
是一个shell关键字。那么shell内置和关键字有什么区别呢?
gni*_*urf 53
在 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
Run Code Online (Sandbox Code Playgroud)
关键词:
$ compgen -k
if then else elif fi case
esac for select while until do
done in function time { }
! [[ ]] coproc
Run Code Online (Sandbox Code Playgroud)
请注意,例如[
是一个内置[[
函数,那是一个关键字。我将使用这两个来说明下面的区别,因为它们是众所周知的运算符:每个人都知道它们并且经常(或应该)使用它们。
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
Run Code Online (Sandbox Code Playgroud)
这工作正常,Bash 会很高兴地输出
$ compgen -k
if then else elif fi case
esac for select while until do
done in function time { }
! [[ ]] coproc
Run Code Online (Sandbox Code Playgroud)
请注意,我没有引用$string_with_spaces
. 而以下内容:
string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
echo "The string is non-empty"
fi
Run Code Online (Sandbox Code Playgroud)
表明 Bash 不高兴:
The string is non-empty
Run Code Online (Sandbox Code Playgroud)
为什么它适用于关键字而不是内置函数?因为当 Bash 解析代码时,它会看到[[
哪个是关键字,并且很早就知道它的特殊性。所以它会寻找关闭]]
并以特殊的方式对待内部。内置命令(或命令)被视为将使用参数调用的实际命令。在最后一个示例中,bash 理解它应该运行[
带有参数的命令(每行显示一个):
string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
echo "The string is non-empty"
fi
Run Code Online (Sandbox Code Playgroud)
因为发生了变量扩展、引号删除、路径名扩展和分词。该命令被[
证明是在 shell 中构建的,因此它使用这些参数执行它,这导致错误,因此投诉。
在实践中,您会看到这种区别允许复杂的行为,这是内置函数(或命令)无法实现的。
仍在实践中,您如何区分内置和关键字?这是一个有趣的实验:
bash: [: too many arguments
Run Code Online (Sandbox Code Playgroud)
当 Bash 解析该行时$a -d . ]
,它看不到任何特别之处(即,没有别名、没有重定向、没有关键字),所以它只是执行变量扩展。变量展开后,它看到:
-n
some
spaces
here
]
Run Code Online (Sandbox Code Playgroud)
so[
使用参数-d
,.
和执行命令(内置)]
,这当然是真的(这只测试是否.
是目录)。
现在看:
$ a='['
$ $a -d . ]
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)
哦。那是因为当 Bash 看到这一行时,它没有看到任何特别之处,因此展开所有变量,最终看到:
[ -d . ]
Run Code Online (Sandbox Code Playgroud)
这时候,别名扩展和关键字扫描早就做了,不会再做,所以Bash试图找到调用的命令[[
,没有找到,并抱怨。
同样的道理:
$ a='[['
$ $a -d . ]]
bash: [[: command not found
Run Code Online (Sandbox Code Playgroud)
和
[[ -d . ]]
Run Code Online (Sandbox Code Playgroud)
别名扩展也很特别。你们都至少做过一次以下的事情:
$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found
Run Code Online (Sandbox Code Playgroud)
推理是一样的:别名扩展发生在变量扩展和引用删除之前很久。
关键字与别名
如果我们将别名定义为关键字,您认为会发生什么?
$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found
Run Code Online (Sandbox Code Playgroud)
哦,它有效!所以别名可用于别名关键字!很高兴知道。
结论:内置函数实际上就像命令一样:它们对应于使用经过直接变量扩展和分词和通配符的参数执行的操作。这真的就像在某个地方有一个外部命令,/bin
或者/usr/bin
用变量扩展后给出的参数调用,等等。请注意,当我说它真的就像有一个外部命令时,我的意思只是关于参数,分词,通配,变量扩展等。一个内置函数可以修改shell 的内部状态!
另一方面,关键字很早就被扫描和理解,并允许复杂的 shell 行为:shell 将能够禁止分词或路径名扩展等。
现在查看内置函数和关键字的列表,并尝试找出为什么有些需要成为关键字。
!
是关键字。似乎可以用一个函数来模拟它的行为:
$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found
Run Code Online (Sandbox Code Playgroud)
但这会禁止像这样的结构:
$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)
(在那种情况下,我的意思是not ! true
哪个不起作用)或
not() {
if "$@"; then
return 1
else
return 0
fi
}
Run Code Online (Sandbox Code Playgroud)
相同的time
:给它一个关键字会更强大,这样它就可以用重定向来计时复杂的复合命令和管道:
$ ! ! true
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)
如果time
where 只是一个命令(甚至是内置命令),它只会看到参数grep
,^#
和/home/gniourf/.bashrc
, 时间,然后它的输出将通过管道的其余部分。但是有了关键字,Bash 可以处理一切!它可以time
完整的管道,包括重定向!如果time
只是一个命令,我们不能这样做:
$ ! { true; }
echo $?
1
Run Code Online (Sandbox Code Playgroud)
尝试一下:
$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null
Run Code Online (Sandbox Code Playgroud)
尝试修复(?)它:
$ time { printf 'hello '; echo world; }
Run Code Online (Sandbox Code Playgroud)
绝望。
关键字与别名?
$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'
Run Code Online (Sandbox Code Playgroud)
你认为会发生什么?
真的,内置函数就像一个命令,只不过它是内置在 shell 中的,而关键字是允许复杂行为的东西!我们可以说它是 shell 语法的一部分。
man bash
打电话给他们SHELL BUILTIN COMMANDS
。所以,“shell builtin”就像一个普通的命令,比如grep
,等等,但它不是包含在一个单独的文件中,而是内置到 bash 本身中。这使得它们比外部命令更有效地执行。
甲关键字也是“硬编码到击,但不像一个内建,关键字本身并不是一个命令,但命令构建体的亚基。” 我将此解释为关键字本身没有任何功能,但需要命令来执行任何操作。(从链接中,其他示例是for
、while
、do
和!
,并且在我对您的其他问题的回答中还有更多示例。)