thời gian đọc: 10 minutes

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:

  1. 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

  2. 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ụ

Ctrl+Z

Tạm dừng tiến trình ở tiền cảnh

Gửi`SIGTSTP`

fg

Tiếp tục tác vụ mặc định ở tiền cảnh

fg ou fg %1

fg %n

Tiếp tục job n ở tiền cảnh

fg %2

bg

Tiếp tục công việc mặc định trong nền

bg ou bg %1

bg %n

Tiếp tục job n trong nền

bg %2

jobs

Liệt kê các job của shell hiện tại

jobs -l(với PID)

kill %n

Hoàn thành job n

kill %2

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?

Ctrl+Z

SIGTSTP

Tạm dừng tiến trình (bị đóng băng, trong bộ nhớ)

Vâng:`fg` ou bg

Ctrl+C

SIGINT

Giết tiến trình (đã hoàn tất)

Không: tiến trình bị hủy

Ctrl+\

SIGQUIT

Giết tiến trình + core dump

Không: tiến trình bị hủy

commande &

—

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:

  1. 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

  2. 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

  3. 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à:

  1. Phổ quát: có mặt trong tất cả các shell POSIX (bash, zsh, fish…​)

  2. Không phụ thuộc: không cần cài đặt tmux hoặc screen

  3. Ảnh chụp nhanh: hai đòn tấn công (Ctrl+Z`sau đó`fg) để tạm dừng và tiếp tục

  4. 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

SIGINT

Ctrl+C

Gián đoạn — kết thúc tiến trình

3

SIGQUIT

Ctrl+\

Thoát với core dump

18

SIGTSTP

Ctrl+Z

Dừng tương tác — tạm dừng quy trình

19

SIGCONT

fg/bg

Tiếp tục — khôi phục một tiến trình bị tạm dừng

15

SIGTERM

kill %n

Kết thúc sạch

9

SIGKILL

kill -9 %n

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. _

Bài viết liên quan