3.7 执行命令

评论 0 浏览 0 2023-01-26

1 简单的命令扩展

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

  1. 解析器标记为变量赋值(命令名称前面的那些)和重定向的词被保存起来,以便以后处理。
  2. 不属于变量赋值或重定向的词会被展开(见3.5 Shell的扩展)。 如果在展开后仍有任何词,第一个词会被认为是命令的名称,其余的词是参数。
  3. 重定向是按上述方法进行的(见重定向)。
  4. 在每个变量赋值中,‘=’之后的文本在被赋值到变量之前都要经过波浪号扩展、参数扩展、命令替换、算术扩展和引号去除等步骤。

如果没有命令名称的结果,变量赋值会影响当前的shell环境。 如果是这样的命令(仅由赋值语句和重定向组成的命令),赋值语句会在重定向之前执行。 否则,变量会被添加到被执行的命令的环境中,不会影响当前的shell环境。 如果任何赋值试图为一个只读变量赋值,会发生错误,并且命令会以非零状态退出。

如果没有命令名称的结果,就会进行重定向,但不影响当前的shell环境。重定向错误会导致命令以非零状态退出。

如果在扩展后还有一个命令名,则按下文所述继续执行。否则,命令退出。如果其中一个扩展包含了一个命令替换,那么该命令的退出状态就是最后一次执行的命令替换的退出状态。如果没有命令替换,命令的退出状态为0。

2 命令搜索和执行

在一个命令被分割成单词后,如果它的结果是一个简单的命令和一个可选择的参数列表,则会采取以下行动。

  1. 如果命令的名字不包含斜线,shell会尝试定位它。如果存在一个以该名称命名的shell函数,该函数将按照3.3 Shell函数中的描述被调用。
  2. 如果名称与一个函数不匹配,shell会在shell的内置函数列表中搜索它。如果找到了匹配的函数,该内置函数就会被调用。
  3. 如果这个名字既不是shell函数也不是内置程序, 并且不包含斜线, Bash就会搜索$PATH的每个元素, 以寻找包含这个名字的可执行文件的目录。Bash使用一个哈希表来记忆可执行文件的完整路径名以避免多次PATH搜索(见4.1 Bourne Shell内置程序中对hash的描述)。 只有在哈希表中没有找到该命令时,才会对$PATH中的目录进行全面搜索。 如果搜索不成功,Shell会搜索一个名为command_not_found_handle的定义的Shell函数。如果该函数存在,它将在一个单独的执行环境中以原始命令和原始命令的参数作为参数被调用,并且该函数的退出状态成为该子shell的退出状态。 如果该函数没有被定义,shell将打印一个错误信息并返回127的退出状态。
  4. 如果搜索成功,或者命令名称包含一个或多个斜线,shell将在一个单独的执行环境中执行命名的程序。 参数0被设置为所给的名称,命令的其余参数被设置为所提供的参数,如果有的话。
  5. 如果因为文件不是可执行格式而执行失败,并且文件不是一个目录,那么就会假定它是一个shell脚本,并且shell会按照Shell Scripts中的描述来执行它。
  6. 如果该命令不是以异步方式开始的,shell将等待该命令的完成并收集其退出状态。

3 命令的执行环境

shell有一个执行环境,它由以下部分组成。

  • 打开在调用时由shell继承的文件,并由提供给exec内建程序的重定向所修改。
  • cdpushdpopd设置的当前工作目录,或由shell在调用时继承的工作目录
  • umask设置或从 shell 的父级继承的文件创建模式掩码。
  • 当前由trap设置的陷阱
  • 通过变量赋值或用set设置的shell参数,或从环境中的shell父辈继承的shell参数。
  • 在执行过程中定义的或从环境中的shell父辈继承的shell功能。
  • 在调用时启用的选项(默认情况下或使用命令行参数),或由set
  • shopt启用的选项(见2 Shopt 内置程序)。
  • alias定义的shell别名(见6.6 别名)。
  • 各种进程的ID,包括背景工作的ID(见4 命令列表),$$的值,以及$PPID的值。

当要执行一个除内置函数或shell函数以外的简单命令时,它将在一个单独的执行环境中被调用,该环境由以下内容组成。除非另有说明,这些值都是从shell中继承的。

  • shell打开的文件,加上任何由重定向到命令所指定的修改和补充。
  • 当前的工作目录
  • 文件创建模式的掩码
  • 标记为输出的shell变量和函数,以及为命令输出的变量,在环境中传递(见4 环境)。
  • 被shell捕获的陷阱被重置为从shell父类继承的值,而被shell忽略的陷阱则被忽略。

在这个独立的环境中调用的命令不能影响shell的执行环境。

一个subshell是shell进程的一个副本。

命令替换、用括号分组的命令和异步命令在子shell环境中调用,子shell环境是shell环境的复制品,除了shell捕获的陷阱被重置为shell在调用时从其父级继承的值。作为管道的一部分被调用的内置命令也在子shell环境中执行。对子shell环境的改变不能影响shell的执行环境。

为执行命令替换而产生的子shell会继承父壳的-e选项的值。当不在POSIX模式下时,Bash会清除此类子shell中的-e选项。

如果一个命令后面有‘&’,并且作业控制没有激活,那么该命令的默认标准输入是空文件/dev/null。 否则,被调用的命令会继承调用的shell的文件描述符,这些描述符被重定向所修改。

4 环境

当一个程序被调用时,它被赋予一个称为environment的字符串数组。 这是一个名-值对的列表,其形式为name=value

Bash提供了几种操作环境的方法。 在调用时, shell会扫描它自己的环境并为每一个发现的名字创建一个参数, 自动将其标记为export给子进程。执行的命令会继承环境。 export和‘declare -x’命令允许将参数和函数添加到环境中或从环境中删除。如果环境中某个参数的值被修改,新的值就会成为环境的一部分,取代旧的。任何被执行的命令所继承的环境由shell的初始环境组成,其值可以在shell中修改,减去任何由unset和‘export -n’命令删除的对,加上任何通过export和‘declare -x’命令增加的内容。

3.4 shell参数中所述,任何简单的命令或函数的环境都可以通过参数赋值的方式临时增加。 这些赋值语句只影响到该命令所看到的环境。

如果设置了-k选项(见1 Set 内置程序),那么所有的参数赋值都会放在命令的环境中,而不仅仅是放在命令名前面的参数。

当Bash调用一个外部命令时,变量‘$_’被设置为该命令的完整路径名,并在其环境中传递给该命令。

5 退出状态

一个被执行的命令的退出状态是由waitpid系统调用或等效函数返回的值。退出状态在0到255之间,不过,正如下面所解释的,shell可以特别使用125以上的值。来自shell内置程序和复合命令的退出状态也被限制在这个范围内。在某些情况下,shell会使用特殊的值来表示特定的失败模式。

对于shell来说,一个退出状态为0的命令是成功的。 一个非零的退出状态表示失败。 这个看似反直觉的方案的使用是为了有一种定义明确的方式来表示成功,并有多种方式来表示各种失败模式。 当一个命令终止于一个编号为N的致命信号时,Bash使用128+N的值作为退出状态。

如果没有找到命令,为执行该命令而创建的子进程返回状态为127。如果找到了一个命令但不能执行,返回状态为126。

如果一个命令在扩展或重定向过程中因错误而失败,则退出状态大于零。

Bash的条件性命令(见5.2 条件性结构)和一些列表结构(见4 命令列表)都会用到退出状态。

所有的Bash内置程序在成功时返回0的退出状态,在失败时返回非0的状态,因此它们可以被条件和列表结构使用。 所有的内置程序都返回2的退出状态,表示使用不正确,通常是无效的选项或缺少参数。

最后一条命令的退出状态在特殊参数$? (见2 特殊参数)中可以获得。

6 信号

当Bash是交互式的时候, 在没有任何陷阱的情况下, 它忽略了SIGTERM (所以‘kill 0’不会杀死一个交互式的shell), 并且SIGINT被捕获和处理(所以wait内置程序是可中断的).当Bash收到一个SIGINT时, 它就会脱离任何正在执行的循环. 在所有情况下, Bash都会忽略SIGQUIT. 如果工作控制是有效的(见7 作业控制), Bash会忽略SIGTTIN, SIGTTOU, 和SIGTSTP.

由Bash启动的非内置命令的信号处理程序被设置为shell从其父级继承的值。 当作业控制没有生效时,除了这些继承的处理程序外,异步命令还忽略了SIGINTSIGQUIT。 由于命令替换而运行的命令忽略了键盘生成的作业控制信号SIGTTINSIGTTOUSIGTSTP

shell在收到SIGHUP时默认退出。 在退出之前,交互式shell会向所有正在运行或停止的作业重新发送SIGHUP。 停止的作业会被发送SIGCONT,以确保它们收到SIGHUP。 为了防止shell向某个特定的作业发送SIGHUP信号,应该用disown内置程序将其从作业表中删除(见7.2 作业控制内置程序)或用disown -h标记为不接收SIGHUP

如果huponexit shell选项被设置为shopt(参见2 Shopt 内置程序),当交互式登录shell退出时,Bash会向所有作业发送一个SIGHUP

如果Bash正在等待一个命令的完成,并且收到一个已经设置了陷阱的信号,那么在命令完成之前,陷阱将不会被执行。 当Bash通过wait内置程序等待一个异步命令时,收到一个已经设置了陷阱的信号将导致wait内置程序立即返回,退出状态大于128,紧接着,陷阱就会被执行。

当作业控制未被启用时,Bash正在等待一个前台命令的完成,shell会收到键盘产生的信号,如SIGINT(通常由‘^C’产生),用户通常打算将其发送给该命令。这是因为shell和命令与终端在同一个进程组中,而‘^C’向该进程组中的所有进程发送SIGINT。 参见7 作业控制,以了解对进程组的更深入讨论。

当Bash在没有启用作业控制的情况下运行,并在等待前台命令时收到SIGINT时,它会等待,直到该前台命令终止,然后决定如何处理SIGINT

  1. 如果命令因SIGINT而终止,Bash就会认为用户是想结束整个脚本,并对SIGINT采取行动(例如,运行一个SIGINT陷阱或退出自己)。
  2. 如果流水线没有因为SIGINT而终止,那么程序自己处理了SIGINT,并且没有把它当作致命信号。 在这种情况下,Bash也不会把SIGINT当作致命信号,而是假设SIGINT是作为程序’正常操作的一部分(例如,emacs用它来中止编辑命令)或者故意丢弃。然而,Bash会运行任何设置在SIGINT上的陷阱,就像它在等待前台命令完成时收到的任何其他陷阱信号一样,以实现兼容性。
最后更新2023-03-02
0 个评论
上一篇: 3.6 重定向
下一篇: 3.8 Shell脚本