Супер советы команды fg: когда Ctrl+Z спасает ваш терминальный сеанс
Опубликовано 18 April 2026
- Проблема: занятый терминал
- три незаменимые команды
- Сценарий 1 : Длинная сборка
- Сценарий 2: Забытый редактор
- Сценарий 3 : приостановленный процесс opencode
- Сценарий 4: Управлять несколькими задачами одновременно
- Различия между fg, bg, Ctrl+Z, Ctrl+C и &
- Полный жизненный цикл задания
- Продвинутые советы
- Ловушки и типичные ошибки
- Сводная диаграмма
- Почему fg недооценён
- Техническая память: сигналы
- Вывод
- Ссылки
Вы бросаете`./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]`(two spaces)номер задания. Le `+`указывает задание по умолчанию.
fg — Вернуть на передний план
`fg`Переместить приостановленную задачу на передний план. Процесс возобновляется точно там, где он остановился.
$ fg
./gradlew build
> Building... (reprend où il en était)
С конкретным номером задания:
$ fg %2
bg — Возобновить в фоновом режиме
`bg`Возобновляет процесс без блокировки терминала. Идеально для длительных сборок.
$ bg
[1]+ ./gradlew build &
$
Le `&`В конце означает, что процесс работает в фоновом режиме.
jobs — Перечислить задания оболочки
$ 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 : Остановлено (job [1]) term --> dev : Свободный промпт $ dev -> term : git status 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 : Приостановлено (задание [1]) dev -> shell : cat autre_fichier.txt shell --> dev : содержимое файла dev -> shell : fg shell -> vim : Возобновляет (job [1]) vim --> dev : Курсор точно\nв той же позиции note right of vim vim восстанавливает intégralement состояние : позиция курсора, буфер, стек отмены end note @enduml
vim`восстанавливает идеально его состояние после`fg: положение курсора, содержимое буфера, история отмены — всё сохранено.
|
Сценарий 3 : приостановленный процесс opencode
Это сценарий, который послужил основой для этой статьи. Вы используете`opencode`(CLI‑инструмент pour piloter un LLM sur votre code), и процесс приостановлен.
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif skinparam componentStyle rectangle actor Développeur as dev participant "opencode\n(LLM агент)" as agent participant "Shell" as shell participant "git" as git dev -> agent : Сессия активна agent -> agent : Tour de parole\ndu LLM en cours note over agent Ctrl+Z прибывает прямо посреди анализа кода end note dev -> agent : Ctrl+Z agent -> shell : Suspendu (job [1]) dev -> git : git diff git --> dev : видимые изменения dev -> shell : фг 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 test" 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
Каждая задача сохраняет
точное состояние
между приостановками /resume
end note
@enduml
Сводка команд заданий
| Заказ | Эффект | Пример |
|---|---|---|
|
Приостановить процесс в переднем плане |
Отправка`SIGTSTP` |
|
Возобнови задание по умолчанию на переднем плане |
|
|
Возобновляет задание n на переднем плане |
|
|
Возобновляет джоб по умолчанию в фоновом режиме |
|
|
Возобнови задание n в фоновом режиме |
|
|
Список заданий текущей оболочки |
|
|
Закончи работу 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 "Возобновляет задачу\nsuspendu в фоне\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
Alternative : запустить ./gradlew build &
Сразу в фоновом режиме
end note
@enduml
| Сочетание клавиш/Команда | сигнал | Эффект | Вернуться назад? |
|---|---|---|---|
|
|
Приостановить процесс (замороженный, в памяти) |
Да:`fg` ou |
|
|
Убей процесс (завершённый) |
Нет: процесс уничтожен |
|
|
Убей процесс + дамп ядра |
Нет: процесс уничтожен |
|
— |
Запусти напрямую в фоновом режиме |
`fg`чтобы вернуть его на передний план |
| `Ctrl+Z`естьнеразрушающий. Процесс заморожен как есть, в памяти. Вы можете немедленно продолжить с`fg`Это пауза, не стоп. |
Полный жизненный цикл задания
@startuml skinparam backgroundColor #FEFEFE skinparam defaultFontName sans-serif title Полный жизненный цикл задания оболочки state "Не существует" as none state "Передний план\n(занимает терминал)" as foreground #LightCoral state "Приостановлен\n(Ctrl+Z / SIGTSTP)" as suspended #LightYellow state "Фоновый\n(bg или &)" as background #LightGreen state "Завершено\n(exit code 0)" as done #LightGray [*] --> none none --> foreground : tape une commande foreground --> suspended : Ctrl+Z foreground --> done : нормальный конец suspended --> foreground : fg suspended --> background : bg suspended --> done : kill %n background --> foreground : fg background --> done : нормальное окончание\n(в фоновом режиме) background --> suspended : Ctrl+Z\n(если fg затем Ctrl+Z) done --> [*] note right of suspended Приостановленное состояние является опорой всей системы заданий end note @enduml
Продвинутые советы
fg с последней работой и чередованием
fg`без аргумента возвращает помеченную задачу+(самый последний). Но вы можете также использовать%-`pour нацеливания на предыдущую работу :
$ 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)
disown : отсоединить от оболочки
disown`Удаляет задание из таблицы заданий оболочки. Процесс продолжает работать, но его нельзя более вернуть с`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 "kill %n" as kill_ #LightCoral {
card "SIGTERM (чистый)" as k1
card "SIGKILL -9 (принудительно)" as k2
}
rectangle "disown" as disown #Lavender {
card "Отсоединяет от оболочки\n(остаётся при отключении)" as d1
}
rectangle "nohup + &" as nohup_ #LightGreen {
card "Процесс иммунизирован\nпротив SIGHUP\n(остаётся при закрытии\nтерминала)" as n1
}
fg_ -right-> kill_ : если нужно\nзавершить
kill_ -right-> disown_ : или отсоединить
nohup_ -left-> fg_ : fg переводит в\nпередний-план
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
Пауза → Возобновить
end note
note bottom of c
Убийство → Проигрыш
end note
@enduml
Задания локальны для оболочки
jobs, fg et `bg`работают только в оболочке, которая запустила процессы. Если вы откроете новый терминал, вы не увидите заданий другого.
# 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
Задания не сохраняются при закрытии оболочки
Если вы закроете терминал, все приостановленные и фоновые задания будут удалены. Чтобы процесс остался живым, используйте`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 "Терминал заблокирован\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 : "Нативное решение\nUnix на протяжении 40 лет"
solution -right-> advantages : "Результат"
note bottom of advantages
fg — это не какой-то малоизвестный трюк
Это фундаментальный механизм
Unix-оболочки
end note
@enduml
Почему fg недооценён
Большинство современных разработчиков открывают`fg`поздно, или даже никогда. Несколько причин :
-
IDE скрывают терминал: VS Code предлагает встроенный терминал, но множественные вкладки делают`fg`внешне менее необходимо
-
Графические инструменты заменяют CLI: файловые менеджеры, системные мониторы, графические клиенты Git
-
tmux и screen: эти мультиплексоры решают одну и ту же проблему по-разному, но`fg`проще и всегда доступен
Однако,`fg`есть :
-
Универсальный: присутствует во всех POSIX-оболочках (bash, zsh, fish…)
-
Без зависимости: не нужно устанавливать tmux или screen
-
Снимок: два удара (
Ctrl+Z`потом`fg) для приостановки и возобновления -
Хранитель контекста: состояние процесса неповреждено
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
rectangle "Почему fg\nнедооценен?" as why #LightCoral {
card "IDE с терминалом\nна несколько вкладок" as w1
card "Графические инструменты,\nзаменяющие CLI" as w2
card "tmux/screen\nрешают проблему\nиначе" as w3
}
rectangle "Почему fg\nлучше?" 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 (signal 18) note right of proc : Процесс завис\nв памяти user -> shell : fg shell -> proc : SIGCONT (signal 19) note right of proc : Процесс возобновлен\nна переднем плане user -> shell : bg shell -> proc : SIGCONT (signal 19) note right of proc : Процесс возобновлен\nв фоновом режиме user -> shell : Ctrl+C shell -> proc : SIGINT (signal 2) note right of proc : Процесс завершен\nсостояние потеряно @enduml
Вывод
`fg`Это не скрытый совет — это фундаментальный механизм Unix-оболочки, доступный с 1970-х годов. То, что большинство разработчиков им пренебрегает, является симптомом нашей эпохи: IDE и мультиплексоры отдалили нас от основ.
Но когда вы на сервере SSH, в минимальном терминале, или просто используя`opencode`et que LLM работает вовсю —Ctrl+Z, fg, bg, `jobs`Они ваши лучшие друзья.
_ Ctrl+Z приостанавливает, fg возобновляет. Это пауза‑play терминала. _
Ссылки
-
man bash— секция JOB CONTROL -
man signal— список сигналов POSIX -
Продвинутое руководство по написанию сценариев Bash — управление заданиями