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 fg

Ctrl+Y — Send SIGTSTP only when the process tries to read input (delayed suspend, BSD/macOS only).

less bigfile   # Ctrl+Y suspends on next read

kill -STOP <pid> — Suspend any process by PID (not just the foreground one).

kill -STOP 4242

kill -CONT <pid> — Resume a stopped process by PID — the counterpart to -STOP.

kill -CONT 4242

Listing Jobs

jobs — List all jobs in the current shell with their numbers, status, and command.

$ jobs
[1]+  Running                 sleep 300 &
[2]-  Stopped                 vim file.txt

jobs -l — List jobs including their PID.

jobs -l

jobs -p — Print only PIDs of jobs (one per line) — handy for scripting.

jobs -p | xargs kill

jobs -r — List only running jobs.

jobs -r

jobs -s — List only stopped jobs.

jobs -s

jobs -n — List only jobs whose state changed since the last notification.

jobs -n

jobs %<n> — Show status of one specific job.

jobs %1

Job 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 %?make

Foreground & Background

fg — Bring the current (most recent) job back to the foreground.

fg

fg %<n> — Bring job number n back to the foreground.

fg %1

bg — Resume the current stopped job in the background (continue running, but no terminal input).

Ctrl+Z, then 'bg' to keep running detached

bg %<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 %1

kill -9 %<n> — Force-kill (SIGKILL) a job that won't terminate.

kill -9 %2

kill -<signal> %<n> — Send any signal by name or number to a job.

kill -HUP %1

kill %<string> — Kill the job whose command starts with .

kill %make

kill $(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 & disown

disown %<n> — Detach a specific job by number.

disown %2

disown -h %<n> — Keep the job in the table but mark it to NOT receive SIGHUP when the shell exits.

disown -h %1

disown -a — Detach all jobs from the shell.

disown -a

disown -r — Detach only running jobs (leave stopped ones in the table).

disown -r

shopt -s huponexit — Bash: send SIGHUP to all jobs when the shell exits (default is OFF — they survive).

shopt -s huponexit

setopt nohup — Zsh: do NOT send SIGHUP to jobs when the shell exits.

setopt nohup

Waiting for Jobs

wait — Block until all background jobs of the current shell have finished.

task1 & task2 & task3 & wait

wait %<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 4242

wait -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 -n

wait -f <pid> — Wait until the process actually terminates, even if its parent already reaped it (bash 5.1+).

wait -f 4242

Patterns & 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 %1

set -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.pid

trap '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' EXIT

Common Recipes

Ctrl+Z, bg, disown — Promote a foreground command into a fully detached background process — survives logout.

tail -f big.log   # Ctrl+Z
bg
disown

for 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; wait

Parallel 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; wait

kill %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

  • bash – the Bourne-Again shell that hosts the job-control builtins
  • kill – send signals to processes and jobs (SIGTERM, SIGKILL, SIGCONT)
  • nohup – immunise processes against SIGHUP so they survive logout