jobs 命令详解与参数实测(Ubuntu 22.04)
基于 Ubuntu 22.04(bash 5.1)逐参数实测 jobs:无参数作业表、-l/-p/-r/-s/-n 过滤、%N/%%/%+/%-/%name 作业号语法、Running/Stopped/Done 状态转换、disown 作业脱离,以及 no job control 踩坑与 ps 对照实战。
jobs 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 jobs 的每一个常用参数——作业表查看、
-l/-p/-r/-s/-n状态过滤、jobspec 作业号语法(%1、%%、%+、%-、%name)、作业状态(Running/Stopped/Done/Terminated)识别、disown作业脱离,以及与ps的进程对照,全部命令与回显已在 Ubuntu 22.04(bash 5.1)实测通过。
一、环境信息
本文所有命令与回显均在以下环境实测采集,读者在相同版本的 Ubuntu 上可直接复现:
| 项目 | 实测值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS |
| 架构 | x86_64 |
| Shell | GNU bash,版本 5.1.16(1)-release |
| jobs 命令类型 | shell builtin(Shell 内置命令,非外部可执行文件) |
| 实测演示目录 | /root/demo/jobs |
确认命令与实测回显如下:
$ head -2 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
$ uname -m
x86_64
$ echo "BASH_VERSION=$BASH_VERSION"
BASH_VERSION=5.1.16(1)-release
$ type jobs
jobs is a shell builtin
💡 说明:
jobs是 bash 的内置命令,随 bash 一同提供,不存在独立的jobs可执行文件。因此它没有--version一类的独立版本号,版本信息即 bash 的版本信息(见 2.3 节)。
二、命令概览
2.1 语法与测试数据
本节先给出 jobs 的语法形态,并准备一个干净的演示目录与演示文件,后续所有实测都在此基础上进行。
jobs 的语法:
jobs [-lnprs] [jobspec ...]
jobs -x command [args]
参数速览:
| 参数/语法 | 作用 | 实测结论 |
|---|---|---|
| 无参数 | 显示当前所有作业的状态 | 显示作业号、+/− 标记、状态与命令行 |
-l | 在常规信息之外附加显示进程 PID | PID 与 ps 中一致 |
-p | 仅列出作业进程的 PID | 可直接喂给 kill 批量终止 |
-r | 仅显示正在运行(Running)的作业 | 常与 -s 互斥使用 |
-s | 仅显示已停止(Stopped)的作业 | 空表时无输出 |
-n | 仅显示自上次通知以来状态发生变化的作业 | 连续两次调用第二次为空 |
-x | 将 jobspec 替换为对应进程组长的 PID 后执行 command | 冷门用法,日常少用 |
jobspec | %1、%%、%+、%-、%name 等作业引用方式 | 详见 3.7 节 |
准备演示目录与演示文件(一个供 tail -f 持续读取的日志文件,3.7 节按名匹配时使用):
$ mkdir -p /root/demo/jobs && echo "demo log line" > /root/demo/jobs/demo.log && cd /root/demo/jobs && pwd && ls -l
/root/demo/jobs
total 4
-rw-r--r-- 1 root root 14 Oct 15 10:19 demo.log
本篇实测的核心演示数据为两个长睡眠后台作业,同时启动后作为整篇的作业表基础:
$ sleep 300 & sleep 301 &
💡 技术拓展:
jobs之前需要先认识「作业(job)」与「进程(process)」的关系——bash 把一行命令(可以是单条命令,也可以是管道等序列)作为一个作业管理,一个作业对应一个或一组进程。查看进程的工具有多种视角可选:
工具/视角 管理对象 典型输出 适用场景 jobs shell 会话内的作业(本进程的子进程) [1]+ Running sleep 300 &作业调度、前后台切换 ps 全系统的进程(任意用户) PID/PPID/PGID/STAT/CMD 全局巡检、定位僵尸进程 top/htop 全系统的进程(动态刷新) 实时 CPU/内存占用 性能观测 pstree 进程树 父子层级树状图 分析进程派生关系 本文主角 jobs 只能看到「当前这个 shell 自己派生的后台作业」,看不到其他会话的进程;全系统视角则交给 ps(对照实测见 3.8 节与 4.4 节)。
2.2 help 全量输出
jobs 是内置命令,帮助信息通过 help jobs 获取。以下为实测的完整输出(逐行照抄):
$ help jobs
jobs: jobs [-lnprs] [jobspec ...] or jobs -x command [args]
Display status of jobs.
Lists the active jobs. JOBSPEC restricts output to that job.
Without options, the status of all active jobs is displayed.
Options:
-l lists process IDs in addition to the normal information
-n lists only processes that have changed status since the last
notification
-p lists process IDs only
-r restrict output to running jobs
-s restrict output to stopped jobs
If -x is supplied, COMMAND is run after all job specifications that
appear in ARGS have been replaced with the process ID of that job's
process group leader.
Exit Status:
Returns success unless an invalid option is given or an error occurs.
If -x is used, returns the exit status of COMMAND.
输出解读:jobs 接受 -lnprs 五个状态过滤选项与 jobspec 作业号限定;-x 形态会把命令参数里出现的 jobspec 替换成对应作业组长的 PID 再执行;退出码方面,正常情况返回成功,选项非法或出错时返回失败。
2.3 版本信息
jobs 无独立版本号,其能力由 bash 决定。实测版本确认:
$ bash --version | head -1
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
$ echo $BASH_VERSION
5.1.16(1)-release
三、参数逐项实测
本章按「无参数 → 五个选项 → jobspec 语法 → 状态转换 → disown」的顺序逐项实测。每个参数都给出实测命令与真实回显,并在要点中说明适用场景。
3.1 无参数:查看当前作业表
不加任何参数时,jobs 显示当前 shell 的全部活跃作业:作业号、当前/上一作业标记(+/−)、状态与命令行。先在无作业的干净表上执行一次,确认空表行为:
$ set -m; jobs; echo rc=$?
rc=0
空表时无任何输出、退出码为 0(本篇除 5.1 节反例外的所有实测均在 set -m 开启作业控制的 shell 中进行,原因见 5.1 节)。启动两个后台作业后再看:
$ sleep 300 & sleep 301 &
$ jobs
[1]- Running sleep 300 &
[2]+ Running sleep 301 &
输出解读:
[1]、[2]是作业号(job number),是 shell 会话内的逻辑编号,从 1 递增;+标记「当前作业」(最后启动的那个,即%+),-标记「上一个作业」(即%-);Running是作业状态,本章 3.8 节覆盖全部状态值;- 行尾的
&是命令本身的启动方式回显,表示它是以后台方式启动的。
💡 要点:
+/-标记决定了fg、bg、kill等命令在不写作业号时的默认目标——%+(或省略)取+作业,%-取-作业。
3.2 -l:附加显示进程 PID
-l(list process IDs)在每行作业前附加显示该作业的进程 PID,是与 ps 打通的关键选项:
$ jobs -l
[1]- 394 Running sleep 300 &
[2]+ 395 Running sleep 301 &
输出解读:394、395 即两个 sleep 进程的真实 PID,可直接用于 ps -p 394、kill 394 等按 PID 操作。作业状态变化时 jobs -l 的行首标记会随之变化(如 Stopped、Done),PID 部分始终不变。
💡 要点:记住「作业号是会话内的、PID 是全系统唯一的」这对概念——
jobs -l正是把两者关联起来的桥梁。
3.3 -p:仅列出进程 PID
-p 只输出 PID 数字,不带任何装饰,天然适合脚本里做变量收集或批量操作:
$ jobs -p
394
395
输出解读:与 jobs -l 相比去掉了作业号、标记、状态与命令行,只剩纯 PID 列表。配合命令替换可以一步拿到全部后台作业的 PID,批量操作实战见 4.2 节(PIDS=$(jobs -p) 收集后统一 kill)。
💡 要点:
kill $(jobs -p)是「杀掉本会话全部后台作业」的经典写法,4.2 节有完整实战。
3.4 -r:仅列出正在运行的作业
-r(running)把输出过滤为 Running 状态的作业。当前两个作业都在运行:
$ jobs -r
[1]- Running sleep 300 &
[2]+ Running sleep 301 &
等其中部分作业进入其他状态后,-r 的过滤效果更直观(见 3.8 节 STOP 之后的对比)。
3.5 -s:仅列出已停止的作业
-s(stopped)只显示 Stopped 状态的作业。当前没有已停止的作业,输出为空:
$ jobs -s
空输出本身就是有效结论:-s 与 -r 按状态互补过滤,组合 jobs -r 与 jobs -s 可以分别盘点「在跑的」与「被挂起的」作业。制造一个 Stopped 作业后再看,效果见 3.8 节。
3.6 -n:仅列出状态发生变化的作业
-n(notification)只报告「自上次通知以来状态发生了变化」的作业,用于增量感知作业状态。让一个短作业自然结束后连续调用两次 -n 对比:
$ sleep 1 &
$ sleep 3
$ jobs -n; echo "rc=$?"
[3]+ Done sleep 1
rc=0
$ jobs -n; echo "rc=$?"
rc=0
输出解读:第一次 jobs -n 报告了刚结束的 sleep 1(状态 Running→Done 的变化从未通知过);第二次调用时该变化已被上一次通知消费掉,输出为空。
💡 要点:
-n的语义是「增量通知」。写在循环巡检脚本里,每次只拿到新发生状态变化的作业,避免重复处理已报告过的作业。
3.7 jobspec 作业号语法:%N / %% / %+ / %- / %name
jobspec 是 bash 引用作业的统一语法,jobs、fg、bg、kill、wait、disown 都认它。逐个实测:
$ jobs %1
[1]- Running sleep 300 &
$ jobs %2
[2]+ Running sleep 301 &
%N 按「作业号 N」精确匹配,最直白。
$ jobs %%
[2]+ Running sleep 301 &
$ jobs %+
[2]+ Running sleep 301 &
%% 与 %+ 等价,都引用「当前作业」(带 + 标记的)。
$ jobs %-
[1]- Running sleep 300 &
%- 引用「上一个作业」(带 - 标记的)。
按命令名前缀匹配。为演示成功形态,先启动一个名字唯一的作业:
$ tail -f /root/demo/jobs/demo.log & TAILPID=$!
$ jobs -l
[1] 394 Running sleep 300 &
[2]- 395 Running sleep 301 &
[3]+ 396 Running tail -f /root/demo/jobs/demo.log &
$ jobs %tail
[3]+ Running tail -f /root/demo/jobs/demo.log &
%name 按命令名(可只写前缀)匹配作业。当名字匹配到多个作业时触发歧义报错,两个 sleep 同名正好演示(为贴近读者终端形态,此处用 bash -c 载体实测,报错前缀为 bash: line 1:;在脚本文件内执行时前缀为脚本名与行号,交互终端则为 bash:):
$ bash -c "sleep 300 & sleep 301 & jobs %sleep"
bash: line 1: jobs: sleep: ambiguous job spec
bash: line 1: jobs: %sleep: no such job
%?name 是更宽松的匹配:按「完整命令行的子串」查找。%?sleep 依旧命中两个作业而歧义;用参数 301 做子串则唯一命中:
$ bash -c "sleep 300 & sleep 301 & jobs %?sleep"
bash: line 1: jobs: sleep: ambiguous job spec
bash: line 1: jobs: %?sleep: no such job
$ jobs %?301
[2]+ Running sleep 301 &
引用不存在的作业:
$ bash -c "jobs %nojob"
bash: line 1: jobs: %nojob: no such job
jobspec 语法汇总:
| 写法 | 含义 | 实测行为 |
|---|---|---|
%N | 作业号 N | 精确匹配 |
%% / %+ | 当前作业(+ 标记) | 取最后启动/切换的作业 |
%- | 上一个作业(- 标记) | 取次新的作业 |
%name | 以 name 开头的命令名 | 唯一匹配成功;多作业同名报 ambiguous job spec |
%?name | 命令行中含 name 子串的作业 | %?301 成功命中;子串仍多处命中时报歧义 |
清理演示用的 tail 作业(kill 产生的状态报告会由 shell 打印在下一行,属正常现象):
$ kill -9 $TAILPID; sleep 0.3; jobs; echo "tail 已清理 rc=$?"
396 Killed tail -f /root/demo/jobs/demo.log
[1]- Running sleep 300 &
[2]+ Running sleep 301 &
tail 已清理 rc=0
💡 要点:
%name匹配的是「作业的命令名」而非参数——%sleep对sleep 300与sleep 301会同时命中;同名作业较多时建议直接用%N消歧。
3.8 作业状态与状态转换(Running / Stopped / Done / Terminated)
jobs 输出里的状态字段共四类,本节用一次完整的状态转换把它们全部呈现。当前两个作业均 Running,先制造 Stopped:对当前作业发送 SIGSTOP 信号(交互式终端中按 Ctrl-Z 的效果与之等效,均使进程进入暂停态):
$ kill -STOP %+
$ jobs
[1]- Running sleep 300 &
[2]+ Stopped sleep 301
注意两个细节:Stopped 行尾没有 &(它不再「运行中」,也不是正常退出);sleep 301 从 Running 转入 Stopped。此时用 -r 与 -s 过滤,互补关系一目了然:
$ jobs -r
[1]- Running sleep 300 &
$ jobs -s
[2]+ Stopped sleep 301
发送 SIGCONT 恢复运行,作业回到 Running 且 & 回归:
$ kill -CONT %+
$ jobs
[1]- Running sleep 300 &
[2]+ Running sleep 301 &
再让一个作业自然结束,观察 Done:
$ sleep 1 &
$ sleep 3
$ jobs; echo "rc=$?"
[1]- Running sleep 300 &
[2]+ Running sleep 301 &
[3]+ Done sleep 1
rc=0
最后是被信号终止的 Terminated(kill 后 shell 在下一次状态检查点打印):
$ kill %1 %2; sleep 0.3; jobs
[1]- Terminated sleep 300
[2]+ Terminated sleep 301
状态对照总结:
| 状态 | 含义 | 行尾 & | 典型触发方式 |
|---|---|---|---|
| Running | 正在运行 | 有 | cmd & 启动、bg 恢复、kill -CONT |
| Stopped | 已暂停(信号 SIGSTOP/TSTP) | 无 | 交互 Ctrl-Z、kill -STOP %N |
| Done | 正常退出(退出码 0) | 无 | 短作业自然结束 |
| Terminated | 被信号(默认 SIGTERM)终止 | 无 | kill %N |
| Killed | 被 SIGKILL 强杀 | 无 | kill -9 %N(3.7 节实测) |
💡 要点:Done/Terminated 的作业还留在作业表里,直到 shell 做一次状态通知(显式 jobs、或交互终端打印提示)才真正移除;期间仍可用
jobs -n增量感知(3.6 节)。
3.9 disown:让作业脱离作业表
disown 把作业从 shell 的作业表中删除,进程本身继续存在——jobs 看不到了,ps 还在。先启动并立即 disown(用 $! 取刚启动的 PID):
$ sleep 302 & PID302=$!; disown $PID302
再看作业表——已无该作业:
$ jobs; echo "rc=$?"
rc=0
用 ps 确认进程仍存活(STAT 为 S 表示可中断睡眠,即 sleep 的正常等待态):
$ ps -o pid,ppid,pgid,stat,cmd -p $PID302
PID PPID PGID STAT CMD
405 382 405 S sleep 302
验证后清理该进程:
$ kill $PID302; sleep 0.3; echo "清理 disown 进程 rc=$?"
清理 disown 进程 rc=0
💡 技术拓展:让命令「不挂在当前 shell 会话上」的常见方式不止 disown 一种,横向对比——
方式 原理 会话断开后 典型场景 cmd &后台作业,仍属作业表 随会话可能收到 SIGHUP 临时后台任务 nohup cmd &忽略 SIGHUP,输出重定向 nohup.out 存活 断网跑长任务 disown %N事后从作业表摘除(不改变已忽略与否的信号配置) 存活 作业已启动才想起要防断连 setsid cmd &新会话新进程组,脱离控制终端 存活 守护化启动 本文演示 disown,因为它的定位是「配合 jobs/作业表」使用:先把作业跑起来,再决定要不要摘除。nohup 的完整用法见同专栏《nohup 命令详解与参数实测(Ubuntu 22.04)》篇。
四、实战示例
本章给出 4 个以 jobs 为核心的组合场景,全部为实测原样回显。
4.1 后台任务 + wait 收集退出码
后台作业的退出码不会自动带回,用 wait $! 等待并收集。timeout 会在 3 秒时终止 sleep 100,退出码 124:
$ timeout 3 sleep 100 & wait $!; echo "wait rc=$?"
wait rc=124
💡 要点:
wait $!中$!是最近一个后台作业的 PID;wait 也能接受 jobspec(如wait %1)。这是脚本里「并行执行再统一收码」的标准姿势。
4.2 批量后台任务收集 PID 并统一终止
启动两个后台作业,用 jobs -p 一次性收集 PID,再批量 kill:
$ sleep 310 & sleep 311 & jobs -p
410
411
$ PIDS=$(jobs -p); echo "收集到的 PID: $PIDS"
收集到的 PID: 410
411
$ kill $PIDS; sleep 0.3; jobs
[1]- Terminated sleep 310
[2]+ Terminated sleep 311
输出解读:$(jobs -p) 的多行输出被 kill 展开为多个参数,等价于 kill 410 411;jobs 复核显示两个作业均 Terminated。
4.3 用 kill -STOP/-CONT 模拟作业暂停与恢复
不依赖交互按键,纯脚本环境对后台作业做「暂停 → 查看 → 恢复」的闭环:
$ sleep 312 & kill -STOP %+; jobs -l; kill -CONT %+; sleep 0.3; jobs
[1]+ 414 Stopped (signal) sleep 312
[1]+ Running sleep 312 &
$ kill %+; sleep 0.3; jobs
[1]+ Terminated sleep 312
输出解读:jobs -l 抓到了 Stopped (signal) 中间态(signal 表示由 SIGSTOP 类信号导致,而非终端 Ctrl-Z 的 SIGTTIN/TSTP);CONT 后回到 Running;最后 kill 收尾。
4.4 jobs -l 与 ps 的进程对照
同一个 sleep 320 作业,jobs 侧与 ps 侧各看一次,PID 完全对上:
$ sleep 320 &
$ jobs -l
[1]+ 401 Running sleep 320 &
$ ps -o pid,ppid,pgid,sid,stat,cmd $(jobs -p)
PID PPID PGID SID STAT CMD
401 382 401 382 S sleep 320
输出解读:PID 401 两边一致;ps 侧额外给出 PGID 401(该作业独立成进程组,组号=组长 PID)、SID 382(所属会话)、STAT S(睡眠)。验证后清理:
$ kill %+; sleep 0.3; jobs
[1]+ Terminated sleep 320
五、注意事项与常见问题
5.1 作业控制开关:no job control 踩坑(本篇最大坑)
Q1:为什么在脚本里跑 fg/bg 会报 no job control,jobs 却能看到作业?
bash 的「作业控制」(job control)只在交互式会话默认开启;脚本等非交互 shell 默认关闭(可用 echo $- 验证:脚本中为 hB,set -m 后变为 hmB,多出的 m 即 monitor mode)。实测两种形态:
无作业控制时(set +m,非交互默认)——jobs 仍能列出后台作业(作业表存在),但 fg/bg 直接报错:
$ sleep 600 & PID600=$!
$ jobs; echo "jobs rc=$?"
[1]+ Running sleep 600 &
jobs rc=0
$ bash -c "sleep 60 & jobs; fg %1; true"
[1]+ Running sleep 60 &
bash: line 1: fg: no job control
$ bash -c "sleep 60 & jobs; bg %1; true"
[1]+ Running sleep 60 &
bash: line 1: bg: no job control
$ kill %1; echo rc=$?
rc=0
(报错行前缀随执行载体变化:交互终端为 bash:;脚本文件内为 脚本名: 行号:;bash -c 为 bash: line 1:。以下同。)
set -m 开启作业控制后,fg/bg 恢复可用,且 jobs 输出开始出现 +/- 标记、kill 信号能按进程组投递——本篇 3.1 节之后的所有实测都在 set -m 下进行:
$ set -m; echo "启用后 \$- 选项串: $-"
启用后 $- 选项串: hmB
结论:要在脚本里使用 jobs/fg/bg 的完整功能,脚本开头加 set -m;或者直接在交互式终端操作。注意「jobs 看得到作业」不代表「作业控制已开启」,两者是独立的两件事。
5.2 Done 提示:交互终端自动打印,脚本里要显式 jobs
Q2:为什么教程里说后台作业结束会打印 [1]+ Done ...,我的脚本里没有?
「作业状态变化时自动打印提示」是交互式终端的行为(在显示提示符前检查作业状态)。脚本(非交互)里即使开启 set -m 也不会自动打印,必须显式调用 jobs(或 wait)才能看到,实测见 3.8 节 Done 段落——sleep 3 前台执行期间 sleep 1 已结束,Done 信息出现在显式的 jobs 输出中,而非自动插印。
5.3 状态更新时序:kill 后立即 jobs 可能看到旧状态
Q3:为什么 kill -STOP %+ 之后紧接着的 jobs 有时还显示 Running?
shell 对子进程状态的回收依赖 SIGCHLD 的处理时机,信号发出到作业状态刷新之间存在一个短暂窗口。实测中同样的 STOP 操作,间隔一个命令后再查就能稳定看到 Stopped(3.8 节),紧贴着查则可能拿到旧的 Running 快照。写脚本时在 kill 与状态检查之间插入 sleep 0.3 一类的缓冲即可稳定。本文 4.2、4.3 节的 sleep 0.3; jobs 正是为此。
5.4 其他高频问题
Q4:jobs %sleep 为什么报 ambiguous job spec?
按名匹配命中了多个作业(sleep 300 与 sleep 301 都是 sleep)。改用 %N 精确作业号,或用更长的命令名前缀消歧(实测见 3.7 节 %tail 成功、%sleep 歧义的对照)。
Q5:作业号会复用吗?
新启动的作业号按表内现存作业单调递增;当作业表中所有作业都被移除(状态通知完毕)后,下一个作业号重新从 1 计起(实测 3.8 节末尾清表后新作业显示 [1]+)。因此脚本里不要把作业号写死复用,需要引用时用 $! 记 PID 或实时查询。
Q6:jobs 和 ps 该用哪个?
看本会话作业用 jobs(能拿到作业号、+/− 标记,可与 fg/bg/kill %N 联动);做全系统巡检、排查与本会话无关的进程用 ps(对照实测见 4.4 节)。jobs -l 与 jobs -p 是两者之间的桥。
Q7:kill 能直接接 jobspec 吗?
能。kill 是内置命令,kill %1、kill %+、kill %1 %2 都合法(3.8 节实测);在无作业控制形态下 kill %1 也生效(5.1 节实测 rc=0)。外部命令 /bin/kill 则不认识 %N 语法。
六、参考链接
- bash 手册(Bash Reference Manual,Job Control 章节):https://www.gnu.org/software/bash/manual/html_node/Job-Control.html
- bash 内置命令 jobs 官方文档:https://www.gnu.org/software/bash/manual/html_node/Job-Control-Builtins.html
- POSIX Shell Command Language(jobs 规范):https://pubs.opengroup.org/onlinepubs/9699919799/utilities/jobs.html
- man7.org Linux man-pages(ps/kill 等进程工具):https://man7.org/linux/man-pages/man1/ps.1.html
- Ubuntu 22.04 bash 软件包页:https://packages.ubuntu.com/jammy/bash