время чтения : 10 minutes

Вы бросаете`./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é

Преимущество двойное:

  1. Нет потери контекста: сессия LLM, история диалога, открытые файлы — всё сохранено

  2. Нет перезагрузки: не нужно перезапускать 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

Сводка команд заданий

Заказ Эффект Пример

Ctrl+Z

Приостановить процесс в переднем плане

Отправка`SIGTSTP`

fg

Возобнови задание по умолчанию на переднем плане

fg ou fg %1

fg %n

Возобновляет задание n на переднем плане

fg %2

bg

Возобновляет джоб по умолчанию в фоновом режиме

bg ou bg %1

bg %n

Возобнови задание n в фоновом режиме

bg %2

jobs

Список заданий текущей оболочки

jobs -l(с PID)

kill %n

Закончи работу n

kill %2

Различия между 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
Сочетание клавиш/Команда сигнал Эффект Вернуться назад?

Ctrl+Z

SIGTSTP

Приостановить процесс (замороженный, в памяти)

Да:`fg` ou bg

Ctrl+C

SIGINT

Убей процесс (завершённый)

Нет: процесс уничтожен

Ctrl+\

SIGQUIT

Убей процесс + дамп ядра

Нет: процесс уничтожен

commande &

—

Запусти напрямую в фоновом режиме

`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`поздно, или даже никогда. Несколько причин :

  1. IDE скрывают терминал: VS Code предлагает встроенный терминал, но множественные вкладки делают`fg`внешне менее необходимо

  2. Графические инструменты заменяют CLI: файловые менеджеры, системные мониторы, графические клиенты Git

  3. tmux и screen: эти мультиплексоры решают одну и ту же проблему по-разному, но`fg`проще и всегда доступен

Однако,`fg`есть :

  1. Универсальный: присутствует во всех POSIX-оболочках (bash, zsh, fish…​)

  2. Без зависимости: не нужно устанавливать tmux или screen

  3. Снимок: два удара (Ctrl+Z`потом`fg) для приостановки и возобновления

  4. Хранитель контекста: состояние процесса неповреждено

@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

SIGINT

Ctrl+C

Прерывание — завершает процесс

3

SIGQUIT

Ctrl+\

Выходит с дампом ядра

18

SIGTSTP

Ctrl+Z

Интерактивная остановка — приостанавливает процесс

19

SIGCONT

fg/bg

Продолжить — возобновляет приостановленный процесс

15

SIGTERM

kill %n

собственное окончание

9

SIGKILL

kill -9 %n

Принудительное завершение (неперехватываемое)

Полный поток :

@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 терминала. _

Похожие статьи