shell脚本系列:8、执行命令
• Simple Command Expansion | Bash如何在执行简单命令之前展开它们。 | |
• Command Search and Execution | Bash如何查找命令并运行它们。 | |
• Command Execution Environment | Bash在其中执行非shell内置命令的环境。 | |
• Environment | 给指令的环境。 | |
• Exit Status | 命令返回的状态以及Bash如何解释它。 | |
• Signals | 当Bash或它运行的命令接收到一个信号时会发生什么。 |
1. 简单命令扩展
当执行一个简单的命令时,shell按照以下顺序从左到右执行以下展开、分配和重定向。
- 解析器标记为变量赋值(在命令名之前)和重定向的单词将被保存以供以后处理。
- 扩展不是变量赋值或重定向的词(请参阅Shell扩展)。如果展开后还有任何单词,则第一个单词被认为是命令名,其余单词是参数。
- 按上面描述的方式执行重定向(请参阅重定向)。
- 每个变量赋值中’ = '后面的文本在赋值给变量之前都会进行波浪号展开、参数展开、命令替换、算术展开和引号删除。
如果没有命令名结果,变量分配将影响当前shell环境。否则,这些变量将被添加到被执行命令所在的环境中,不会影响当前shell环境。如果任何赋值尝试将一个值赋给只读变量,则会发生错误,命令退出并处于非零状态。
如果没有命令名结果,则重定向,但不会影响当前shell环境。重定向错误导致命令以非零状态退出。
如果展开后还有一个命令名,执行过程如下所述。否则,命令退出。如果其中一个展开包含命令替换,则命令的退出状态为执行的最后一个命令替换的退出状态。如果没有命令替换,则命令退出,状态为0。
2. 命令搜索和执行
在将一个命令分解为单词之后,如果它生成一个简单的命令和一个可选参数列表,那么将执行以下操作。
- 如果命令名不包含斜杠,shell将尝试找到它。如果存在同名的shell函数,则按shell函数中描述的方式调用该函数。
- 如果名称与函数不匹配,shell将在shell内置函数列表中搜索它。如果找到匹配,就调用该内置函数。
- 如果该名称既不是shell函数也不是内置函数,并且不包含斜杠,那么Bash将在
$PATH
的每个元素中搜索包含该名称的可执行文件的目录。Bash使用哈希表来记住可执行文件的完整路径名,以避免多次PATH
搜索(请参阅Bourne Shell Builtins中hash
的描述)。只有在哈希表中没有找到该命令时,才会执行$PATH
中的目录的完整搜索。如果搜索不成功,shell将搜索名为command_not_found_handle
的已定义shell函数。如果该函数存在,它将在一个单独的执行环境中调用,使用原始命令和原始命令的参数作为其参数,函数的退出状态将成为该子shell的退出状态。如果未定义该函数,shell将输出错误消息并返回退出状态127。 - 如果搜索成功,或者命令名包含一个或多个斜杠,shell将在单独的执行环境中执行命名的程序。参数0被设置为给定的名称,命令的其余参数被设置为提供的参数(如果有的话)。
- 如果执行失败,因为文件不是可执行格式的,并且文件不是一个目录,它被假定为一个shell脚本,shell按照shell Scripts中描述的那样执行它。
- 如果命令不是异步开始的,shell将等待命令完成并收集其退出状态。
3. 命令执行环境
shell有一个执行环境,它包括以下内容:
- 打开调用时由shell继承的文件,通过提供给
exec
内置的重定向进行修改 - 由
cd
、pushd
或popd
设置的当前工作目录,或在调用时由shell继承的工作目录 - 由
umask
设置或从shell的父程序继承的文件创建模式掩码 - 当前
trap
设置 - Shell参数,通过变量赋值设置,或使用
set
设置,或在环境中从Shell的父类继承 - 在执行过程中定义的Shell函数,或者在环境中从Shell的父类继承的Shell函数
- 在调用时(默认或使用命令行参数)或通过
set
启用的选项 - 由
shopt
启用的选项(参见shopt Builtin) - 使用别名定义的shell别名(参见别名)
- 各种进程id,包括后台作业的进程id(请参见列表)、
$$
的值和$PPID
的值
当要执行一个简单的命令,而不是一个内置或外壳函数时,它将在一个单独的执行环境中调用,该执行环境由以下部分组成。除非另有说明,否则这些值都是从shell继承的。
- shell打开的文件,以及重定向到命令指定的任何修改和添加
- 当前工作目录
- 文件创建模式掩码
- shell变量和标记为导出的函数,以及为命令导出的变量,在环境中传递(参见environment)
- 被shell捕获的陷阱会被重置为从shell的父程序继承的值,被shell忽略的陷阱也会被忽略
在这个单独的环境中调用的命令不能影响shell的执行环境。
命令替换、用圆括号分组的命令和异步命令在子shell环境中被调用,该子shell环境是shell环境的副本,但是shell捕获的陷阱被重置为shell在调用时从其父shell继承的值。作为管道的一部分调用的内置命令也在子shell环境中执行。对子shell环境所做的更改不会影响shell的执行环境。
执行命令替换派生的子shell从父shell继承-e选项的值。当不处于POSIX模式时,Bash将清除此类子shell中的-e选项。
如果命令后面跟着’ & ',并且作业控制没有激活,该命令的默认标准输入是空文件/dev/null
。否则,被调用的命令将继承通过重定向修改的调用shell的文件描述符。
4. 环境
当程序被调用时,会给它一个称为环境的字符串数组。这是一个名称-值对列表,形式为name=value
。
Bash提供了几种操作环境的方法。在调用时,shell扫描自己的环境,并为找到的每个名称创建一个参数,自动将其标记为导出到子进程。执行的命令继承环境。export
和declare -x
命令允许将参数和函数添加到环境中或从环境中删除。如果环境中某个参数的值被修改,新值将成为环境的一部分,取代旧值。任何被执行的命令所继承的环境包括shell的初始环境,其值可以在shell中修改,除了unset
和export -n
命令删除的任何对,以及通过export
和declare -x
命令添加的任何内容。
任何简单命令或函数的环境都可以通过添加参数赋值前缀来临时增强,如Shell Parameters中所述。这些赋值语句只影响该命令所看到的环境。
如果设置了-k选项(请参见内置设置),那么所有参数分配都将被放置在命令的环境中,而不仅仅是那些位于命令名之前的参数。
当Bash调用外部命令时,变量’ $_ '被设置为该命令的完整路径名,并在其环境中传递给该命令。
5. 退出状态
执行命令的退出状态是waitpid系统调用或等效函数返回的值。退出状态介于0到255之间,但是,如下所述,shell可能会特别使用高于125的值。shell内置命令和复合命令的退出状态也仅限于此范围。在某些情况下,壳体会使用特殊的值来表示特定的破坏模式。
对于shell的目的,退出状态为零的命令已经成功。非零退出状态表示失败。使用这种看似违反直觉的模式,就有一种明确定义的方法来表示成功,而有多种方法来表示各种失败模式。当一个命令在一个编号为N的致命信号上终止时,Bash使用值128+N作为退出状态。
如果没有找到命令,为执行该命令而创建的子进程将返回状态为127。如果找到了一个命令但不能执行,则返回状态为126。
如果扩容或重定向过程中出现错误导致命令失败,则退出状态大于零。
退出状态由Bash条件命令(请参见条件结构)和一些列表结构(请参见列表)使用。
如果成功,所有Bash内置函数都返回一个退出状态0,如果失败,返回一个非零状态,因此它们可以被条件和列表构造使用。所有内置函数都返回一个退出状态2来指示不正确的用法,通常是无效的选项或缺少参数。
6. 信号
当Bash是交互式的,在没有任何陷阱的情况下,它会忽略SIGTERM
(这样’ kill 0 '不会杀死一个交互式shell),并且SIGINT
会被捕获和处理(这样内置的wait
是可中断的)。当Bash接收到一个SIGINT
时,它将中断任何执行循环。在所有情况下,Bash都会忽略SIGQUIT
。如果作业控制生效(请参阅作业控制),Bash将忽略SIGTTIN
、SIGTTOU
和SIGTSTP
。
由Bash启动的非内置命令将信号处理程序设置为shell从其父程序继承的值。当作业控制不生效时,除了这些继承的处理程序外,异步命令还忽略SIGINT
和SIGQUIT
。作为命令替换的结果运行的命令将忽略键盘生成的作业控制信号SIGTTIN
、SIGTTOU
和SIGTSTP
。
当接收到SIGHUP
时,shell默认退出。在退出之前,一个交互式shell重新发送SIGHUP
到所有作业,运行或停止。停止的作业被发送SIGCONT
以确保它们接收到SIGHUP
。为了防止shell发送SIGHUP
信号到一个特定的作业,它应该从作业表中删除与disown
内置(参见作业控制内置)或标记为不接收SIGHUP
使用disown -h
。
如果使用shopt
设置了huponexit
shell选项(参见shopt Builtin),那么当交互登录shell退出时,Bash会向所有作业发送SIGHUP
。
如果Bash正在等待一个命令完成,并收到一个已设置了trap的信号,则该trap将直到命令完成才会执行。当Bash通过内置的wait
等待一个异步命令时,接收到一个已经设置了trap的信号将导致内置的wait
立即返回,退出状态大于128,然后立即执行trap。