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

Avi*_*Raj 35 command-line

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

$ 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)

如果timewhere 只是一个命令(甚至是内置命令),它只会看到参数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 语法的一部分。

  • 同意@JohnyTex,这是我在 Stack 站点上看到的最全面、最合适的答案之一。谢谢你。一个可能不相关的问题:只是为了好奇,我正在尝试使用 `man`、`apropos` 和 `help 从在命令前面加上 `=\`' 的命令中查找“临时禁用别名”功能的文档`而且我没有任何运气。知道我会去哪里找到这些信息吗?主要是为了将来我可以看到那里还有什么,因为我想我缺少参考源。 (2认同)

Spa*_*awk 9

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

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

  • 有趣的事实:“[[ 是一个 shell 关键字”,但“[ 是一个 shell 内置函数”。我不知道为什么。 (2认同)
  • 由于历史原因和 POSIX 标准,它可能会尽可能地符合旧的 Bourne shell,因为“[”在当时作为单独的命令存在。标准没有指定“[[”,因此开发人员可以选择将其作为关键字或内置。 (2认同)