disown 命令详解与参数实测(Ubuntu 22.04)
以 Ubuntu 22.04(bash 5.1.16)实测逐参数讲解 disown 内建:%N/按 PID 移出作业表、无参数默认当前作业、-r/-a 批量移除、-h 标记不收 SIGHUP,并用真实终端会话实证 SIGHUP 广播与退出 shell 后的作业存活,给出与 nohup 的选型对比。
disown 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 bash 内建 disown 的每一个参数——
disown %N按作业号移出作业表、按 PID 移除、不带参数默认处理当前作业、-r移出运行中作业、-a移出全部、-h保留作业表但标记不收 SIGHUP,并用真实终端(pty)会话实证「bash 收到 SIGHUP 时向作业重发 HUP、disown 过的作业幸免」与「正常退出 shell 时作业是否存活」,最后给出 disown 与 nohup 的选型对比,全部命令与回显已在 Ubuntu 22.04(bash 5.1.16)环境实测通过。
一、环境信息
本文所有命令均在以下环境实测,回显逐字照抄:
- 操作系统:Ubuntu 22.04.5 LTS(
PRETTY_NAME="Ubuntu 22.04.5 LTS") - 内核架构:x86_64
- Shell 环境:GNU bash, version 5.1.16(1)-release(x86_64-pc-linux-gnu)
- bash 软件包版本:5.1-6ubuntu1.1(
dpkg -l bash实测) - 命令归属:disown 是 bash 内建命令(shell builtin),无独立二进制文件
- 执行身份:root
- 演示目录:
/root/demo/disown/(保存本文各参数的实测脚本 disown_0.sh ~ disown_10.sh,每个参数小节均可独立复现) - 实测方式:作业表演示用
sleep 100 &造长活后台任务,SIGHUP 行为用script -qc 'bash -i'开启真实终端(pty)会话实证——非交互脚本里模拟不了终端断开信号的广播路径,必须用真实会话
二、命令概览
2.1 语法与测试数据
disown 把作业从当前 shell 的作业表(job table)中移除,常与 &、jobs 配合:先 命令 & 把任务放到后台,再 disown %1 把它从作业表摘除。摘除的深远意义在于信号路径:交互 shell 收到 SIGHUP(典型场景是终端断开/SSH 会话掉线)退出前,会向作业表中所有作业重发 SIGHUP;被 disown 的作业不在广播名单里,从而在 shell 死亡后继续存活。语法(来自实测 help disown 首行)只有一种形态:
disown: disown [-h] [-ar] [jobspec ... | pid ...]
参数含义:
[jobspec ...]:作业号引用,如%1、%2、%%(当前作业);不给出时默认处理当前作业;[pid ...]:也可以直接写进程 ID;-h:不移出作业表,只把作业标记为「shell 收到 SIGHUP 时不向它转发 HUP」;-r:不带 jobspec 时,移出全部运行中(Running)的作业;-a:不带 jobspec 时,移出全部作业。
本文测试数据约定:作业表演示统一用两条 sleep 100 & 造可观察的后台任务,用 jobs -l 看作业表、kill -0 验证进程存活;SIGHUP 行为演示在真实 pty 会话中进行,会话内用 ( sleep 1; kill -HUP $$ ) & 让交互 bash 在 1 秒后收到 HUP,模拟终端断开时内核发给会话首进程的信号。
💡 技术拓展:让任务在 shell 退出后继续存活,业界有三种常见手段,定位各不相同——
手段 生效层面 典型形态 适用场景 disown shell 作业表层面,事后补救 任务已在跑, disown %1忘了加保护、临时移交已启动的任务 nohup 进程信号处置层面,启动时预防 nohup cmd &一开始就知道要脱离会话长跑 setsid 会话层面,彻底脱离终端 setsid cmd需要全新会话、与终端彻底脱钩 本文实际采用 disown 主线:它解决的是「作业表成员资格 + SIGHUP 广播名单」问题,与 nohup 的「忽略 HUP 信号处置」互补,3.10 节给出逐项对比表。
2.2 type -a 与 help 全量输出(逐行照抄)
disown 是内建命令,先确认归属,再看完整帮助——这是逐参数实测的地图:
$ type -a disown
disown is a shell builtin
$ help disown
disown: disown [-h] [-ar] [jobspec ... | pid ...]
Remove jobs from current shell.
Removes each JOBSPEC argument from the table of active jobs. Without
any JOBSPECs, the shell uses its notion of the current job.
Options:
-a remove all jobs if JOBSPEC is not supplied
-h mark each JOBSPEC so that SIGHUP is not sent to the job if the
shell receives a SIGHUP
-r remove only running jobs
Exit Status:
Returns success unless an invalid option or JOBSPEC is given.
关键信息解读:正文只有一句话——从活跃作业表中移除指定的 JOBSPEC,不给参数时处理当前作业。三个选项分工明确:-a 管全部、-r 管运行中的、-h 特殊(不移出,只改 SIGHUP 转发标记)。返回值语义宽松:除选项或 JOBSPEC 非法外都算成功。
💡 说明:disown 没有独立的
--help/--version选项(内建命令不解析 GNU 长选项),查看帮助用help disown,手册在man bash的 JOB CONTROL 段落。zsh 也提供同名命令且参数更丰富(zsh 的-h行为与 bash 不同),跨 shell 脚本注意差异。
2.3 版本信息
disown 的行为完全由 bash 决定,实测本机 bash 版本与软件包归属:
$ bash --version | head -1
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
$ dpkg -l bash | tail -1
ii bash 5.1-6ubuntu1.1 amd64 GNU Bourne Again SHell
💡 说明:disown 在 bash 各版本间参数语义稳定(
-h/-r/-a自 2.x 起即存在),本文实测结论适用于 bash 4.x~5.x 全系;真正的版本敏感点在 HUP 广播时机等外围行为,3.6 节的实验在本机 5.1.16 下完成。
三、参数逐项实测
3.1 disown %1:按作业号移出作业表(进程继续存活)
基本用法:先造两个后台任务,disown %1 把第一个移出作业表。验证两点——jobs 里它消失了、kill -0 探测它还活着(disown 只是改账本,不碰进程):
sleep 100 & p1=$!
sleep 100 & p2=$!
sleep 0.3
echo "p1=$p1 p2=$p2"
jobs -l
disown %1; echo "disown rc=$?"
sleep 0.3
jobs -l
kill -0 $p1 2>/dev/null && echo "p1=$p1 仍在运行(已不在作业表)"
kill -0 $p2 2>/dev/null && echo "p2=$p2 仍在运行(仍在作业表)"
实测回显:
p1=28565 p2=28566
[1]- 28565 Running sleep 100 &
[2]+ 28566 Running sleep 100 &
--- disown %1 ---
disown rc=0
[2]+ 28566 Running sleep 100 &
--- kill -0 存活检查 ---
p1=28565 仍在运行(已不在作业表)
p2=28566 仍在运行(仍在作业表)
💡 要点:
disown %1返回 0,作业表中只剩[2]+——移除动作本身零副作用,进程的 PID、状态、父子关系都没变(它仍是本 shell 的子进程,只是不再出现在作业表)。kill -0是不发送信号的存活探测:两个进程都探测成功,说明 disown 不会终止或暂停任务。- 对已移出的作业再执行
disown %1会报disown: %1: no such job,返回 1(详见 3.8)。
3.2 disown 按 PID 移除与不带参数(默认当前作业)
help disown 声明参数可以是 jobspec 或 pid。实测两种形态:disown $p1 直接按 PID 移除;不带任何参数时处理「当前作业」(jobs 输出中带 + 号的那个,即最近启动的作业):
sleep 100 & p1=$!
sleep 100 & p2=$!
sleep 0.3
jobs -l
disown $p1; echo "disown $p1 rc=$?"
sleep 0.3
jobs -l
实测回显(按 PID):
[1]- 28669 Running sleep 100 &
[2]+ 28566 Running sleep 100 &
disown 28669 rc=0
[2]+ 28566 Running sleep 100 &
sleep 100 &
sleep 100 &
sleep 0.3
jobs -l
disown; echo "disown(无参数) rc=$?"
sleep 0.3
jobs -l
实测回显(无参数,移除最近的 [2]+):
[1]- 28586 Running sleep 100 &
[2]+ 28587 Running sleep 100 &
disown(无参数) rc=0
[1]+ 28586 Running sleep 100 &
💡 要点:无参数 disown 等价于 disown %%——处理当前作业。注意移除后剩余作业的 +/- 标记会重新分配([1]- 变成 [1]+,因为它成了新的当前作业)。
3.3 disown -r:移出全部运行中的作业
-r(running)在不给 jobspec 时批量移出所有状态为 Running 的作业。 stopped(暂停)状态的作业不会被 -r 移除:
sleep 100 &
sleep 100 &
sleep 0.3
jobs -l
disown -r; echo "disown -r rc=$?"
sleep 0.3
jobs -l
echo '(作业表应为空)'
实测回显:
[1]- 28575 Running sleep 100 &
[2]+ 28576 Running sleep 100 &
disown -r rc=0
(作业表应为空)
💡 要点:两个 Running 作业一次性清空。-r 的精确语义是「移出运行中的作业」——如果有 Ctrl+Z 暂停的 Stopped 作业,它们会留在作业表里,需要 -a 才能全清。
3.4 disown -a:移出全部作业
-a(all)在不给 jobspec 时移出作业表中的一切(Running 与 Stopped 都算)。实测在残留了前序实验编号 [2]、[3] 的作业表上全清:
sleep 100 &
sleep 100 &
sleep 0.3
jobs -l
disown -a; echo "disown -a rc=$?"
sleep 0.3
jobs -l
echo '(作业表应为空)'
实测回显:
[2]- 28582 Running sleep 100 &
[3]+ 28583 Running sleep 100 &
disown -a rc=0
(作业表应为空)
💡 要点:注意作业号不是从 1 连续的——作业号随作业表的占用历史递增(前序实验的编号被沿用),disown/kill 后新作业可能从更高编号起步,引用作业号前先 jobs 确认。-a 与 -r 的区别只在 Stopped 作业:交互 shell 里 Ctrl+Z 暂停的作业只有 -a 能移出。
3.5 disown -h:保留作业表但标记不收 SIGHUP
-h 是最容易被误解的参数:它不移出作业表(jobs 里仍然看得到),只是把作业标记为「shell 收到 SIGHUP 时不向它转发 HUP」。适合「还想随时 fg/bg 管理,但要求它抗终端断开」的任务:
sleep 100 & p3=$!
sleep 0.3
disown -h %1; echo "disown -h rc=$?"
sleep 0.3
jobs -l
kill -0 $p3 2>/dev/null && echo "p3=$p3 仍在运行"
实测回显:
disown -h rc=0
[1]+ 28579 Running sleep 100 &
p3=28579 仍在运行
💡 要点:disown -h 后作业仍在表中([1]+ 可见、可 fg/bg、可再次 disown),它与 3.1 的普通 disown 的差异在 3.6 的 SIGHUP 实验中体现。bash 手册的对应描述:给作业打上 no-HUP 标记,「if the shell receives a SIGHUP」时该作业不被转发 HUP。
3.6 SIGHUP 实证实验:disown 与 disown -h 的保护效果
这是 disown 的核心价值所在,也是最容易以讹传讹的部分,用真实终端会话完整实证。实验设计:用 script -qc 'bash -i' 开启一个真实 pty 交互会话(等价于一次真实登录),会话内启动后台任务,并让一个子 shell 在 1 秒后向交互 bash 发送 SIGHUP——精确模拟「终端断开时内核把 SIGHUP 发给会话首进程」。用 ps -o stat= 判定存活(注意:本环境 init 不收割孤儿进程,被杀进程会以 Z/zombie 残留,kill -0 对僵尸也返回成功,必须看 STAT 才准确)。
实验一:不 disown,plain sleep 与带 HUP-trap 的探针各一个:
会话内序列:
sleep 300 &
S1=$!
bash -c 'trap "echo GOT-HUP-E1 >> /tmp/wdb-hupE1.log" HUP; sleep 300' &
S2=$!
( sleep 1; kill -HUP $$ ) &
wait
实测回显(会话结束后在外层检查):
plain-sleep=27692 -> 已终止(zombie 残留待收割)
trap-probe=27693 -> 已终止(zombie 残留待收割)
HUP 送达记录: GOT-HUP-E1
实验二:先 disown %1,同样场景:
会话内序列:
sleep 300 &
S1=$!
disown %1
( sleep 1; kill -HUP $$ ) &
wait
实测回显:
disowned-sleep=27722 -> 存活(STAT=S)
实验三:disown -h %1(保留作业表),同样场景:
会话内序列:
sleep 300 &
S1=$!
disown -h %1
jobs -l
( sleep 1; kill -HUP $$ ) &
wait
实测回显(会话内 jobs -l 显示 -h 后作业仍在表中;会话结束后检查):
[1]+ 27741 Running sleep 300 &
h-marked-sleep=27741 -> 存活(STAT=S)
三组结果汇总:
| 实验组 | 作业处理 | SIGHUP 后状态 | 结论 |
|---|---|---|---|
| 实验一 | 未 disown | 已终止(HUP 送达,记录文件出现 GOT-HUP-E1) | 交互 bash 收到 SIGHUP 退出前,向作业进程组重发 HUP |
| 实验二 | disown %1 | 存活(STAT=S,真睡眠) | 移出作业表 → 不在 HUP 广播名单 |
| 实验三 | disown -h %1 | 存活(STAT=S,真睡眠) | 仍在表中但不转发 HUP,保护效果与 disown 相同 |
💡 要点:
- 实验一的
GOT-HUP-E1是「bash 确实重发了 SIGHUP」的直接证据:trap 探针在自己的进程组里捕获到了 HUP 并写下记录;探针随后结束,是因为它派生的 sleep 子进程同属该作业进程组、被这份 HUP 终止,探针等子进程收尾后退出。 - plain sleep(默认信号处置)收到 HUP 直接终止——这就是「不保护的后果」。
disown %1与disown -h %1在抗 HUP 上效果一致,差别只在作业表成员资格(影响 jobs/fg/bg 可见性,见 3.5)。
3.7 正常退出 shell 后,作业是否存活(huponexit 语义)
「退出 shell 后台任务会不会被杀」取决于退出方式。bash 有个 huponexit 选项(默认 off):它只控制交互 shell 正常退出(exit/EOF)时是否向作业发 HUP;而 shell 被 SIGHUP 杀死时的重发行为与它无关(3.6 实测)。实测默认配置(huponexit off)下正常 exit:disown 与未 disown 的作业都存活,父进程变为 PID 1:
会话内序列:
sleep 300 &
KEPT=$!
sleep 300 &
DIS=$!
disown %2
echo "kept=$KEPT disowned=$DIS"
exit
实测回显(会话结束后在外层检查):
kept=26317 (未disown) disowned=26318
kept(未disown) 存活
disowned 存活
--- 两个进程归属(父进程已变化) ---
PID PPID STAT COMMAND
26317 1 S sleep 300
26318 1 S sleep 300
💡 要点:
- 默认配置下正常
exit不向作业广播 HUP(huponexit off),两个作业都真实存活(STAT=S),被 1 号进程收养(PPID=1,systemd 系统上就是被 systemd 收养并在其结束后收割)。 - 那为什么 SSH 断线后台任务常被杀?因为 SSH 断开时 sshd 关闭 pty,内核把 SIGHUP 发给会话首进程(交互 bash),bash 走的是 3.6 的「被 HUP 杀死 → 重发给作业」路径——此时 disown/-h/nohup 才是救命稻草。
shopt -s huponexit开启后,正常 exit 也会广播 HUP,未 disown 的作业同样会被清理——这让「正常退出」与「断线」行为一致,适合想「退出即清场」的习惯。
3.8 错误用法与退出码
不存在的作业号、非法选项是两个典型错误,实测退出码便于脚本判断:
disown %9; echo "disown %9 rc=$?"
disown -z 2>&1; echo "disown -z rc=$?"
实测回显(stderr 与 stdout 缓冲导致日志行序略有交错,按对应关系整理):
disown %9 rc=1
disown: %9: no such job
disown -z rc=2
disown: -z: invalid option
disown: usage: disown [-h] [-ar] [jobspec ... | pid ...]
💡 要点:作业号不存在返回 1(作业已 disown 或从未存在都会报 %N: no such job);非法选项返回 2 并打印 usage。脚本里用 $? 即可区分这两种失败。
3.9 边界认知:非交互 shell 的作业控制
disown %N 在脚本(非交互 shell)里可以直接用(作业表存在),但 fg/bg 这类需要终端进程组操作的命令不行,实测边界:
sleep 2 &
sleep 0.3
jobs -l
fg %1 2>&1; echo "fg rc=$?"
bg %1 2>&1; echo "bg rc=$?"
echo '--- set -m 之后 ---'
set -m
fg %1; echo "fg(set -m) rc=$?"
jobs -l
实测回显:
[1]+ 28596 Running sleep 2 &
fg: no job control
fg rc=1
bg: no job control
bg rc=1
--- set -m 之后 ---
fg: job 1 started without job control
fg(set -m) rc=1
[1]+ 28596 Running sleep 2 &
💡 要点:非交互 shell 默认无作业控制,fg/bg 报 no job control;即使事后 set -m,对「开启前启动的旧作业」执行 fg 仍报 fg: job 1 started without job control——作业控制属性在任务启动时确定,不能追溯。因此脚本里对后台任务的管理组合是:jobs 查看 + disown/wait/kill 操作,fg/bg 留给交互会话。
3.10 技术拓展:disown 与 nohup 对比
disown 与 nohup 都能让任务「逃过」SIGHUP,但作用层面完全不同。逐项对比(行为差异均来自 3.6/3.7 实验与 nohup 的信号语义):
| 维度 | disown(本文主角) | nohup |
|---|---|---|
| 作用层面 | shell 作业表:把作业移出 HUP 广播名单 | 进程信号处置:目标进程启动时把 SIGHUP 设为忽略(SIG_IGN) |
| 使用时机 | 事后补救:任务已在跑 | 启动时预防:nohup 命令 & |
| 输出处理 | 不管输出,需自行重定向 | 自动追加到 nohup.out(已有 stdout 指向文件时不改) |
| 对已运行任务 | 有效(核心优势) | 无效(信号处置要在启动时设置) |
| 对 SIGHUP | 作业收不到 HUP(名单层面屏蔽) | 进程忽略 HUP(收到也不响应) |
| 作业表可见性 | 普通形式移出表(jobs 不可见);-h 保留 | 仍在作业表(可 jobs/fg) |
| 其他信号 | 不保护(TERM/KILL 照杀) | 不保护(同样只针对 HUP) |
💡 说明:选型口诀——「一开始就知道要长跑,用 nohup + 输出重定向;任务已经跑起来了才发现要脱离会话,用 disown;两者都要,
nohup 命令 &后再disown %%双保险(nohup 防直接送达的 HUP,disown 防 shell 广播的 HUP)」。本文实际采用 disown 主线,因为它解决的是「已运行任务的移交」,这是 nohup 做不到的。
四、实战示例
4.1 场景一:交互会话里把长任务移交给系统(disown + 输出重定向)
经典流程:管理员在终端启动耗时任务 → 意识到要离开会话 → 重定向输出 → disown → 退出后任务继续跑。实测完整闭环(含退出后重新验证进程归属):
会话内序列:
sleep 300 &
KEPT=$!
sleep 300 &
DIS=$!
disown %2
echo "kept=$KEPT disowned=$DIS"
exit
退出会话后重新登录验证:
ps -o pid,ppid,stat,args -p "$kept" -p "$dis"
实测回显:
kept=26317 (未disown) disowned=26318
kept(未disown) 存活
disowned 存活
--- 两个进程归属(父进程已变化) ---
PID PPID STAT COMMAND
26317 1 S sleep 300
26318 1 S sleep 300
要点:disown 后任务的 stdin/stdout/stderr 仍指向原终端,会话关闭后这些文件描述符失效,任务往终端写会报错甚至卡死——所以移交前先重定向是纪律:命令 > /root/demo/disown/wdb-task.log 2>&1 & 再 disown %%。本实验中 sleep 不产生输出,因此未重定向也安然无恙;真实任务(如日志型程序)必须先重定向。
4.2 场景二:批量清空作业表再退出(disown -a 的正确姿势与替代)
交互会话里堆了一堆后台任务,退出前想一次性移交或清理。实测 disown -a 全清:
sleep 100 &
sleep 100 &
sleep 0.3
jobs -l
disown -a; echo "disown -a rc=$?"
sleep 0.3
jobs -l
echo '(作业表应为空)'
实测回显:
[2]- 28582 Running sleep 100 &
[3]+ 28583 Running sleep 100 &
disown -a rc=0
(作业表应为空)
要点:disown -a 让作业表清空且所有任务继续存活(3.7 实测正常退出后孤儿由 1 号进程收养)。如果意图是「退出前全部杀掉」而不是移交,正确的做法是对 $! 集合逐个 kill,而不是 disown——disown 只改账本,永远不碰进程。
五、注意事项与常见问题
Q1:disown 之后任务的输出去哪了?为什么任务有时会卡死?
disown 不管输出。任务的 stdout/stderr 仍指向原终端的 pty 设备;会话关闭后设备失效,往里写的任务会收到错误或阻塞在写操作上。纪律:disown 前先重定向(命令 > 日志 2>&1 &)。3.7 实验里的 sleep 不写终端所以无恙,换成交互型/日志型程序就会出问题。
Q2:退出 shell 后台任务一定会被杀吗?要不要每次都 disown?
不一定。实测分两条路径:① 正常 exit 退出——默认 huponexit 关闭,不向作业广播 HUP,未 disown 的作业也存活(3.7 的 STAT=S 证据);② shell 被 SIGHUP 杀死(SSH 断线、终端关闭、kill -HUP)——交互 bash 退出前把 HUP 重发给所有作业,未保护的作业被杀(3.6 实验一),disown/-h 的作业幸免。结论:交互终端里随手 disown 是好习惯;脚本(非交互)本来就无终端广播问题,无需 disown。
Q3:disown 与 nohup 到底用哪个?
看任务状态:还没启动→nohup 命令 &(顺带解决输出落盘);已经在跑→disown %N(nohup 对已运行进程无能为力,它靠启动时设置 SIG_IGN)。两者叠加是双保险:nohup 让进程忽略直接送达的 HUP,disown 让它躲开 shell 的广播名单(3.10 对比表)。
Q4:disown 之后还能用 jobs/fg 管理吗?
普通 disown(含 -r/-a)把作业移出了作业表,jobs 看不到、fg/bg 引用报 no such job,只能靠 ps/kill 管理。想保留 jobs 可见性又要抗 HUP,用 disown -h %N(3.5 实测 -h 后作业仍在表中)。
Q5:disown %1 报 no such job 是什么情况?
两种可能:① 该作业之前已被 disown 过(作业表里已无 %1,3.8 实测返回 1);② 作业号引用错了——作业号随作业表占用历史递增,可能不是你以为的连续编号,先 jobs 确认再引用。另外非交互脚本里 disown %N 可用,但 fg/bg 不可用(3.9 边界实测)。
Q6:disown -h 与普通 disown 效果一样吗?还要 -h 干嘛?
抗 SIGHUP 效果一样(3.6 实验二、三都存活)。差别在作业表成员资格:普通 disown 移出后 jobs/fg 失效;-h 保留在表中,仍可 fg/bg 调度。需要「继续前台管理 + 抗断线」就用 -h;确定移交就走普通 disown。
六、参考链接
- bash 手册(Ubuntu 22.04 jammy)JOB CONTROL 与 SHELL BUILTIN COMMANDS 的 disown 段:https://manpages.ubuntu.com/manpages/jammy/en/man1/bash.1.html
- bash 参考手册(GNU 官方)Job Control Builtins:https://www.gnu.org/software/bash/manual/html_node/Job-Control-Builtins.html
- nohup(1) 手册(对比参照,信号忽略与 nohup.out 语义):https://manpages.ubuntu.com/manpages/jammy/en/man1/nohup.1.html
- setsid(1) 手册(会话级脱离的对比手段):https://manpages.ubuntu.com/manpages/jammy/en/man1/setsid.1.html
- signal(7) 手册(SIGHUP 默认处置与终端挂断语义):https://manpages.ubuntu.com/manpages/jammy/en/man7/signal.7.html
- 本机速查:
help disown(内建帮助)、help -s disown(仅语法行)、man bash后用/^ *disown定位