shell 内置命令和 shell 关键字有什么区别?

shell 内置命令和 shell 关键字有什么区别?

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

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

上面很清楚的表明了cd是shell的内建命令,if是shell的关键字。那么shell的内建命令和关键字有什么区别呢?

答案1

在 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

哦,成功了!所以别名可以用来给关键字设置别名!很高兴知道。


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

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

现在查看内置函数和关键字的列表,并尝试弄清楚为什么有些需要是关键字。


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

not() {
    if "$@"; then
        return 1
    else
        return 0
    fi
}

但这会禁止如下构造:

$ ! ! true
$ echo $?
0

(在这种情况下,我的意思not ! true是这不起作用)或

$ ! { 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

您认为会发生什么?


真的,内置类似于命令,只不过它是内置在 shell 中的,而关键词是允许复杂行为的东西!我们可以说它是 shell 语法的一部分。

答案2

man bash调用它们SHELL BUILTIN COMMANDS。因此,“shell 内置命令”就像普通命令一样,如grep等,但它不是包含在单独的文件中,而是内置于 bash 本身.这使得它们比外部命令执行得更有效率。

A关键词也是“硬编码到 Bash 中,但与内置命令不同,关键字本身不是命令,而是命令构造的子单元。”我将其解释为关键字本身没有功能,但需要命令才能执行任何操作。(从链接中,其他示例包括、、for和,还有更多whiledo!我的答案回答您的另一个问题。

答案3

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

仅当没有任何字符被引用并且单词的用法如下时,才会发生这种识别:

  • 命令的第一个字

  • 除 case、for 或 in 之外的保留字之后的第一个字

  • 案例命令中的第三个单词(在这种情况下只有 in 有效)

  • for 命令中的第三个字(在这种情况下只有 in 和 do 有效)

这里的关键是关键字/保留字具有特殊含义,因为它们有助于 shell 语法,用于表示某些代码块,例如循环、复合命令、分支( if / case )语句等。它们允许形成命令语句,但它们本身不执行任何操作,事实上,如果你输入诸如for, until,之类的关键字case,shell 将期望一个完整的语句,否则会出现语法错误:

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

在源代码级别,bash 的保留字定义在帕雷塞,而内置函数有整个目录献给他们。

边注

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

也可以看看:关键字、保留字和内置字之间的区别?

相关内容