Mẹo siêu hay của lệnh fg: khi Ctrl+Z lưu lại phiên terminal của bạn
Đăng ngày 18 April 2026
- Vấn đề: một terminal đang bận
- Ba lệnh không thể thiếu
- Kịch bản 1: Quá trình build lâu
- Kịch bản 2: Trình soạn thảo bị lãng quên
- Kịch bản 3: Tiến trình opencode bị tạm dừng
- Kịch bản 4: Quản lý đồng thời nhiều công việc
- Sự khác biệt giữa fg, bg, Ctrl+Z, Ctrl+C và &
- Vòng đời đầy đủ của một job
- Mẹo nâng cao
- Cạm bẫy và các lỗi thường gặp
- Sơ đồ tóm tắt
- Tại sao fg bị đánh giá thấp
- Bản ghi nhớ kỹ thuật: các tín hiệu
- Kết luận
- Tài liệu tham khảo
Bạn khởi chạy`./gradlew build`, terminal bị treo trong 3 phút, và bạn cần làm việc khác trong lúc chờ? Không cần mở tab mới.`Ctrl+Z`tạm dừng quy trình,`fg`đưa nó trở lại đúng nơi bạn đã để nó lại. Đây là mẹo Unix bị đánh giá thấp nhất trong công việc thường ngày của một lập trình viên.
Vấn đề: một terminal đang bận
Mọi lập trình viên đều đã từng gặp tình huống này:
$ ./gradlew build
> Building...
Terminal bị treo. Bạn không thể nhập bất kỳ lệnh nào nữa. Và bạn cần xem một tệp, khởi chạy một`git status`, hoặc kiểm tra một cổng đang bận.
@startuml skinparam backgroundColor #FEFEFE actor Développeur as dev participant "Terminal" as term participant "Gradle\n( tiến trình )" as gradle dev -> term : ./gradlew xây dựng term -> gradle : Chạy build gradle -> term : Chiếm giữ terminal\n(không có dấu nhắc) dev -> term : muốn nhập một lệnh term --> dev : ❌ Terminal bị khóa note right of dev Lập trình viên bị ngưng hoạt động trong toàn bộ thời gian của quá trình build end note @enduml
Hầu hết các nhà phát triển đều mở một terminal mới. Nhưng có một giải pháp thanh lịch hơn, nhanh hơn và native: quản lý job.
Ba lệnh không thể thiếu
Ctrl+Z — Tạm dừng (SIGTSTP)
`Ctrl+Z`gửi tín hiệu`SIGTSTP`đến tiến trình ở tiền cảnh. Tiến trình làbị treo— không bị giết — và terminal lấy lại dấu nhắc của nó.
$ ./gradlew build
> Building...
^Z
[1]+ Stoppé ./gradlew build
$
Số trong ngoặc vuông`[1]`làsố công việc. Le `+`cho biết job mặc định.
fg — Tiếp tục ở tiền cảnh
`fg`đưa công việc bị tạm dừng trở lại tiền cảnh. Tiến trình tiếp tục chính xác từ nơi nó đã dừng lại.
$ fg
./gradlew build
> Building... (reprend où il en était)
Với một số hiệu công việc cụ thể:
$ fg %2
bg — Tiếp tục trong nền
`bg`tiếp tục quá trình mà không chặn terminal. Lý tưởng cho các bản build dài.
$ bg
[1]+ ./gradlew build &
$
Le `&`ở cuối có nghĩa là tiến trình chạy ở chế độ nền.
jobs — Liệt kê các job của shell
$ jobs
[1]- Stoppé vim README.md
[2]+ En cours ./gradlew build
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif state "Tiền cảnh\n(terminal bị khóa)" as fg #LightCoral state "Đã tạm dừng\n(Ctrl+Z)" as stopped #LightYellow state "Hậu cảnh\n(bg)" as bg_ #LightGreen [*] --> fg : Lance une commande fg --> stopped : Ctrl+Z\n(SIGTSTP) stopped --> fg : fg\ntiếp tục ở nền trước stopped --> bg_ : bg\ntiếp tục ở chế độ nền bg_ --> fg : fg\nđưa lên phía trước fg --> [*] : Đã hoàn thành lệnh note right of stopped Quá trình bị dừng nhưng _không_ chết end note note left of bg_ Terminal còn trống build vẫn tiếp tục end note @enduml
Kịch bản 1: Quá trình build lâu
Đây là trường hợp sử dụng phổ biến nhất. Bạn khởi chạy một bản dựng Gradle, terminal bị chặn, và bạn cần làm việc khác.
@startuml skinparam backgroundColor #FEFEFE autonumber actor Développeur as dev participant "Terminal" as term participant "Gradle" as gradle database "Trạng thái\ntiến trình" as state dev -> term : ./gradlew bản dựng term -> gradle : Chạy build gradle -> state : Đang thực hiện (FG) dev -> term : Ctrl+Z term -> state : Tạm dừng (công việc [1]) term --> dev : Lời nhắc tự do $ dev -> term : git status term --> dev : working tree sạch dev -> term : fg term -> state : Tiếp tục (FG) gradle --> dev : Build thành công ✓ note right of state Tiến trình Gradle chưa bao giờ bị buộc dừng Nó tiếp tục chính xác từ nơi nó đã dừng lại end note @enduml
Từng bước một
$ ./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
Kịch bản 2: Trình soạn thảo bị lãng quên
Bạn đang ở trong`vim`, bạn cần xem một tệp khác mà không rời khỏi`vim`.
# Dans vim, en mode commande :
:shell
$ cat /path/to/other/file.txt
$ exit
# Retour dans vim
Nhưng nó nặng nề. Với`Ctrl+Z`, thật tức thì:
# 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 : Đang được chỉnh sửa dev -> vim : Ctrl+Z vim -> shell : Tạm dừng (job [1]) dev -> shell : mèo autre_fichier.txt shell --> dev : nội dung của tập tin dev -> shell : fg shell -> vim : Tiếp tục (công việc [1]) vim --> dev : Con trỏ chính xác\nở cùng vị trí note right of vim vim khôi phục intégralement l'estat : vị trí con trỏ, bộ đệm, ngăn xếp undo end note @enduml
No source text was provided to translate. Please send the French text you’d like translated into Vietnamese.vim`khôi phục hoàn hảo trạng thái của nó sau một`fg: vị trí con trỏ, nội dung bộ đệm, lịch sử hoàn tác — mọi thứ đều được giữ nguyên.
Kịch bản 3: Tiến trình opencode bị tạm dừng
Đây là kịch bản đã thúc đẩy bài viết này. Bạn đang sử dụng`opencode`(công cụ CLI để điều khiển một LLM trên mã của bạn), và tiến trình bị tạm dừng.
@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 : Phiên hoạt động agent -> agent : Tour de parole\ndu LLM en cours note over agent Ctrl+Z xuất hiện ngay trong quá trình phân tích mã end note dev -> agent : Ctrl+Z agent -> shell : Suspendu (job [1]) dev -> git : git diff git --> dev : thay đổi có thể nhìn thấy dev -> shell : fg shell -> agent : Tiếp tục (job [1]) agent --> dev : Reprend exactement\nl'analyse en cours note right of agent Không mất bối cảnh LLM tiếp tục trả lời đúng chỗ nơi nó đã dừng lại 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é
Lợi ích là gấp đôi:
-
Không mất ngữ cảnh: phiên LLM, lịch sử trò chuyện, các tệp đang mở — mọi thứ đều được giữ nguyên
-
Không khởi động lại: không cần khởi động lại opencode, tải lại ngữ cảnh, giải thích lại vấn đề cho LLM
Kịch bản 4: Quản lý đồng thời nhiều công việc
fg et `bg`chấp nhận một số job để nhắm mục tiêu một tiến trình cụ thể khi nhiều tiến trình bị tạm dừng.
$ 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 "Công việc bị tạm dừng" as jobs #LightYellow {
card "[1] vim config.yml" as j1
card "[2] ./gradlew test" as j2
card "[3] htop" as j3
}
rectangle "Hành động" as actions #LightGreen {
card "fg %2\n→ tiền cảnh" as a1
card "bg %3\n→ hậu cảnh" as a2
card "fg %1\n→ tiền cảnh" as a3
}
jobs --> actions : fg/bg + số công việc
actions --> j2 : Tiếp tục
actions --> j3 : Ở phía sau
note bottom of jobs
Mỗi job giữ nguyên
trạng thái chính xác của nó
giữa các suspend/resume
end note
@enduml
Tóm tắt các lệnh job
| Đơn hàng | Hiệu ứng | Ví dụ |
|---|---|---|
|
Tạm dừng tiến trình ở tiền cảnh |
Gửi`SIGTSTP` |
|
Tiếp tục tác vụ mặc định ở tiền cảnh |
|
|
Tiếp tục job n ở tiền cảnh |
|
|
Tiếp tục công việc mặc định trong nền |
|
|
Tiếp tục job n trong nền |
|
|
Liệt kê các job của shell hiện tại |
|
|
Hoàn thành job n |
|
Sự khác biệt giữa fg, bg, Ctrl+Z, Ctrl+C và &
Nhiều nhà phát triển nhầm lẫn các cơ chế này. Đây là một sự làm rõ mang tính quyết định.
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
card "Dừng tạm thời tiến trình\n(nó vẫn còn trong bộ nhớ)\nCó thể dùng với fg/bg" as c1
}
rectangle "**Ctrl+C**\nSIGINT" as ctrlc #LightCoral {
card "Dừng tiến trình\n(không thể quay lại)\nMất toàn bộ trạng thái" as c2
}
rectangle "** & \n(phông nền)" as ampersand #LightGreen {
card "Khởi động trực tiếp\nở nền\nterminal miễn phí ngay lập tức" as c3
}
rectangle "**fg**\n(tiền cảnh)" as fg_ #SkyBlue {
card "Lấy một job\nngưng trong tiên-trước\nTerminal bận" as c4
}
rectangle "**bg**\n(background)**" as bg_ #Lavender {
card "Tiếp tục một công việc\nđã dừng tạm ở nền\n terminal troncón? Wait check spelling: terminal trống (not trón?). I wrote terminal trống correct. Ensure spelling: terminal trống. Final: "Tiếp tục một công việc\nđã dừng tạm ở nền\n terminal trống". Let's output.\n\n</think>\n\nTiếp tục một công việc\nđã dừng tạm ở nền\n terminal trống" as c5
}
ctrlz -right-> fg_ : fg để tiếp tục
ctrlz -right-> bg_ : bg để tiếp tục\ntrong nền
ctrlc -right-> c2 : Không có trả về\ntiến trình bị giết
note bottom of ctrlz
Quy trình là **đóng băng** nhưng **chưa bị giết**
end note
note bottom of ampersand
Thay thế : chạy
./gradlew xây dựng &
trực tiếp trong nền
end note
@enduml
| Phím tắt/Lệnh | Tín hiệu | Hiệu ứng | Quay lại? |
|---|---|---|---|
|
|
Tạm dừng tiến trình (bị đóng băng, trong bộ nhớ) |
Vâng:`fg` ou |
|
|
Giết tiến trình (đã hoàn tất) |
Không: tiến trình bị hủy |
|
|
Giết tiến trình + core dump |
Không: tiến trình bị hủy |
|
— |
Khởi chạy trực tiếp trong nền |
`fg`để đưa nó trở lại tiền cảnh |
Ctrl+Z`làkhông phá hủy. Quá trình bị đóng băng nguyên trạng trong bộ nhớ. Bạn có thể tiếp tục ngay với`fg. Đây là một tạm dừng, không phải một dừng hẳn.
|
Vòng đời đầy đủ của một job
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif title Vòng đời đầy đủ của một job shell state "Không tồn tại" as none state "Tiền cảnh\n(occupe le terminal)" as foreground #LightCoral state "Tạm dừng\n(Ctrl+Z / SIGTSTP)" as suspended #LightYellow state "Nền\n(bg ou &)" as background #LightGreen state "Đã kết thúc\n(exit code 0)" as done #LightGray [*] --> none none --> foreground : gõ một lệnh foreground --> suspended : Ctrl+Z foreground --> done : kết thúc bình thường suspended --> foreground : fg suspended --> background : bg suspended --> done : kill %n background --> foreground : fg background --> done : kết thúc bình thường\n(en arrière-plan) background --> suspended : Ctrl+Z\n(si fg puis Ctrl+Z) done --> [*] note right of suspended Trạng thái tạm dừng là trục xoay của toàn bộ hệ thống công việc end note @enduml
Mẹo nâng cao
fg với công việc gần đây nhất và chương trình vừa học vừa làm
fg`không có đối số thì trả về job đã được đánh dấu+(mới nhất). Nhưng bạn cũng có thể sử dụng%-`để nhắm đến job trước đó:
$ 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)
giết một job một cách sạch sẽ
$ 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: tách khỏi shell
disown`loại bỏ một job khỏi bảng job của shell. Tiến trình vẫn tiếp tục chạy nhưng không thể được đưa trở lại bằng`fg:
$ ./gradlew build &
[1] 12345
$ disown %1
# Le processus continue mais n'est plus lié au shell
# Ça survives à la fermeture du terminal
Kết hợp với nohup cho các tiến trình chạy lâu
Để một tiến trình tồn tại sau khi đóng terminal:
$ 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 "Đưa job\ngần nhất trở lại foreground (+)" as f1
card "Chỉ định job\ncụ thể: fg %n" as f2
card "Luân phiên: fg %-" as f3
}
rectangle "kill %n" as kill_ #LightCoral {
card "SIGTERM (sạch)" as k1
card "SIGKILL -9 (cưỡng bức)" as k2
}
rectangle "disown" as disown #Lavender {
card "Tách khỏi shell\n(survit à la déconnexion)" as d1
}
rectangle "nohup + &" as nohup_ #LightGreen {
card "Tiến trình miễn nhiễm\nvới SIGHUP\n(survit à la fermeture\ndu terminal)" as n1
}
fg_ -right-> kill_ : nếu phải\nkết thúc
kill_ -right-> disown_ : hoặc tháo ra
nohup_ -left-> fg_ : fg đưa lên\nnền trước
note bottom of disown_
Sau khi disown, fg không thể đưa job trở lại nữa
end note
@enduml
Cạm bẫy và các lỗi thường gặp
Nhầm lẫn Ctrl+Z với Ctrl+C
Đây là lỗi phổ biến nhất.`Ctrl+C`giết tiến trình.`Ctrl+Z`tạm dừng nó. Nếu bạn nhấn vào`Ctrl+C`Do phản xạ, toàn bộ trạng thái bị mất — các tệp đang mở, phiên LLM, các bản dựng đang diễn ra.
@startuml
skinparam backgroundColor #FEFEFE
rectangle "Ctrl+Z (SIGTSTP)" as z #LightGreen {
card "Tiến trình bị đóng băng trong bộ nhớ" as z1
card "fg đưa nó trở lại nguyên vẹn" as z2
card "Không mất mát gì" as z3
}
rectangle "Ctrl+C (SIGINT)" as c #LightCoral {
card "Tiến trình đã kết thúc" as c1
card "Trạng thái bị mất vĩnh viễn" as c2
card "Không thể quay lại" as c3
}
note bottom of z
Tạm dừng → Tiếp tục
end note
note bottom of c
Kill → Mất
end note
@enduml
Các job là cục bộ trong shell
jobs, fg et `bg`chỉ hoạt động trong shell đã khởi chạy các tiến trình. Nếu bạn mở một terminal mới, bạn sẽ không thấy các job của terminal kia.
# Terminal 1
$ vim file.txt
^Z
[1]+ Stoppé vim file.txt
# Terminal 2 (nouveau)
$ jobs
# (rien — les jobs sont locaux au shell)
Để xem các tiến trình toàn hệ thống, hãy sử dụng`ps` ou htop :
ps aux | grep vim
Các job không tồn tại sau khi đóng shell
Nếu bạn đóng terminal, tất cả các job bị tạm dừng hoặc chạy nền đều sẽ bị hủy. Để một tiến trình tồn tại, hãy dùng`nohup`+disown :
$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Fermer le terminal → le build continue
Các tiến trình tương tác và fg
Một số chương trình tương tác không hoạt động tốt sau một`Ctrl+Z`It looks like the text to translate is missing — you’ve only sent the instructions and a "+" symbol. Please provide the French text you’d like me to translate into Vietnamese.fg. Điều đó hiếm nhưng vẫn xảy ra:
-
Một số chương trình sử dụng tín hiệu (vim, htop, less) xử lý tốt`Ctrl+Z`)
-
Các chương trình mạng chạy lâu (ssh, opencode) thường khôi phục đúng cách
-
Các chương trình thay đổi terminal (ncurses) đôi khi có thể khiến terminal rơi vào trạng thái không mong đợi
Trong trường hợp terminal bị hỏng sau một`fg`, nhập:
reset
Sơ đồ tóm tắt
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam ArrowColor #444444
title "fg — Mẹo thay đổi mọi thứ"
rectangle "**Vấn đề**" as problem #LightCoral {
card "Terminal bị treo\nbởi một tiến trình" as p1
card "Mở một\nterminal mới = giải pháp cồng kềnh" as p2
}
rectangle "**Giải pháp**" as solution #LightGreen {
card "Ctrl+Z → tạm dừng" as s1
card "fg → tiếp tục" as s2
card "bg → chạy nền" as s3
card "jobs → xem các tiến trình bị tạm dừng" as s4
}
rectangle "**Ưu điểm**" as advantages #SkyBlue {
card "Không mất ngữ cảnh" as a1
card "Không cần khởi động lại" as a2
card "Chỉ cần một terminal" as a3
}
problem -right-> solution : "Giải pháp native\nUnix từ 40 năm qua"
solution -right-> advantages : "Kết quả"
note bottom of advantages
fg không phải là một mẹo ít người biết
Đó là một cơ chế nền tảng
của shell Unix
end note
@enduml
Tại sao fg bị đánh giá thấp
Hầu hết các nhà phát triển hiện đại khám phá ra`fg`muộn, thậm chí không bao giờ. Một vài lý do:
-
Các IDE ẩn terminal: VS Code cung cấp một terminal tích hợp, nhưng nhiều tab khiến`fg`có vẻ ít cần thiết hơn
-
Các công cụ đồ họa thay thế CLI: trình quản lý tệp, trình giám sát hệ thống, ứng dụng khách Git đồ họa
-
tmux và screen: các bộ ghép kênh này giải quyết cùng một vấn đề theo cách khác nhau, nhưng`fg`đơn giản hơn và luôn sẵn có
Tuy nhiên,`fg`là:
-
Phổ quát: có mặt trong tất cả các shell POSIX (bash, zsh, fish…)
-
Không phụ thuộc: không cần cài đặt tmux hoặc screen
-
Ảnh chụp nhanh: hai đòn tấn công (
Ctrl+Z`sau đó`fg) để tạm dừng và tiếp tục -
Người bảo tồn ngữ cảnh: trạng thái của tiến trình là nguyên vẹn
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "Tại sao fg bị\nđánh giá thấp?" as why #LightCoral {
card "IDE với terminal\nnhiều tab" as w1
card "Công cụ đồ họa\nthay thế CLI" as w2
card "tmux/screen\ngiải quyết vấn đề\ntheo cách khác" as w3
}
rectangle "Tại sao fg lại\nvượt trội?" as superior #LightGreen {
card "Phổ quát: bash,\nzsh, fish, sh..." as s1
card "Không phụ thuộc:\nkhông cần cài đặt" as s2
card "Hai lần nhấn:\nCtrl+Z rồi fg" as s3
card "Ngữ cảnh được giữ nguyên:\nkhông mất trạng thái" as s4
}
why -right-> superior : "Sự đơn giản\nluôn chiến thắng"
@enduml
Bản ghi nhớ kỹ thuật: các tín hiệu
Dành cho những người tò mò, đây là các tín hiệu liên quan:
| Tín hiệu | Tên | Gửi bởi | Hiệu ứng |
|---|---|---|---|
2 |
|
|
Gián đoạn — kết thúc tiến trình |
3 |
|
|
Thoát với core dump |
18 |
|
|
Dừng tương tác — tạm dừng quy trình |
19 |
|
|
Tiếp tục — khôi phục một tiến trình bị tạm dừng |
15 |
|
|
Kết thúc sạch |
9 |
|
|
Chấm dứt cưỡng bức (không thể chặn) |
Luồng hoàn chỉnh:
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif participant "Người dùng" as user participant "Shell" as shell participant "Tiến trình" as proc user -> shell : Ctrl+Z shell -> proc : SIGTSTP (tín hiệu 18) note right of proc : Tiến trình bị treo\ntrong bộ nhớ user -> shell : fg shell -> proc : SIGCONT (tín hiệu 19) note right of proc : Tiến trình tiếp tục\nở tiền cảnh user -> shell : bg shell -> proc : SIGCONT (tín hiệu 19) note right of proc : Tiến trình tiếp tục\nở chế độ nền user -> shell : Ctrl+C shell -> proc : SIGINT (tín hiệu 2) note right of proc : Tiến trình đã hoàn tất\ntrạng thái bị mất @enduml
Kết luận
`fg`không phải là một mẹo ít người biết — đó là một cơ chế nền tảng của shell Unix, đã có từ những năm 70. Việc hầu hết lập trình viên không biết đến nó là một triệu chứng của thời đại chúng ta: các IDE và multiplexer đã khiến chúng ta xa rời những điều cơ bản.
Nhưng khi bạn đang ở trên một máy chủ SSH, trong một terminal tối giản, hoặc chỉ đơn giản là đang sử dụng`opencode`và rằng LLM đang làm việc —Ctrl+Z, fg, bg, `jobs`là những người bạn tốt nhất của bạn.
_ Ctrl+Z tạm dừng, fg tiếp tục. Đó là chức năng tạm dừng-phát của terminal. _
Tài liệu tham khảo
-
man bash— mục JOB CONTROL -
man signal— danh sách các tín hiệu POSIX