Фоновые процессы: &, nohup, setsid, disown, jobs, wait
Запуск в фоне, управление заданиями bash и как сделать, чтобы процесс пережил закрытие терминала
Команда с & в конце запускается в фоне: оболочка не ждёт её завершения и сразу возвращает приглашение. Такие процессы — задания (jobs) текущей оболочки: их видно в jobs, можно вернуть на передний план и дождаться через wait. Но при закрытии терминала оболочка рассылает своим заданиям сигнал SIGHUP, и они завершаются. Чтобы процесс пережил выход из SSH, его отвязывают от терминала: nohup, disown или setsid.
Синтаксис
команда & # запустить в фоне
jobs [-l|-p|-r|-s] # список заданий
fg [%N] / bg [%N] # на передний план / продолжить в фоне
wait [-n] [PID|%N…] # дождаться завершения
disown [-h] [-a] [%N] # отвязать задание от оболочки
nohup команда [аргументы…] [&]
setsid [-f] [-w] команда [аргументы…]
Управление заданиями (встроено в bash)
| Команда / клавиши | Что делает |
|---|---|
команда & | запустить в фоне; печатает [1] 4242 — номер задания и PID |
$! | PID последнего фонового процесса — сохрани в переменную |
Ctrl+Z | приостановить текущую команду (сигнал SIGTSTP), она станет Stopped |
bg [%N] | продолжить остановленное задание в фоне |
fg [%N] | вернуть задание на передний план |
jobs | список: [1]- Running sleep 100 &; + — текущее, - — предыдущее |
jobs -l / -p | с PID / только PID |
jobs -r / -s | только работающие / только остановленные |
kill %N | послать сигнал заданию по номеру |
Ссылки на задания: %1 — по номеру, %+ или %% — текущее, %- — предыдущее, %sleep — по началу команды, %?str — по подстроке.
wait
| Форма | Что делает |
|---|---|
wait | ждать все фоновые процессы; код возврата 0 |
wait PID / wait %N | ждать конкретный; код возврата — код этого процесса |
wait -n | ждать первый завершившийся из любых; его код возврата |
wait -n -p var | то же и записать PID завершившегося в var (bash 5.1+) |
Если такого процесса нет среди детей оболочки — код 127.
Как пережить закрытие терминала
| Способ | Как работает | Вывод |
|---|---|---|
nohup cmd & | запускает команду с игнорированием SIGHUP | если stdout — терминал, пишет в ./nohup.out (или ~/nohup.out), права 600 |
cmd & disown | убирает задание из таблицы оболочки — она не пошлёт ему SIGHUP | остаётся привязан к терминалу — перенаправь сам |
disown -h %N | задание остаётся в jobs, но SIGHUP ему не пошлют | то же |
setsid cmd | запускает в новой сессии без управляющего терминала; родителем становится PID 1 | то же — перенаправь сам |
tmux / screen | терминал живёт на сервере, можно переподключиться | всё видно после повторного входа |
| systemd-юнит | правильный способ для долгоживущих сервисов | journalctl |
Опции setsid: -f — всегда создавать новый процесс (вернуть управление сразу), -w — дождаться программы и вернуть её код, -c — сделать текущий терминал управляющим.
Примеры
# запустить в фоне и запомнить PID
sleep 300 &
pid=$!
jobs -l
kill "$pid"
# остановленную команду продолжить в фоне
# (нажал Ctrl+Z во время долгого cp)
bg
jobs
# параллельно несколько задач и дождаться всех, проверив коды
for host in web1 web2 web3; do
ping -c1 -W1 "$host" >/dev/null 2>&1 &
done
wait
# дождаться конкретного и получить его код возврата
(sleep 1; exit 3) &
wait $!
echo "код: $?" # 3
# переживёт выход из SSH; вывод — в свой лог, stdin закрыт
nohup ./long-task.sh > task.log 2>&1 < /dev/null &
# уже запустил без nohup — отвязать задним числом
./long-task.sh > task.log 2>&1 &
disown
# полностью отделить от терминала и сессии
setsid -f ./daemon.sh > /tmp/daemon.log 2>&1 < /dev/null
Подводные камни
- Обычное
cmd &умирает при закрытии SSH. Интерактивная оболочка при полученииSIGHUPрассылает его всем своим заданиям.nohup,disown,disown -hиsetsidот этого защищают. nohupне перенаправляет вывод, если он уже не терминал.nohup.outпоявляется только когда stdout — терминал. Аdisownиsetsidне перенаправляют никогда: процесс, пишущий в закрытый терминал, получит ошибку записи и может упасть. Всегда указывай> файл 2>&1 < /dev/null.nohupне делает&за тебя.nohup cmdбез&займёт терминал до конца работы.waitбез аргументов возвращает 0, даже если задания упали. Нужен код каждого — сохраняй$!и вызывайwait "$pid"по очереди.waitвидит только своих детей. Процесс, запущенный из другой оболочки или ушедший вsetsid -f, дождаться так нельзя: «is not a child of this shell», код127. Для чужих процессов — цикл сkill -0 PID.jobsи%Nработают только в той оболочке, где запущено задание. В новом SSH-сеансеjobsпуст — ищи процесс черезpgrep/ps.- Фоновый процесс, читающий с терминала, останавливается (
Stopped (tty input)). Закрывай stdin:< /dev/null. $!— PID последнего элемента конвейераa | b &, а не первого.- Для настоящих сервисов
nohup— костыль: нет перезапуска при падении и нормальных логов. Используй systemd или supervisor.
См. также
kill · ps · timeout · tmux · redirection · supervisorctl · exit-codes