# 15.1 任务控制命令

以下某些作业控制命令将*作业标识符*作为参数。您可以参阅本章末尾的[表格](#biao-ge-151.-zuo-ye-biao-shi-fu)。

### jobs

列出在后台运行的作业，并给出该*作业ID*。在实际应用中，没有[ps](https://tldp.org/LDP/abs/html/system.html#PPSSREFhttps://tldp.org/LDP/abs/html/system.html#PPSSREF)有用。

> ![note](http://tldp.org/LDP/abs/images/note.gif)非常容易将*作业*和*进程*二者混淆。某些[内建命令](https://tldp.org/LDP/abs/html/internal.html#BUILTINREF) (例如**kill**，**disown**和**wait**) 既接受作业ID，又接受进程ID作为参数。[fg](https://tldp.org/LDP/abs/html/x9644.html#FGREF)、[bg](https://tldp.org/LDP/abs/html/x9644.html#BGREF)和**jobs**命令仅接受一个作业编号。

```shell
bash$ sleep 100 &
[1] 1384

bash $ jobs
[1]+  Running                 sleep 100 &
```

“1” 是作业ID (作业由当前shell维护)。“1384” 是[PID](https://tldp.org/LDP/abs/html/internalvariables.html#PPIDREF)或*进程ID号* (进程由系统维护)。如果要杀死这个作业/进程，可以使用**kill % 1**或**kill 1384**。

*感谢 S.C.*

### disown

从shell的活动作业表中删除作业。

### fg, bg

**fg**命令将在后台运行的作业切换到前台。**bg**命令重新启动一个挂起的作业，并在后台运行它。如果未指定作业ID，则**fg**或**bg**命令将作用于当前运行的作业。

### wait

暂停脚本执行，直到在后台运行的所有作业都终止，或者直到以作业ID或进程ID标识的指定后台作业终止。返回wait-for命令的[退出状态](https://tldp.org/LDP/abs/html/exit-status.html#EXITSTATUSREF)。

您可以使用**wait**命令来阻止脚本在后台作业完成执行之前退出 (这将创建一个可怕的[孤立进程](https://tldp.org/LDP/abs/html/x9644.html#ZOMBIEREF))。

**样例 15-26. 等待一个进程结束后再继续**

```shell
#!/bin/bash

ROOT_UID=0   # 只有UID是0的用户拥有root权限。
E_NOTROOT=65
E_NOPARAMS=66

if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Must be root to run this script."
  # “快跑吧，孩子，已经过了你的就寝时间了。”
  exit $E_NOTROOT
fi  

if [ -z "$1" ]
then
  echo "Usage: `basename $0` find-string"
  exit $E_NOPARAMS
fi


echo "Updating 'locate' database..."
echo "This may take a while."
updatedb /usr &     # 必须使用root权限运行。

wait
# 直到"updatedb"运行结束，不要运行这个脚本剩下部分的代码。
# 你希望在查找文件名之前更新数据库。

locate $1

# 没有"wait"命令，在更加糟糕的情况下，
# 这个脚本将会在"updatedb"仍在运行的情况下退出，
# 并且将它作为一个孤立进程。

exit 0
```

或者，**wait**可以将*作业标识符*作为一个参数，例如，`wait % 1`或`wait $ PPID`。 [\[1\]](https://tldp.org/LDP/abs/html/x9644.html#FTN.AEN9753)请参阅[作业id表](https://tldp.org/LDP/abs/html/x9644.html#JOBIDTABLE)。

{% hint style="info" %}
在脚本中，在后台运行带有 & 符号的命令可能会导致脚本挂起，直到命中**ENTER**。这似乎发生在原本写入`stdout`的命令中。对于程序员来说，这可能是一个很大的烦恼。

```shell
#!/bin/bash
# test.sh          

ls -l &
echo "Done."
```

```shell
bash$ ./test.sh
Done.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
 _
```

正如Walter Brameld IV解释说：

据我所知，这样的脚本实际上并没有挂起。似乎他们这样做是因为后台命令在提示后将文本写入控制台。用户得到的印象是提示从未显示过。这是事件的顺序:

1. 脚本启动后台命令。
2. 脚本退出。
3. Shell输出提示。（译者注：即输出`Done`）
4. 后台命令继续运行并向控制台写入文本。（译者注：即`ls -l`命令的输出信息写入`标准输出(stdout)`）
5. 后台命令运行结束
6. 用户在脚本输出的末尾看不到提示，认为脚本挂起中。

在后台命令之后放置**wait**命令似乎可以解决此问题。

```shell
#!/bin/bash
# test.sh          

ls -l &
echo "Done."
wait
```

```shell
bash$ ./test.sh
Done.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
```

将命令的输出[重定向](https://tldp.org/LDP/abs/html/io-redirection.html#IOREDIRREF)到文件甚至是`/dev/null`也可以解决此问题。
{% endhint %}

### suspend

该命令与**Control-Z**具有类似的效果，但它会挂起shell (shell的父进程应在适当的时间恢复它)。

### logout

退出登录的shell，可以有选择性地指定它的[退出状态](https://tldp.org/LDP/abs/html/exit-status.html#EXITSTATUSREF)。

### times

以以下形式展现执行命令时经过的系统时间的统计信息:

```shell
0m0.020s 0m0.020s
```

此功能的价值相对有限，因为它在配置文件和基准测试shell脚本中并不常见。

### kill

通过向进程发送适当的*终止*信号来强制终止进程 (参见[样例 17-6](https://tldp.org/LDP/abs/html/system.html#KILLPROCESS))。

**样例 15-27. 一个杀死自己的脚本**

```shell
#!/bin/bash
# self-destruct.sh

kill $$  # 这里，脚本杀死自己的进程。
         # 回想一下，"$$"指的是当前脚本的PID。

echo "This line will not echo."
# 相反，shell会将"终止"信息传递给标准输出(stdout)。

exit 0   # 正常地退出？非也！

#  在这脚本过早终止后，
#  它返回了一个怎样的退出状态？
#
# sh self-destruct.sh
# echo $?
# 143
#
# 143 = 128 + 15
#             终止信号
```

> ![note](http://tldp.org/LDP/abs/images/note.gif)`kill -l`列出了所有[信号](https://tldp.org/LDP/abs/html/debugging.html#SIGNALD)(与文件`/usr/include/asm/signal.h`一样)。`kill -9`是一个*有把握的kill*，通常会终止那些顽固地拒绝以简单的`kill`终止的进程。有些情况下，`kill -15`是生效的。*僵尸*进程是指子进程已经被终止，而[父进程](https://tldp.org/LDP/abs/html/internal.html#FORKREF)还没有（已经）被杀死，僵尸进程无法被登录用户杀死 -- 你不能杀死任何已经死去的东西 -- 但是，通常**init**会迟早清理它。

### killall

**killall**命令通过**名称**来杀死一个运行中的程序，而不是通过[进程ID](https://tldp.org/LDP/abs/html/special-chars.html#PROCESSIDREF)。如果某个特定命令的多个实例正在运行，则执行**killall**将终止**所有**实例。

> ![note](http://tldp.org/LDP/abs/images/note.gif)这是影响脚本命令处理的三个shell指令之一。其他二者为[builtin](https://tldp.org/LDP/abs/html/x9644.html#BLTREF)和[enable](https://tldp.org/LDP/abs/html/x9644.html#ENABLEREF)。

### builtin

调用**builtin BUILTIN\_COMMAND**将命令*BUILTIN\_COMMAND*作为shell的[内建命令](https://tldp.org/LDP/abs/html/internal.html#BUILTINREF) 运行，暂时禁用具有相同名称的函数和外部系统命令。

### enable

该命令可以启用或禁用shell内建命令。例如，`enable -n kill`会禁用shell内建命令[kill](https://tldp.org/LDP/abs/html/x9644.html#KILLREF)，因此当Bash随后遇到`kill`命令时，它会调用外部命令`/bin/kill`。

*enable*命令的`-a`选项列出了所有的shell内建命令，并提示它们是否已启用。*enable*命令的`-f 文件名`选项可以从正确编译的对象文件中加载共享库 (DLL) 模块作为[内建命令](https://tldp.org/LDP/abs/html/internal.html#BUILTINREF)。 [\[2\]](https://tldp.org/LDP/abs/html/x9644.html#FTN.AEN9928)。

### autoload

这是*ksh*自动加载器的Bash入口。当**autoload**到位的情况下，带有*autoload*声明的函数将在第一次调用时从外部文件加载。[\[3\]](https://tldp.org/LDP/abs/html/x9644.html#FTN.AEN9949) 这节省了系统资源。

请注意，*autoload*不是核心Bash安装的一部分。它需要用*enable -f*加载 (见上文)。

### 表格 15-1. 作业标识符

| 符号表示 |              意义             |
| :--: | :-------------------------: |
|  %N  |          作业编号 \[N]          |
|  %S  |     以字符串*S*开始的作业调用（命令行）     |
|  %?S |      包含字符串*S*的作业调用（命令行）     |
|  %%  | “当前”作业（指最后一个在前台停止或在后台启动的作业） |
|  %+  | “当前”作业（指最后一个在前台停止或在后台启动的作业） |
|  %-  |            最后一个作业           |
|  $!  |           最后一个后台进程          |

## 注记

{% hint style="info" %}
[\[1\]](https://tldp.org/LDP/abs/html/x9644.html#AEN9753)当然，这仅适用于子进程。

[\[2\]](https://tldp.org/LDP/abs/html/x9644.html#AEN9928)通常能够在`/usr/share/doc/bash-?.??/functions`目录下找到许多可加载的内建命令的C源码。

注意，**enable**命令的`-f`选项并非[可移植](https://tldp.org/LDP/abs/html/portabilityissues.html)到所有系统。

[\[3\]](https://tldp.org/LDP/abs/html/x9644.html#AEN9949)使用[typeset -fu](https://tldp.org/LDP/abs/html/declareref.html)可以达到与**autoload**相同的效果。
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://linuxstory.gitbook.io/advanced-bash-scripting-guide-in-chinese/zheng-wen/part4/15_internal_commands_and_builtins/15_1_job_control_commands.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
