Linux进程前后台管理:jobs、fg、bg、nohup、disown
你有没有过这种经历:在终端里跑一个耗时脚本,手一抖 Ctrl+C 把它掐了;或者 SSH 连上去启动服务,关掉窗口进程就没了。问题的根源在于——终端和进程之间有一条「会话」的脐带,一旦终端断开,内核会向它启动的进程发信号。这一篇把 &、Ctrl+Z、jobs、fg、bg、nohup、disown 讲清楚,掌握后就能自如地让进程在后台安稳运行。
一、前台与后台:终端只能伺候一个前台
Linux 里,一个终端(terminal)同一时刻只有一个前台进程组,键盘输入、Ctrl+C(SIGINT)、Ctrl+Z(SIGTSTP)都发给这个前台进程组。其他进程要么在后台跑,要么被暂停。
- 前台:占着终端,能接收键盘输入和
Ctrl+C - 后台:在跑,但不占终端,键盘信号发给不到它
- 暂停:被 SIGSTOP/SIGTSTP 冻结,不占 CPU,等
fg或bg唤醒
理解这条主线,后面所有命令都顺了。
二、&:启动即入后台
语法格式
命令 &
在整条命令末尾加一个 &,shell 会把它放进后台,立即把提示符还给你。
示例与预期输出
sleep 300 &
预期输出:
[1] 12345
[1]是作业号(job number),是 shell 给这个任务编的号,只在当前 shell 有效12345是进程号(PID),是内核编的,全局唯一
两者区别要记牢:作业号管前后台切换,PID 管杀进程。
注意:& 不等于「断开也不死」
command & 只是放后台,它仍然属于当前终端会话。关掉终端,它照样死。想断开也不死,得配合 nohup 或 disown,见后文。
三、Ctrl+Z:把前台进程按暂停
正在跑的进程想临时腾出终端,按 Ctrl+Z,它会被 SIGTSTP 暂停并扔到后台(状态 T):
$ vim bigfile.log
^Z
[1]+ Stopped vim bigfile.log
注意是停止不是后台运行——它冻结了,不消耗 CPU。想让它继续跑,用 bg;想回到前台,用 fg。
四、jobs:查看当前 shell 的后台任务
语法格式
jobs [选项]
常用参数
| 参数 | 说明 |
|---|---|
-l | 同时显示 PID |
-r | 只显示运行中的任务 |
-s | 只显示已停止的任务 |
示例与预期输出
jobs -l
预期输出:
[1]- 12345 Running sleep 300 &
[2]+ 12346 Stopped vim bigfile.log
符号解释:
+:当前任务,fg不带参数时默认操作它-:前一个任务Running/Stopped:运行中 / 已暂停
五、fg / bg:前后台切换
语法格式
fg [%作业号] # 把后台任务调回前台
bg [%作业号] # 让暂停的任务在后台继续跑
作业号要带 %,如 %1;不带参数则操作当前(+)任务。
示例与预期输出
把 [2] 的 vim 唤回前台:
fg %2
让刚才暂停的 vim 转到后台继续(这个例子意义不大,但换成 rsync 就很有用):
bg %1
预期输出:
[1]+ sleep 300 &
⚠️ 后台任务仍会往终端写输出
后台进程的 stdout/stderr 默认还是连到终端,它的输出会突然插进你正在敲的命令里,把屏幕弄乱。启动时就重定向掉:
long_task.sh > run.log 2>&1 &
这也是写后台任务脚本的标配姿势。
六、nohup:挂断也不死
nohup = no hang up。它让命令忽略 SIGHUP 信号,并把输出默认重定向到 nohup.out。
语法格式
nohup 命令 [参数] &
示例与预期输出
nohup python3 train.py > train.log 2>&1 &
预期输出:
[1] 12345
nohup: ignoring input and appending output to 'nohup.out'
(这里因为已经手动重定向到 train.log,所以不会再生成 nohup.out。)
关键点
nohup只解决 SIGHUP,不解决 stdin。它在启动时会把 stdin 重定向到 /dev/null,所以别再指望进程能读键盘。- 通常写作
nohup cmd > out.log 2>&1 &:忽略挂断 + 标准输出 + 错误输出 + 后台,一步到位。 - 想停止它,用
ps/pgrep找到 PID 再kill,因为作业号在关掉 shell 后就没了。
⚠️ nohup 不等于守护进程
nohup 进程仍然是普通进程,跟着系统走,但不会开机自启、不会自动重启、不受服务管理。生产环境的长期服务应该交给 systemd(见第 35 篇),nohup 只适合临时跑个长任务。
七、disown:事后补救
有时进程已经在跑了(可能还带 &),你才想起来「糟了,关终端就没了」。这时不用重开,disown 可以把作业从 shell 的作业表里摘掉,让它不再收到 SIGHUP。
语法格式
disown [选项] [%作业号]
常用参数
| 参数 | 说明 |
|---|---|
-h | 只标记为「不接收 SIGHUP」,任务仍留在 jobs 列表 |
-a | 移除所有作业 |
-r | 只移除运行中的作业 |
示例与预期输出
sleep 300 &
disown %1
jobs
jobs 会显示 [1] 已不在列表里,但 sleep 进程还在:
pgrep -a sleep
12345 sleep 300
nohup vs disown 怎么选
| 维度 | nohup | disown |
|---|---|---|
| 使用时机 | 启动时就加 | 跑起来后补 |
| 输出 | 默认写 nohup.out | 不变,仍指向原位置 |
| 灵活性 | 低,写死在命令前 | 高,可对已有任务操作 |
八、⚠️ 常见错误
- 以为
&就能防断开。&只是后台,SSH 一断照样收到 SIGHUP 退出。要断开也不死,必须nohup或disown,或用screen/tmux。 - 用
Ctrl+Z后忘了恢复。很多人Ctrl+Z停了 vim 又去干别的,回来发现 jobs 里堆了一串 Stopped,占用着资源。记得fg或kill %n清理。 nohup没重定向输出导致磁盘写满。默认写nohup.out且不断追加,长时间跑的任务如果没重定向,某天会把/撑爆。永远显式> xxx.log 2>&1。- 作业号跨 shell 失效。
jobs的作业号只在当前 shell 里认,新开一个终端fg %1会报no such job。跨会话管理进程要靠 PID 或tmux。 kill %1与kill 1搞混。%1是作业号,1是 PID 1(init),kill 1可能把系统搞挂(虽然一般会权限拒绝)。杀后台任务时写清%。
九、原理探究:SIGHUP 到底是什么
SIGHUP 字面是「挂断」(Hang Up),起源于拨号上网时代——调制解调器掉线,内核就向该终端上的所有进程发 SIGHUP。如今终端虽不用电话线,但这个信号的语义保留了下来:
当控制终端(controlling terminal)关闭时,内核会向前台进程组和该会话的所有进程发送 SIGHUP。
一条 SSH 连接对应一个会话(session),会话里有一个控制终端。你关掉 SSH 窗口,控制终端消失,内核给会话里的进程发 SIGHUP,默认动作是终止进程。
这就解释了三件事:
nohup为什么有效:它启动进程时就把 SIGHUP 忽略(signal(SIGHUP, SIG_IGN)),收到也不理。disown为什么有效:修订进程的作业表,让 shell 在退出时不向它转发 SIGHUP。- 为什么有的进程「关终端也没事」:它可能自己就忽略了 SIGHUP,或者已经脱离了这个会话(如
systemd启动的服务)。
顺带理解「进程组 / 会话」:一个会话包含多个进程组,一个进程组包含多个进程。Ctrl+C 发给前台进程组全体,所以 a | b | c 管道里三个进程会一起收到 SIGINT。想只杀管道里最后一个,得用 kill 精确到 PID。
十、更靠谱的方案:tmux 与 systemd
nohup/disown 是轻量解决方案,但有两个硬伤:进程输出只能靠日志捞、进程管理混乱(一多就靠 ps 找 PID)。
实际工作中更推荐:
# 方案一:tmux,会话可随时 attach/detach
tmux new -s deploy
# 在里面跑任务,然后 Ctrl+B 再按 D 脱离;重连:
tmux attach -t deploy
# 方案二:写 systemd 服务,长期服务交给系统管
sudo systemctl start myapp
sudo systemctl enable myapp # 开机自启
journalctl -u myapp -f # 看日志
- 临时长任务(跑个数据、下个大文件):
tmux最优,随时回来接着看。 - 长期服务(web、mq、agent):写
systemdunit,开机自启、崩溃重启、日志集中,见第 35、36 篇。
nohup/disown 理解原理是必须的,但日常尽量用 tmux 和 systemd 替代。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/m0_66083353/article/details/167653903



