7.1 作业控制基础(Job Control Basics)
作业控制所指的是可选择性地停止(或暂停)进程的执行和在后继节点可继续(或恢复)其执行的能力。通常,一个用户通过由操作系统内核的终端驱动和 Bash 联合提供的交互接口来使用此控制工具。
shell 将作业(job)与每一个管道关联起来。其会持有一个当前执行中的作业表,可使用 jobs 命令将该表列出。当 Bash 以异步方式启动一个作业时,其会打印一个类似于“[1] 25647”这样的一行。其中中括号里的 1 表示作业编号,后面的数字 25647 是关联到这个作业的管道中的最后一个进程的 ID。在一个单管道中的所有进程是同一作业的成员。Bash 使用 job(即作业)这个抽象的概念来作为作业控制的基础。
对于作业控制的用户接口执行工具,操作系统使用当前终端进程组 ID 的概念来进行维护。该进程组的成员(即当前终端进程组 ID 等同于进程的进程组 ID 的所有进程)接收由键盘产生的信号,如 SIGINT。这些进程表述为在前台的进程。后台进程是进程组 ID 不同于终端的进程;这样的进程是对键盘产生的信号是免疫的。只有前台进程允许从终端进行读取,或如果用户指定了“stty tostop”,则可向终端写入。由内核的终端驱动向试图从终端进行读取(当“stty tostop”生效则为写入)的后台进程发送一个 SIGTTIN(或 SIGTTOU)信号,除非该信号被捕获,否则会挂起该进程。
如果运行着 Bash 的操作系统支持作业控制,Bash 包含的控制工具则可使用它。键入挂起字符(通常是“^Z”,即“ctrl-Z”)会使正在运行的进程停止并将控制权返回给 Bash。键入延迟挂起字符(通常是“^Y”,即“ctrl-Y”),当进程试图从终端读取输入时,将该进程停止并将控制权返还给 Bash。之后,用户可以使用命令对这个作业的状态进行操控,使用 bg 命令来使作业继续在后台运行,使用 fg 命令来使作业继续在前台运行,或使用 kill 命令来结束它。“^Z”会立即生效,并且会产生输出悬停和预输入丢弃的额外副作用。
在 shell 中对于作业的引用有几种方法。由字符“%”引入作业规范(jobspec)。
作业编号 n 可引用为“%n”。符号“%%”和“%+”引用为 shell 的当前作业,其是在前台已停止的或在后台已开始的最后一个作业。一个单一的“%”(即不带有作业规范时)同样引用为当前作业。前一个作业可使用“%-”引用。如果只有一个作业,“%+”和“%-”都引用为该作业。在有关作业的输出中(例如使用 jobs 命令的输出),当前作业总是使用“+”标记,并且前一个作业使用“-”标记。
也可以使用启动作业的命令的命令名前缀来引用该作业,或使用出现在该命令行中的子字符串进行引用。例如,“%ce”引用为一个已停止的,命令名以“ce”开头的作业。别一方面,使用“%?ce”引用为在命令行中包含字符串“ce”的任意作业。如果前缀或子字符串匹配到多于一个作业,Bash 会报错。
常使用简化的作业名将作业移入前台:“%1”等同于“fg %1”,是将作业 1 从后台移入前台。同样地,“%1 &”是在后台恢复作业 1 的运行,等同于“bg %1”。
无论作业何时改变状态,shell 都会立即知道。一般,在报告作业状态变化之前,Bash 会进行等待直到其打印提示符,这样就不会打扰到任何其它输出。如果内建命令 set 的“-b”选项(参见 4.3.1《内建命令 set》)开启了,Bash 会立即报告这样的变更。在 SIGCHLD 上的任何捕获都会在每一个子进程退出时执行。
如果在作业停止期间(或者如果开启了 shopt 的“checkjobs”选项,作业运行期间)试图退出 Bash,shell 则打印一个警告消息。并且如果开启了“checkjobs”选项,会列出所有作业和其状态。之后可以使用 jobs 命令来检测作业状态。如果试图进行不带有干预命令的第二次退出,Bash 不会打印另外的警告,并且会终结任意已停止的作业。
当 shell 正在等待一个使用内建命令 wait 启动的作业或进程时,并且已开启了作业控制,在作业状态改变时 wait 将会返回。“-f”选项会使 wait 命令在返回之前等待作业或进程终结。