3.7 命令执行(Executing Commands)

3.7.1 简单命令扩展(Simple Command Expansion)

当执行一个简单命令时,shell 按下面顺序,从左到右执行扩展、赋值和重定向操作。

  1. 分析器将单词标记为变量赋值语句(这优先于命令名分析)并且保留重定向用于后继处理。
  2. 非变量赋值或重定向的单词会进行扩展。如果有任何单词在扩展后留存下来,第一个单词将标记为命令名并且剩余单词将标记为参数(arguments)。
  3. 依据上节所描述那样执行重定向。
  4. 在变量赋值的等号“=”后面的文本在被分配给变量之前,进行波浪号扩展、参数扩展、命令替换、算术扩展和引用移除。

如果没有命令名产生,则变量赋值将会对当前 shell 环境产生影响。否则,变量只添加到执行命令的环境中并且不会对当前环境产生影响。如果任意一个赋值语句试图将一个值分配给只读变量,则会产生报错,并且命令退出并返回一个非零值的状态。

如果没有命令名产生,则执行重定向,但不会影响当前 shell 环境。重定向报错会使命令以非零状态值退出。

如果在扩展之后有命令名,将会按下面所描述的继续执行。否则,命令将退出。如果扩展中包含了命令替换,则该命令的退出状态就是最后一条命令替换执行的退出状态。如果没有命令替换,则命令以状态零退出。

3.7.2 命令搜索和执行(Command Search and Execution)

在命令被分割为多个单词之后,如果产生的是一个简单命令和一个可选的参数列表,下列动作将会发生:

  1. 如果命令名不包含斜线“\”,则 shell 会试图定位这个命令。如果该名字是一个 shell 函数,则依照 3.3 节《Shell 函数》所描述的调用这个函数。
  2. 如果该名字未匹配到函数,则 shell 将在内建命令列表中搜索它。如果找到,则调用该内建命令。
  3. 如果名字即不是 shell 函数又不是内建命令,并且其不包含斜线,则 Bash 在变量“PATH”中包含的所有目录元素中搜索该名字的可执行文件。Bash 为了防止对“PATH”的多次搜索,使用一个哈希表(关于哈希表“hash”,请参见 4.1《Bourne Shell 内建命令》)来存储这些可执行文件的完整路径名。只有在哈希表中没有找到命令时,才会对变量“PATH”执行目录的完全搜索。如果搜索不成功,则 shell 搜索一个已定义的 shell 函数“command_not_found_handle”。如果这个函数存在,则在另外一个执行环境中调用带有原来命令的这个已定义的 shell 函数,并且将原来命令的参数作为该函数的参数,最后函数的退出状态作为这个子 shell 的退出状态。如果未定义函数“command_not_found_handle”,则 shell 打印一条报错消息并且返回退出状态值 127。
  4. 如果搜索成功,或命令名包含了一个或多个斜线,则 shell 会在另外一个执行环境中执行这个名字的程序。参数 0 设置为这个给定的命令名。如果命令中提供了参数,则将这些参数设置为后继位置参数。
  5. 如果由于这个文件不是可执行格式而执行失败,并且文件不是一个目录,则假设它是一个 shell 脚本并且 shell 将依照 3.8 节《Shell 脚本》所描述的进行执行。
  6. 如果命令不是执行于异步模式,则 shell 等待这个命令执行完成并收集其退出状态。

3.7.3 命令执行环境(Command Execution Environment)

Shell 的执行环境由下面所述条目组成:

当非内建命令或 shell 函数的简单命令执行时,其执行于下列条目所组成的一个分开的执行环境。除非有其它的注释,这些值继承自 shell。

在分开的环境中调用的命令不会影响到 shell 执行环境。

除了在调用时 shell 捕获的捕捉器其值被重置为继承自 shell 父进程的值,命令替换、由小括号成组的命令和异步命令都执行在由 shell 环境复制出的子 shell 环境中。作为管道一部分调用的内建命令也执行于子 shell 环境中。子 shell 环境的改变不会影响到 shell 执行环境。

用于执行命令替换所产生的子 shell 继承其父 shell 由“-e”选项指定的值。当不在 POSIX 模式时,Bash 会在子 shell 中清除“-e”选项。

如果一个命令跟随着一个“&”符号并且作业控制未激活,则这个命令的默认标准输入是空文件“/dev/null”。否则,被调用的命令继承来自通过重定向修改了的调用方 shell 的文件描述符。

3.7.4 环境(Environment)

当调用一个程序时,其给定的一个字符串数组被称为环境。这是一个以“name=value”形式出现的名称-值对列表。

Bash 提供一些操控环境的方法。在调用时,shell 扫描自己的环境并且为每一个找到的名称创建一个参数,并自动将其导出到子进程中。执行的命令继承这个环境。“export”和“declare -x”命令允许将参数和函数添加到环境或从环境中删除。如果在环境中一个参数的值改变了,新值则替换旧值变成这个环境的一部分。由可执行命令继承的环境由 shell 初始化环境组成。其值可以在 shell 中修改,使用“unset”和“export -n”命令移除名称-值对,或通过“export”和“declare -x”命令增加。

用于简单命令或函数的环境可以在其前面加入参数赋值语句进行临时扩大,参见 3.4《Shell 参数》。这些赋值语句只影响该命令可见的环境。

如果设置了“-k”选项(参见 4.3.1《内建命令 set》),则置于环境中的所有参数赋值都用于命令,而不是只是置于命令名前的参数赋值。

当 Bash 调用一个外部命令时,变量“$_”设置为这个命令的完整路径名并且将其传递进它的环境中。

3.7.5 退出状态(Exit Status)

一个执行命令后的退出状态是由 waitpid 系统调用或相等的函数返回的值。退出状态值落于 0 到 255 之间,这会在下面进行说明。shell 对 125 以上的值有特殊含义。来自于 shell 内建命令和复合命令的退出状态也在这个范围内。在一些情况下,shell 将使用一些特殊值来指示特定的失败模式。

出于 shell 的目的,使用零作为退出状态表示命令运行是成功的。非零的退出状态代表失败。这种定义明确的一种状态表示成功和多种状态表示失败的架构看上去有悖于人类的直觉。当一个命令以一个重大失败信号 N 终结时,Bash 使用 128+N 作为其退出状态。

如果命令没有找到,用于执行它所创建的子进程返回状态 127。如果命令找到但是不可执行的,则返回 126。

如果一个命令由于在扩展或重定向时出错了而引起的失败,则退出状态大于零。

退出状态也被 Bash 条件命令(参见 3.2.5.2《判断结构体》)和一些结构列表(参见 3.2.4《命令列表》)所使用。

所有的 Bash 内建命令在成功运行后返回零退出状态和在失败后返回非零值。所以其也可以用于条件命令和结构列表。所有内建命令在不正确使用时会返回状态 2,一般是缺少参数或使用了无效的选项。

3.7.6 信号(Signals)

当 Bash 处于缺少捕获器的交互模式时,其忽略 SIGTERM 信号(因此“kill0”不会结束掉交互式 shell),并且捕获并处理 SIGINT 信号(因此内建命令 wait 是可打断的)。当 Bash 接收到 SIGINT 时,它中断任何执行中的循环。在所有情况下,Bash 忽略 SIGQUIT。如果作业控制(参见第 7 章《作业控制》)起作用时,Bash 忽略 SIGTTIN、SIGTTOU 和 SIGTSTP。

会为由 Bash 启动的非内建命令设置一个信号控制句柄,其值是继承自 shell 父进程。当作业控制不起作用时,异步命令忽略 SIGINT 和 SIGQUIT 信号,还有这些继承的控制句柄。作为命令替换结果运行的命令忽略由键盘产生的作业控制信号 SIGTTIN、SIGTTOU 和 SIGTSTP。

在默认情况下,shell 退出时是依据接收到的 SIGHUP 信号。在退出之前,交互式 shell 重新给所有运行着的和已停止的作业发送 SIGHUP 信号。已停止作业由发送一个 SIGCONT 信号来确保其收到了 SIGHUP 信号。为了防止 shell 将 SIGHUP 信号发送给一个常规作业,应该使用内建命令 disown(参见 7.2 节《作业控制内建命令》)将其从作业列表中移除,或使用“disown -h”将其标记为不接收 SIGHUP。

如果使用 shopt 设置了“huponexit”选项,Bash 会在交互式登录 shell 退出时向所有作业发送 SIGHUP 信号。

如果 Bash 正在等待一个命令运行完成,并且接收到了一个捕获器已被设置了的信号,则直到命令完成才会执行捕获。当 Bash 通过 wait 内建命令下在等待一个异步命令完成,当接收到捕获器已被设置了的信号时,将会引起内建命令 wait 立即返回并返回一个大于 128 的退出状态,之后立即执行捕获。