Jobs: foreground, background, nohup, disown
Когда вы в shell запускаете команду, по умолчанию она работает на переднем плане (foreground) — занимает терминал до завершения. Если команда длинная (бэкап на 2 часа, копирование 100GB через rsync), вы не можете в этом терминале делать ничего другого.
Для этого есть background jobs — способ запускать процессы «фоном», вернуться к ним позже, и (главное для DE) — оставлять их работать после того, как закроется ваше SSH-соединение с сервером.
& — запуск в фон
Самое простое — добавить & в конец команды:
# Запустить sleep на 300 секунд в фон
sleep 300 &
# [1] 12345
[1] — это job ID (внутри shell). 12345 — это PID. Job ID — это нумерация внутри shell для удобства управления, PID — глобальный.
После & команда уходит в фон, и shell возвращает вам приглашение. Вы можете запускать другие команды, пока та работает.
Куда уходит процесс при разных способах запуска
jobs — список текущих задач shell
sleep 100 &
sleep 200 &
sleep 300 &
jobs
# [1] Running sleep 100 &
# [2]- Running sleep 200 &
# [3]+ Running sleep 300 &
Колонки:
[1],[2],[3]— Job ID (нумерация локальная для shell).+— current job (default для fg/bg без аргументов).-— previous job.Running/Stopped/Done— состояние.sleep 100 &— команда.
# Только PID-ы
jobs -p
# Только запущенные
jobs -r
# Только остановленные
jobs -s
fg, bg — перемещение между состояниями
fg (foreground) — перенести job на передний план.
bg (background) — запустить остановленный job в фоне.
sleep 100 &
# [1] 12345
sleep 200 &
# [2] 12346
# Вернуть job 1 на передний план
fg %1
# (теперь sleep 100 на foreground, до завершения)
# Если просто fg без аргумента — current job (с +)
fg
# Аналогично bg
bg %2
%1, %2 — это синтаксис job spec:
Разные способы указать конкретную задачу
sleep 100 &
sleep 200 &
vim file.txt &
fg %vim # перенести vim на foreground (по началу команды)
fg %?file # по подстроке
fg %2 # по номеру
Ctrl-Z — приостановить и в фон
Сценарий: вы запустили vim file.txt, и поняли, что вам нужно выполнить ещё одну команду в shell — но не хотите закрывать vim. Решение:
- Нажимаете Ctrl-Z — vim приостанавливается (получает SIGTSTP).
- Shell возвращает prompt. Вы что-то делаете.
- Возвращаетесь в vim:
fg.
vim file.txt
# В vim нажми Ctrl-Z
# [1]+ Stopped vim file.txt
# Сейчас в shell. Можешь что-то сделать
ls
date
# Вернуться в vim
fg
Альтернативно — после Ctrl-Z запустить его в фон через bg. Это полезно если процесс может работать без терминала, например tail -f:
tail -f /var/log/airflow.log
# Ctrl-Z
# [1]+ Stopped tail -f /var/log/airflow.log
bg
# [1]+ tail -f /var/log/airflow.log &
# Теперь tail работает в фоне (но всё ещё пишет в твой терминал — будет мешать)
Главная проблема background jobs: SIGHUP
Все background jobs привязаны к вашему shell. Когда shell завершается (вы закрыли SSH, разлогинились, упало соединение) — shell посылает SIGHUP всем своим jobs. По умолчанию SIGHUP -> процесс умирает.
Это критичная проблема для DE. Запустили бэкап на 2 часа через &, разлогинились — бэкап убит.
Решение — nohup или disown.
nohup — игнорировать SIGHUP
nohup (no hangup) запускает команду в режиме «игнорировать SIGHUP»:
nohup ./long_backup.sh &
# nohup: ignoring input and appending output to 'nohup.out'
# [1] 12345
Что делает nohup:
- Запускает команду, игнорируя SIGHUP.
- Перенаправляет stdout/stderr в файл
nohup.out(если не указано иное). - Отвязывает stdin (no input).
После этого можно безопасно закрыть терминал — процесс продолжит работать.
# С кастомным выходом
nohup ./backup.sh > /var/log/backup.log 2>&1 &
# Проверить, что работает после релогина
ssh server
pgrep -af backup.sh
tail -f /var/log/backup.log
nohup не делает «daemon-изацию» в строгом смысле. Процесс остаётся child вашего терминала-shell (точнее, init после переподписки). Это «good enough» для большинства задач, но для production-сервисов используют systemd (модуль 15).
disown — отвязать job от shell
disown — другой способ. Запустите процесс как обычно с &, потом отвяжите:
./backup.sh &
# [1] 12345
disown
# Теперь job 1 не в списке shell, не получит SIGHUP при закрытии
disown без аргументов отвязывает current job. Можно явно:
disown %1 # конкретный job
disown -a # все jobs
disown -h %1 # не удалять из списка, но не слать SIGHUP
Преимущество disown — гибкость: можно сначала запустить, посмотреть что начало работать как надо, и потом «отвязать». А nohup нужно решать заранее.
Недостаток — stdout/stderr остаются привязанными к терминалу. После закрытия терминала может быть проблема — куда писать? Поэтому если используете disown, всё равно перенаправляйте output в файл:
./backup.sh > /var/log/backup.log 2>&1 &
disown
Сравнение: & vs nohup & vs disown vs screen/tmux
Когда какой инструмент уместен
screen и tmux мы подробно разбираем в модуле 19 (продуктивность). Главная идея: они создают «виртуальный терминал», который продолжает существовать даже после закрытия SSH. Вы возвращаетесь — и видите тот же экран, как будто никуда не уходили. Для долгих интерактивных задач — стандарт de facto в DE/DevOps.
# Превью — модуль 19 даст детально
# Создать tmux-сессию
tmux new -s backup
# Внутри tmux запустить долгую задачу
./backup.sh
# Отключиться от сессии (Ctrl-B, потом d)
# Закрыть SSH, переподключиться
# Вернуться в сессию
tmux attach -t backup
DE-сценарии
1. Долгий rsync на удалённый сервер
# Плохо — потеряется при разлогине
rsync -avz huge_data/ user@backup-server:/data/
# Хорошо
nohup rsync -avz huge_data/ user@backup-server:/data/ > rsync.log 2>&1 &
echo "Started PID $!"
# Можно закрыть SSH
Альтернатива (часто лучше): запустить в tmux/screen сессии, получить визуальный прогресс rsync, отключиться через Ctrl-B d, вернуться через tmux attach.
2. Параллельный запуск нескольких заданий
# Скачать 10 файлов параллельно
for url in $(cat urls.txt); do
wget "$url" &
done
# Дождаться завершения всех
wait
echo "Все скачаны"
wait без аргументов — ждать всех background jobs. С аргументом wait %1 — конкретного.
3. Параллельный rsync для разных директорий
nohup rsync -avz /data/2026-04/ backup:/data/2026-04/ > 04.log 2>&1 &
nohup rsync -avz /data/2026-03/ backup:/data/2026-03/ > 03.log 2>&1 &
nohup rsync -avz /data/2026-02/ backup:/data/2026-02/ > 02.log 2>&1 &
# Все три работают независимо
jobs
4. Запустил-забыл из cron
# В crontab:
0 2 * * * /opt/scripts/nightly_etl.sh > /var/log/etl.log 2>&1
В cron — не нужен nohup/disown, потому что cron сам не привязан к терминалу. Достаточно перенаправления stdout/stderr. Модуль 15 про cron.
5. Hot-reload Airflow scheduler без downtime
# Старый scheduler ещё работает
OLD_PID=$(pgrep -x airflow-scheduler)
# Запускаем новый рядом
nohup airflow scheduler > /var/log/airflow/scheduler.log 2>&1 &
NEW_PID=$!
# Проверяем, что новый поднялся
sleep 5
pgrep -af airflow-scheduler
# Гасим старый
kill -TERM $OLD_PID
В реальности — systemctl restart, но понимать механику полезно.
Подводные камни
1. Output в фон захламляет терминал
Если background job пишет в stdout, текст вылезает поверх вашего prompt. Бесит. Решение — перенаправление:
# Плохо
nohup ./script.sh &
# Хорошо
nohup ./script.sh > /tmp/script.log 2>&1 &
2>&1 — модуль 9, перенаправление stderr в stdout.
2. Bash shell вы видите как-будто завис
Когда вы запускаете cmd &, bash печатает [1] 12345 и возвращает prompt. Но когда job завершается, bash хочет сообщить «[1] Done sleep 100» — он печатает это перед следующим prompt. Если у вас открыт интерактивный prompt, выглядит как будто что-то само напечаталось.
3. Process group и Ctrl-C
Если вы запустили несколько процессов в pipeline cmd1 | cmd2 | cmd3 & — это одна job, состоящая из трёх процессов в одной process group. Ctrl-C / SIGINT идёт всей группе.
sleep 100 | sleep 200 | sleep 300 &
jobs
# [1]+ Running sleep 100 | sleep 200 | sleep 300 &
ps -j
Попробуй сам
# 1. Базовое
sleep 100 &
sleep 200 &
jobs
fg %1 # вернул sleep 100 на foreground
# Прерви Ctrl-C
jobs # теперь только sleep 200
# 2. Ctrl-Z + bg
sleep 100
# Нажми Ctrl-Z
# [1]+ Stopped sleep 100
bg
jobs
# [1]+ Running sleep 100 &
# 3. nohup пережил SSH
nohup sleep 600 > /tmp/sleep.log 2>&1 &
SLEEP_PID=$!
# Откройте новый ssh / закройте текущий, переподключитесь
ps -p $SLEEP_PID # всё ещё работает!
kill $SLEEP_PID
# 4. disown
./long-task.sh &
disown
# Закрой SSH — процесс выживет
# 5. wait для синхронизации
echo "Старт"
(sleep 3; echo "Готово 1") &
(sleep 2; echo "Готово 2") &
(sleep 4; echo "Готово 3") &
wait
echo "Все готовы"
# 6. Параллельный download (если есть wget)
for n in 1 2 3 4 5; do
(echo "download $n"; sleep 2; echo "done $n") &
done
wait
Cross-link: предыдущий урок 04 — про SIGHUP, который убивает background jobs. Модуль 17 — production bash, где обсуждаем trap и graceful shutdown. Модуль 18 — screen/tmux для долгих задач. Модуль 14 — systemd для production-сервисов.