jobs — Run, Suspend and Background Shell Jobs
Shell job control with jobs, fg, bg, &, Ctrl+Z and disown: run, suspend, resume and detach processes inside an interactive shell.
When a command takes a while to run, you don't want it locking up your terminal – that is exactly what the shell's job control is for. With &, Ctrl+Z, jobs, fg, bg and disown you launch processes in the background, suspend them, pull them back, or detach them from the terminal entirely. This guide walks you through the builtins and job specifiers that keep several tasks under control at once, from a quick sleep 300 & to a bounded parallel queue.
Starting Jobs
<command> & — Run a command in the background. The shell prints '[n] pid' and returns immediately.
sleep 300 &<command1> & <command2> & <command3> & — Start multiple background jobs in one line.
make build & make docs & make test &( <command> & ) — Run a command in a subshell so it survives the parent without showing in jobs.
( long-running-task & )<command> &! — Zsh: start in background AND immediately disown — survives shell exit, no SIGHUP.
long-running-task &!<command> |& — Background a pipeline that includes stderr (bash 4+, zsh).
make 2>&1 | tee build.log &Suspending Jobs
Ctrl+Z — Send SIGTSTP to the foreground process — suspends it and returns control to the shell.
vim file.txt # press Ctrl+Z to suspend, edit again with fgCtrl+Y — Send SIGTSTP only when the process tries to read input (delayed suspend, BSD/macOS only).
less bigfile # Ctrl+Y suspends on next readkill -STOP <pid> — Suspend any process by PID (not just the foreground one).
kill -STOP 4242kill -CONT <pid> — Resume a stopped process by PID — the counterpart to -STOP.
kill -CONT 4242Listing Jobs
jobs — List all jobs in the current shell with their numbers, status, and command.
$ jobs
[1]+ Running sleep 300 &
[2]- Stopped vim file.txtjobs -l — List jobs including their PID.
jobs -ljobs -p — Print only PIDs of jobs (one per line) — handy for scripting.
jobs -p | xargs killjobs -r — List only running jobs.
jobs -rjobs -s — List only stopped jobs.
jobs -sjobs -n — List only jobs whose state changed since the last notification.
jobs -njobs %<n> — Show status of one specific job.
jobs %1Job Specifiers
%<n> — Refer to job number n (the number shown by 'jobs').
fg %2%% or %+ — The current job (most recently suspended or backgrounded). Implicit target of 'fg' / 'bg' without arguments.
fg %%%- — The previous job (the one before %+).
fg %-%<string> — Job whose command begins with the given string.
fg %vim%?<string> — Job whose command contains the given string anywhere.
kill %?makeForeground & Background
fg — Bring the current (most recent) job back to the foreground.
fgfg %<n> — Bring job number n back to the foreground.
fg %1bg — Resume the current stopped job in the background (continue running, but no terminal input).
Ctrl+Z, then 'bg' to keep running detachedbg %<n> — Resume a specific stopped job in the background.
bg %2%<n> — Bare job specifier brings that job to the foreground (shortcut for 'fg %n').
%1%<n> & — Bare job specifier with '&' resumes it in the background (shortcut for 'bg %n').
%1 &Killing & Signaling Jobs
kill %<n> — Send SIGTERM to a job by job number.
kill %1kill -9 %<n> — Force-kill (SIGKILL) a job that won't terminate.
kill -9 %2kill -<signal> %<n> — Send any signal by name or number to a job.
kill -HUP %1kill %<string> — Kill the job whose command starts with
kill %makekill $(jobs -p) — Send SIGTERM to all current jobs of the shell.
kill $(jobs -p)Detaching Jobs (disown)
disown — Remove the most recent job from the shell's job table — it keeps running but is no longer tracked.
long-task & disowndisown %<n> — Detach a specific job by number.
disown %2disown -h %<n> — Keep the job in the table but mark it to NOT receive SIGHUP when the shell exits.
disown -h %1disown -a — Detach all jobs from the shell.
disown -adisown -r — Detach only running jobs (leave stopped ones in the table).
disown -rshopt -s huponexit — Bash: send SIGHUP to all jobs when the shell exits (default is OFF — they survive).
shopt -s huponexitsetopt nohup — Zsh: do NOT send SIGHUP to jobs when the shell exits.
setopt nohupWaiting for Jobs
wait — Block until all background jobs of the current shell have finished.
task1 & task2 & task3 & waitwait %<n> — Wait for one specific job to finish — exit status of wait equals the job's exit status.
long-task & wait %1; echo "exited $?"wait <pid> — Wait for a specific PID (also background jobs not in the current shell, on bash 5.1+).
wait 4242wait -n — Wait for ANY single background job to finish (bash 4.3+, zsh). Useful for parallel queues.
for i in 1 2 3; do task $i & done; wait -nwait -f <pid> — Wait until the process actually terminates, even if its parent already reaped it (bash 5.1+).
wait -f 4242Patterns & Pitfalls
command > out.log 2>&1 & — Background a command and redirect both stdout and stderr to a file (otherwise stray output appears in the terminal).
make build > build.log 2>&1 &command < /dev/null > out.log 2>&1 & — Fully detach from terminal stdin/stdout — no SIGTTIN/SIGTTOU when the terminal is closed.
long-task < /dev/null > out.log 2>&1 &set -m / set +m — Enable or disable job control. Off by default in scripts — turn on with 'set -m' to use %n inside a script.
set -m; long-task & fg %1set -b — Bash: notify about job state changes IMMEDIATELY instead of only before the next prompt.
set -b$! — PID of the last backgrounded process — pair it with 'wait' or 'kill' to track a specific job.
long-task & echo $! > task.pidtrap 'kill $(jobs -p) 2>/dev/null' EXIT — Clean up: kill all child jobs when the shell or script exits.
trap 'kill $(jobs -p) 2>/dev/null' EXITCommon Recipes
Ctrl+Z, bg, disown — Promote a foreground command into a fully detached background process — survives logout.
tail -f big.log # Ctrl+Z
bg
disownfor f in *.mp4; do ffmpeg ... "$f" & done; wait — Run a batch in parallel and wait for all to finish.
for f in *.mp4; do ffmpeg -i "$f" "${f%.mp4}.webm" & done; waitParallel queue with -n (max parallel) — Process work items with a bounded number of parallel jobs.
max=4; for i in {1..20}; do (( $(jobs -r | wc -l) >= max )) && wait -n; task $i & done; waitkill %1 && wait %1 2>/dev/null — Stop a job and wait for the shell to fully reap it (silences the 'Terminated' notice).
kill %1 && wait %1 2>/dev/null Conclusion
Job control turns a single shell into a small process manager: jobs shows you what is running, Ctrl+Z followed by bg pushes a stuck command into the background, and fg pulls it back. The catch is that background jobs are tied to your terminal. Quit the shell or log out and, by default, they receive a SIGHUP and die – taking any unsaved work with them. To make a job survive a logout, detach it with disown -h, or start it under nohup, screen or tmux from the outset. And remember: these builtins only work in an interactive shell; inside scripts you must enable job control first with set -m.
Further Reading
- GNU Bash: Job Control – the canonical Bash reference for foreground/background jobs and signals
- Wikipedia: Job control (Unix) – background on suspending, resuming and detaching processes under Unix