fg 命令的超级技巧:当 Ctrl+Z 拯救你的终端会话
发布于 18 April 2026
您发起`./gradlew build`, 终端被阻塞 3 分钟,您需要在 在等待 做其他事情吗?不需要打开新标签页。`Ctrl+Z`暂停进程,`fg`它会把你确切地带回你离开的地方。这是开发者日常中最被低估的Unix技巧。
问题:一个繁忙的终端
每个开发者都遇到过这种情况:
$ ./gradlew build
> Building...
终端已被锁定。您无法再输入任何命令。您需要查看一个文件,启动一个`git status`, 或者检查一个被占用的端口。
@startuml skinparam backgroundColor #FEFEFE actor Développeur as dev participant "终端" as term participant "Gradle\n( 进程 )" as gradle dev -> term : ./gradlew 构建 term -> gradle : 启动构建 gradle -> term : 占用终端\n(无提示) dev -> term : 想输入一个命令 term --> dev : ❌ 终端被阻塞 note right of dev 开发者在整个构建期间被瘫痪 end note @enduml
大多数开发者会打开一个新终端。但有一种更优雅、更快速且原生的解决方案:作业管理。
必不可少的三个命令
Ctrl+Z — 暂停 (SIGTSTP)
`Ctrl+Z`发送信号`SIGTSTP`到前台进程。 该进程是已暂停— 未被杀死 — 终端恢复了提示符。
$ ./gradlew build
> Building...
^Z
[1]+ Stoppé ./gradlew build
$
括号中的数字`[1]`是工作编号. Le `+`表示默认作业。
fg — 切换到前台
`fg`将暂停的作业调到前台。进程正好在它停止的地方继续执行。
$ fg
./gradlew build
> Building... (reprend où il en était)
使用特定的作业编号:
$ fg %2
bg — 在后台恢复
`bg`恢复进程 sans 不阻塞终端。 适用于长时间的构建。
$ bg
[1]+ ./gradlew build &
$
Le `&`在最后表示进程在后台运行。
jobs — 列出 shell 作业
$ jobs
[1]- Stoppé vim README.md
[2]+ En cours ./gradlew build
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif state "前台\n(终端被阻塞)" as fg #LightCoral state "已挂起\n(Ctrl+Z)" as stopped #LightYellow state "后台\n(bg)" as bg_ #LightGreen [*] --> fg : Lance une commande fg --> stopped : Ctrl+Z\n(SIGTSTP) stopped --> fg : fg\n重新置于前景 stopped --> bg_ : bg\n在后台恢复 bg_ --> fg : fg\n将其拉到前景 fg --> [*] : 命令已完成 note right of stopped 进程已冻结 但 _未_ 被杀 end note note left of bg_ 终端空闲 构建继续 end note @enduml
情景 1:冗长的构建
这是最常见的使用情况。您正在运行 Gradle 构建,终端被阻塞,您需要去做其他事情。
@startuml skinparam backgroundColor #FEFEFE autonumber actor Développeur as dev participant "终端" as term participant "Gradle" as gradle database "状态\n进程" as state dev -> term : ./gradlew 构建 term -> gradle : 启动构建 gradle -> state : 进行中 (FG) dev -> term : Ctrl+Z term -> state : 挂起(作业[1]) term --> dev : 自由提示 $ dev -> term : git 状态 term --> dev : 工作区干净 dev -> term : fg term -> state : 重新(FG) gradle --> dev : 构建成功 ✓ note right of state 进程 Gradle 从未被终止,它精确地恢复到之前停止的位置。 end note @enduml
一步一步
$ ./gradlew build
> Building...
^Z
[1]+ Stoppé ./gradlew build
$ git status
On branch main
nothing to commit, working tree clean
$ fg
./gradlew build
> Building...
BUILD SUCCESSFUL in 2m 34s
场景 2 : 被遗忘的编辑
您在`vim`, 您需要查看另一个文件而不退出`vim`.
# Dans vim, en mode commande :
:shell
$ cat /path/to/other/file.txt
$ exit
# Retour dans vim
但是很重。与`Ctrl+Z`, 这是即时的 :
# Dans vim, n'importe quel moment :
Ctrl+Z
[1]+ Stoppé vim README.md
$ cat /path/to/other/file.txt
# ... lire le fichier ...
$ fg
vim README.md
# Retour exact là où on en était, curseur en place
@startuml skinparam backgroundColor #FEFEFE autonumber actor Développeur as dev participant "vim" as vim participant "Shell" as shell dev -> vim : 编辑进行中 dev -> vim : Ctrl+Z vim -> shell : 挂起 (job [1]) dev -> shell : chat autre_fichier.txt shell --> dev : 文件内容 dev -> shell : fg shell -> vim : 重新开始(作业 [1]) vim --> dev : 光标恰好\n位于相同位置 note right of vim vim 恢复 int 完整恢复状态: 光标位置, 缓冲区,撤销栈 end note @enduml
vim`在……之后完美地恢复其状态`fg: 光标位置, 缓冲区内容, 撤销历史 — 一切都被保留。
|
场景 3:opencode 进程已挂起
这是激发本文的情景。您正在使用`opencode`(CLI工具,用于在您的代码上驱动LLM),且过程被挂起。
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif skinparam componentStyle rectangle actor Développeur as dev participant "opencode\n(LLM agent)" as agent participant "Shell" as shell participant "git" as git dev -> agent : Session active agent -> agent : Tour de parole\ndu LLM en cours note over agent Ctrl+Z 在代码分析的正中间出现 end note dev -> agent : 撤销 agent -> shell : Suspendu (job [1]) dev -> git : git diff git --> dev : 可见的变化 dev -> shell : fg shell -> agent : 重新开始 (job [1]) agent --> dev : Reprend exactement\nl'analyse en cours note right of agent 没有上下文丢失 LLM 继续它的回答 正好在它停下的地方 end note @enduml
$ opencode
# ... session en cours, LLM analyse le code ...
^Z
[1]+ Stoppé opencode
$ git diff HEAD~1
# vérifier les changements récents
$ fg
opencode
# Le LLM reprend là où il s'était arrêté
优势是双重的:
-
没有上下文丢失: 会话 LLM, 对话历史, 打开的文件 — 全部都被保留
-
无需重启: 不需要重新启动 opencode,重新加载上下文,或向 LLM 重新解释问题
场景4:同时处理多个作业
fg et `bg`接受作业编号以在多个作业被挂起时定位特定进程。
$ vim config.yml
^Z
[1]+ Stoppé vim config.yml
$ ./gradlew test
^Z
[2]+ Stoppé ./gradlew test
$ htop
^Z
[3]+ Stoppé htop
$ jobs
[1] Stoppé vim config.yml
[2]- Stoppé ./gradlew test
[3]+ Stoppé htop
$ fg %2
./gradlew test
# Le build reprend en avant-plan
$ bg %3
[3] htop &
# htop reprend en arrière-plan
@startuml
skinparam backgroundColor #FEFEFE
rectangle "已暂停的作业" as jobs #LightYellow {
card "[1] vim config.yml" as j1
card "[2] ./gradlew 测试" as j2
card "[3] htop" as j3
}
rectangle "操作" as actions #LightGreen {
card "fg %2\n→ 前景" as a1
card "bg %3\n→ 后台" as a2
card "fg %1\n→ 前景" as a3
}
jobs --> actions : fg/bg + 作业编号
actions --> j2 : 继续
actions --> j3 : 在背景
note bottom of jobs
每个作业保持
其精确状态
在 suspend/resume 之间
end note
@enduml
作业命令概述
| 订单 | 效果 | 例子 |
|---|---|---|
|
暂停前台进程 |
发送的`SIGTSTP` |
|
恢复默认任务在前台 |
|
|
在前台恢复作业 n |
|
|
在后台恢复默认任务 |
|
|
在后台恢复作业 n |
|
|
列出当前 shell 的作业 |
|
|
完成工作 n |
|
fg、bg、Ctrl+Z、Ctrl+C 与 & 的区别
许多开发者混淆了这些机制。以下是一个关键的澄清。
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
card "暂停进程\n(它仍在内存中)\n可使用 fg/bg" as c1
}
rectangle "**Ctrl+C**\nSIGINT" as ctrlc #LightCoral {
card "结束进程\n(不可恢复)\n丢失所有状态" as c2
}
rectangle "**& **\n(后台)" as ampersand #LightGreen {
card "直接启动\n在后台\n终端立即空闲" as c3
}
rectangle "**fg**\n(前台)" as fg_ #SkyBlue {
card "将作业恢复\n挂起的前台\n终端被占用" as c4
}
rectangle "**bg**\n(后台)" as bg_ #Lavender {
card "恢复作业\n挂起的后台\n终端空闲" as c5
}
ctrlz -right-> fg_ : fg 重新开始
ctrlz -right-> bg_ : bg 用于继续\n在后台
ctrlc -right-> c2 : 没有返回\n进程已被终止
note bottom of ctrlz
进程 **已冻结** 但 **未被杀死**
end note
note bottom of ampersand
替代方案:启动
./gradlew build &
直接在后台
end note
@enduml
| 快捷键/命令 | 信号 | 效果 | 返回? |
|---|---|---|---|
|
|
暂停进程(冻结,在内存中) |
是的: |
|
|
终止进程(已完成) |
否:进程已被销毁 |
|
|
终止进程 + core dump |
不是:进程已被销毁 |
|
— |
直接在后台启动 |
`fg`为了把它带到前台 |
Ctrl+Z`是无损. 该过程被原样冻结,存储在内存中。您可以立即继续与`fg. 暂停,不是 停止。
|
工作的完整生命周期
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif title 作业 shell 的完整生命周期 state "不存在" as none state "前景\n(occupe le terminal)" as foreground #LightCoral state "已挂起\n(Ctrl+Z / SIGTSTP)" as suspended #LightYellow state "背景\n(bg ou &)" as background #LightGreen state "已完成\n(exit code 0)" as done #LightGray [*] --> none none --> foreground : 输入一条命令 foreground --> suspended : Ctrl+Z foreground --> done : 正常结束 suspended --> foreground : fg suspended --> background : bg suspended --> done : 杀死 %n background --> foreground : fg background --> done : 正常结束\n(en arrière-plan) background --> suspended : Ctrl+Z\n(si fg puis Ctrl+Z) done --> [*] note right of suspended 暂停状态是作业系统的核心枢纽 end note @enduml
高级技巧
fg 与最新的工作和交替一起
fg`无参数返回被标记的作业+(最新的). 但您也可以使用%-`用于定位上一个作业:
$ vim file1.txt
^Z
[1]+ Stoppé vim file1.txt
$ vim file2.txt
^Z
[2]+ Stoppé vim file2.txt
$ fg # ramène job [2] (le plus récent)
$ fg %- # ramène job [1] (le précédent)
妥善地结束作业
$ jobs
[1]- Stoppé vim config.yml
[2]+ Stoppé ./gradlew test
$ kill %2 # envoie SIGTERM au job 2
$ kill -9 %1 # envoie SIGKILL au job 1 (force)
désown : détacher du shell
disown`从 shell 的作业表中移除一个作业。进程继续运行,但不能再被带回`fg:
$ ./gradlew build &
[1] 12345
$ disown %1
# Le processus continue mais n'est plus lié au shell
# Ça survives à la fermeture du terminal
将 nohup 与长时间运行的进程结合使用
为了让进程在终端关闭后仍然存活:
$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Le build continue même si vous fermez le terminal
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "fg" as fg_ #SkyBlue {
card "将最近的作业\n调至前台 (+)" as f1
card "指定特定作业\n:fg %n" as f2
card "交替: fg %-" as f3
}
rectangle "杀死 %n" as kill_ #LightCoral {
card "SIGTERM (干净)" as k1
card "SIGKILL -9 (强制)" as k2
}
rectangle "disown" as disown #Lavender {
card "从 shell 分离\n(survit à la déconnexion)" as d1
}
rectangle "nohup + &" as nohup_ #LightGreen {
card "免疫的进程\n对 SIGHUP\n(survit à la fermeture\ndu terminal) 有免疫" as n1
}
fg_ -right-> kill_ : 如果必须\n完成
kill_ -right-> disown_ : 或者分离
nohup_ -left-> fg_ : fg 置于前景
note bottom of disown_
disown` 之后 fg 再也无法将该作业调回前台。
end note
@enduml
陷阱和常见错误
混淆 Ctrl+Z 和 `Ctrl+C
这是最常见的错误。`Ctrl+C`杀死该进程。`Ctrl+Z`暂停它。 如果你按下`Ctrl+C`作为反射,所有状态都丢失了 — 打开的文件、LLM 会话、进行中的构建。
@startuml
skinparam backgroundColor #FEFEFE
rectangle "Ctrl+Z(SIGTSTP)" as z #LightGreen {
card "内存中冻结的进程" as z1
card "fg 将其完好无损地带回来" as z2
card "无损失" as z3
}
rectangle "Ctrl+C(SIGINT)" as c #LightCoral {
card "进程已完成" as c1
card "永久丢失的状态" as c2
card "无法回头" as c3
}
note bottom of z
Pause → Reprendre
end note
note bottom of c
Kill → Perdu
end note
@enduml
作业是 shell 本地的
jobs, fg et `bg`仅在启动这些进程的shell中起作用。 如果您打开一个新的终端,您将看不到另一个作业。
# Terminal 1
$ vim file.txt
^Z
[1]+ Stoppé vim file.txt
# Terminal 2 (nouveau)
$ jobs
# (rien — les jobs sont locaux au shell)
要查看系统范围的进程,请使用`ps` ou htop:
ps aux | grep vim
作业不会在 shell 关闭后存活
如果您关闭终端,所有挂起的或在后台运行的作业都将被销毁。为了让进程继续运行,请使用`nohup` + disown:
$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Fermer le terminal → le build continue
交互式进程和 fg
一些交互式程序在一个…之后表现不佳`Ctrl+Z`+fg. 虽然罕见,但会发生:
-
某些使用信号的程序 (vim, htop, less 处理得很好`Ctrl+Z`)
-
长时间运行的网络程序(ssh, opencode)通常能够正确恢复
-
修改终端的程序(ncurses)有时可能会使终端处于意外状态。
如果终端在一个…之后损坏`fg`, 请输入 :
reset
概览图
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam ArrowColor #444444
title "fg — 这个技巧改变一切"
rectangle "**问题**" as problem #LightCoral {
card "Terminal 卡住\n由一个进程" as p1
card "打开 一个新的\n终端 = 笨重的解决方案" as p2
}
rectangle "**解决方案**" as solution #LightGreen {
card "Ctrl+Z → 挂起" as s1
card "fg → 恢复" as s2
card "bg → 后台" as s3
card "jobs → 查看已挂起的作业" as s4
}
rectangle "**优势**" as advantages #SkyBlue {
card "无上下文丢失" as a1
card "无需重启" as a2
card "只需要一个终端" as a3
}
problem -right-> solution : "此解决方案 native\nUnix 已有40年"
solution -right-> advantages : "结果"
note bottom of advantages
fg 不是一个晦涩的技巧
这是一个基本机制
Unix shell 的
end note
@enduml
为什么fg被低估了
大多数现代开发者发现`fg`迟到,甚至可能永不。一些原因:
-
IDE 隐藏了终端: VS Code 提供了内置终端,但多个选项卡会导致`fg`表面上看起来不那么必要
-
图形工具替代 CLI: 文件管理器,系统监视器,图形化 Git 客户端
-
tmux 和 screen: 这些多路复用器以不同的方式解决相同的问题,但`fg`更简单且始终可用
然而,`fg`是 :
-
普遍: 存在于所有 POSIX shell 中(bash, zsh, fish…)
-
无依赖: 不需要安装 tmux 或 screen
-
快照: 两次打击 (
Ctrl+Z`然后`fg) 暂停并恢复 -
上下文保存者: 进程状态完好
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "为什么 fg 被低估了?" as why #LightCoral {
card "集成开发环境,带多标签终端" as w1
card "替代 CLI 的图形工具" as w2
card "tmux/screen\n以另一种方式解决问题" as w3
}
rectangle "为什么 fg 更优?" as superior #LightGreen {
card "通用:bash,\nzsh, fish, sh..." as s1
card "无依赖:\n无需安装" as s2
card "两次按键:\nCtrl+Z 然后 fg" as s3
card "上下文已保留:\n状态零丢失" as s4
}
why -right-> superior : "简约\n永远胜出"
@enduml
技术备忘录:信号
对于好奇的人,以下是涉及的信号:
| 信号 | 名称 | 由…发送 | 效果 |
|---|---|---|---|
2 |
|
|
中断 — 终止进程 |
3 |
|
|
带有核心转储退出 |
18 |
|
|
交互式停止 — 暂停进程 |
19 |
|
|
继续 — 恢复一个暂停的进程 |
15 |
|
|
干净终止 |
9 |
|
|
强制终止(无法拦截) |
完整的流程:
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif participant "用户" as user participant "Shell" as shell participant "进程" as proc user -> shell : Ctrl+Z shell -> proc : SIGTSTP (信号 18) note right of proc : 卡住的进程\n内存中 user -> shell : fg shell -> proc : SIGCONT (信号 19) note right of proc : 进程恢复\n 在前台 user -> shell : bg shell -> proc : SIGCONT (信号 19) note right of proc : 进程在后台继续 user -> shell : Ctrl+C shell -> proc : SIGINT (信号 2) note right of proc : 进程已完成\n状态丢失 @enduml
结论
`fg`这并不是一个晦涩的技巧 — — 而是 Unix shell 的一个基本机制,自 70 年代起就已存在。多数开发者对此视而不见,反映了我们这个时代的特点:IDE 和复用器让我们远离了基础。
但当你在 SSH 服务器上、在最小化终端中,或者只是在使用时`opencode`而且 LLM 正在全力工作 —Ctrl+Z, fg, bg, `jobs`是你们最好的朋友。
_ Ctrl+Z 挂起,fg 恢复。这是终端的暂停/播放。 _
参考文献
-
man bash— 部分 JOB CONTROL -
man signal— POSIX信号列表
相关文章
31 May 2026
14 May 2026
12 May 2026