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 对照实战。

Linux命令Linux系统UbuntuUbuntu 22.04命令行进程管理2026/10/15

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
ShellGNU 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在常规信息之外附加显示进程 PIDPID 与 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 把一行命令(可以是单条命令,也可以是管道等序列)作为一个作业管理,一个作业对应一个或一组进程。查看进程的工具有多种视角可选:

工具/视角管理对象典型输出适用场景
jobsshell 会话内的作业(本进程的子进程)[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 语法。

六、参考链接