opscape

Processes and jobs · bash builtin

jobs

List the background and stopped jobs of the current shell, with their job numbers and state.

jobs [-lnprs] [JOBSPEC...]

8 of 8 examples run in Debian 13.7 on 2026-10-31 · manual: bash(1)

A job is a pipeline the shell started: one command, or several joined by |. Ending a line with & starts it in the background; pressing Ctrl-Z in a terminal stops the foreground job. jobs lists what this shell is tracking, each with a number in brackets that you use with %: kill %2, fg %1, bg %3, wait %1. + marks the current job (the default for fg and bg) and - the previous one.

The job table belongs to one shell. A new terminal, a script, or a subshell has its own, which is why jobs in a second terminal shows nothing and why a script cannot see the jobs of the shell that ran it. Processes are still visible to everyone with ps; jobs are a shell-level bookkeeping layer on top of them.

The examples run in a non-interactive bash -c, where job control is off unless the script turns it on with set -m. Background jobs and jobs work either way; stopping and resuming with fg and bg need job control. Ctrl-Z itself needs a terminal; the examples send the same signal (TSTP) with kill -TSTP instead.

Examples

List background jobs

sleep 300 &
sleep 600 &
jobs
output
[1]-  Running                 sleep 300 &
[2]+  Running                 sleep 600 &

Two jobs, numbered in the order they were started. [2]+ is the current job and [1]- the previous one, so a bare fg would pick job 2.

Show the process IDs too

sleep 300 &
sleep 600 | cat &
jobs -l
output
[1]-  1192 Running                 sleep 300 &
[2]+  1193 Running                 sleep 600
      1194                       | cat &

-l adds the PIDs. A pipeline is one job with several processes, so job 2 lists the PID of each stage on its own line.

Get just the PIDs for another command

sleep 300 &
sleep 600 &
jobs -p
kill $(jobs -p)
wait
jobs -p | wc -l
output
1196
1197
1

-p prints one PID per job (the process group leader). kill $(jobs -p) stops all of them; afterwards there is nothing left to list.

See how finished jobs are reported

sleep 300 &
(sleep 0.1; exit 3) &
true &
sleep 0.3
jobs
echo ---
jobs
output
[1]   Running                 sleep 300 &
[2]-  Exit 3                  ( sleep 0.1; exit 3 )
[3]+  Done                    true
---
[1]+  Running                 sleep 300 &

Jobs that ended since the last report are listed once, as Done or Exit N with their status, and then dropped from the table; the second jobs shows only the running one. An interactive shell prints these notices before the next prompt.

Filter running and stopped jobs

set -m
sleep 300 &
sleep 600 &
kill -TSTP %1
sleep 0.1
echo "stopped:"; jobs -s
echo "running:"; jobs -r
output
stopped:
[1]+  Stopped                 sleep 300
running:
[2]-  Running                 sleep 600 &

kill -TSTP %1 does what Ctrl-Z does to a foreground job: it stops it. -s lists stopped jobs only and -r running ones. With job control on, each job is its own process group, which is what lets the shell stop and resume jobs as units.

Refer to a job by number, name or text

sleep 300 &
python3 -c 'import time; time.sleep(600)' &
jobs %1
jobs %python3
jobs %?600
jobs %-
output
[1]-  Running                 sleep 300 &
[2]+  Running                 python3 -c 'import time; time.sleep(600)' &
[2]+  Running                 python3 -c 'import time; time.sleep(600)' &
[1]-  Running                 sleep 300 &

%N is job N, %name the job whose command starts with name, %?text the one whose command contains text, and %+/%% and %- the current and previous jobs. Each works anywhere a job spec is accepted: kill, wait, fg, bg, disown.

Ask for a job that does not exist

sleep 300 &
jobs %3
outputexit 1
/bin/bash: line 2: jobs: %3: no such job

Job specs are checked: no such job and exit status 1. Scripts can use `jobs %1

/dev/null 2>&1` to test whether a job is still in the table.

Kill a script's background jobs when it exits

worker.sh
#!/bin/bash
trap 'kill $(jobs -p) 2>/dev/null; wait' EXIT
sleep 300 &
sleep 600 &
jobs -p > helpers.pid
echo "started $(wc -l < helpers.pid) helpers"
bash worker.sh
ps -p "$(paste -sd, helpers.pid)" || echo "helpers are gone"
output
started 2 helpers
    PID TTY          TIME CMD
helpers are gone

Background jobs outlive the script that started them unless something stops them. The EXIT trap kills whatever is left in the job table when the script ends, then waits for it to be gone. Afterwards ps -p finds none of the helper PIDs the script recorded.

Common pitfalls

  • Expecting jobs to show processes started in another terminal, by a script, or before sudo -i. Each shell has its own job table; use ps or pgrep to find processes from elsewhere.

  • Closing the terminal with jobs still running. The shell sends HUP to its jobs on exit and most programs die from it. Use nohup, disown, setsid, or a terminal multiplexer (tmux, screen) for work that must survive logout.

  • Using job specs in a script started from your interactive shell. %1 inside the script refers to the script's own jobs, not yours.

  • Relying on jobs output format in scripts. It is meant for humans; use jobs -p for PIDs and wait's exit status for results.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • jobs is a POSIX shell builtin, so -l and -p work in bash, zsh, dash and ksh alike. -r, -s, -n and -x are bash extensions (zsh has its own set).

Options worth knowing

A selection, described in our own words. For the full list, see bash(1).

OptionWhat it does
-lAlso show each job's process IDs.
-pPrint only the PID of each job's process group leader.
-nShow only jobs whose state changed since the last report.
-rShow only running jobs.
-sShow only stopped jobs.
-xRun a command with job specs in its arguments replaced by PIDs.

Related