opscape

Processes and jobs · bash builtin

wait

Wait for background jobs of the current shell to finish, and return their exit status.

wait [-fn] [-p VARNAME] [ID...]

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

wait is how a script runs things in parallel and then joins them. Start each job with &, remember $! if you need its result, and wait blocks until the jobs are done. With no arguments it waits for all background children and returns 0. With a PID or job spec (%1) it waits for that one and returns its exit status, which is the only way to learn whether a background job succeeded.

wait -n (bash 4.3+) returns as soon as any job finishes, with that job's status; -p VAR (bash 5.1+) also stores which PID it was. Together they let a script react to jobs in the order they finish, or keep a fixed number of workers busy.

wait only knows the shell's own children. It cannot wait for a process started by another shell or for a grandchild; for those, poll with kill -0 PID or use tail --pid=PID -f /dev/null, which returns when that process exits.

Examples

Run jobs in parallel and wait for all of them

for n in 3 1 2; do
  (sleep "0.$n"; echo "job $n done") &
done
wait
echo "all finished"
output
job 1 done
job 2 done
job 3 done
all finished

The three jobs run at the same time and finish in order of their delay, not their start. wait with no arguments returns once all are done, after about 0.3 seconds in total instead of 0.6.

Get a background job's exit status

(sleep 0.2; exit 3) &
pid=$!
echo "started $pid"
wait "$pid"
echo "job exited with $?"
output
started 3851
job exited with 3

$! is the PID of the last background job. wait PID returns that job's exit status, so the script can tell success from failure exactly as for a foreground command.

Fail if any parallel job failed

pids=()
for f in data/users.csv data/missing.csv data/sales.csv; do
  wc -l "$f" > /dev/null 2>&1 &
  pids+=($!)
done
failed=0
for pid in "${pids[@]}"; do
  wait "$pid" || failed=$((failed + 1))
done
echo "$failed job(s) failed"
output
1 job(s) failed

Plain wait returns 0 even if jobs failed. Waiting for each PID in turn collects every status; one of the three wc runs failed on the missing file.

React to jobs in the order they finish

for n in 3 1 2; do (sleep "0.$n"; exit "$n") & done
while :; do
  wait -n -p pid; rc=$?
  [ -n "$pid" ] || break
  echo "PID $pid finished with status $rc"
done
output
PID 3859 finished with status 1
PID 3860 finished with status 2
PID 3858 finished with status 3

wait -n returns when the next job finishes and -p pid records which one. Once no jobs are left it returns 127 and leaves pid empty, which ends the loop. Needs bash 5.1 for -p.

Keep at most N jobs running at once

for i in 1 2 3 4 5 6; do
  while [ "$(jobs -rp | wc -l)" -ge 2 ]; do wait -n; done
  (sleep 0.2; echo "$(date +%S.%N | cut -c1-5) task $i done") &
done
wait
output
54.52 task 1 done
54.52 task 2 done
54.73 task 3 done
54.73 task 4 done
54.93 task 5 done
54.94 task 6 done

Before starting each task, the loop waits while two are already running. The timestamps show the tasks completing in pairs. For bigger jobs, xargs -P N does the same with less code.

Wait for a job by job number

sleep 0.2 &
(sleep 0.1; exit 4) &
wait %2
echo "job 2: $?"
wait %1
echo "job 1: $?"
output
job 2: 4
job 1: 0

Job specs from jobs work as well as PIDs. Each wait returns that job's own status.

Status of a job killed by a signal

sleep 300 &
kill $!
wait $!
echo "exit status: $?"
output
exit status: 143

A job that died from a signal reports 128 plus the signal number: 143 for TERM. kill -l 143 translates it back to the name.

wait only works for the shell's own children

wait 1
echo "exit status: $?"
output
/bin/bash: line 1: wait: pid 1 is not a child of this shell
exit status: 127

PID 1 is not a child of this shell, so bash says pid 1 is not a child of this shell and returns 127. For processes started elsewhere, poll with kill -0 or use GNU tail --pid=PID -f /dev/null, which exits once that PID is gone.

Common pitfalls

  • Relying on wait with no arguments to report failures. It always returns 0 once everything finished; wait for each PID if the results matter.

  • Waiting on PIDs from another shell or a pipeline's subshell. wait only knows direct children of the current shell (pid N is not a child of this shell).

  • Starting jobs inside cmd | while read ...; do job & done: the loop body runs in a subshell, so the wait after the pipeline has no jobs to wait for. Use while read ...; done < file or process substitution.

  • Forgetting to wait at the end of a script. The script exits while background jobs are still running, and their output appears after the prompt.

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).

  • Plain wait and wait PID are POSIX and work in every shell. -n needs bash 4.3 (macOS's bundled bash 3.2 lacks it), and -p and -f need bash 5.1 and 5.0. zsh has its own variants.

  • tail --pid is GNU-only; on macOS, loop with while kill -0 "$pid" 2>/dev/null; do sleep 1; done.

Options worth knowing

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

OptionWhat it does
-nWait for the next job to finish and return its status.
-p VARNAMEStore the PID or job ID whose status is returned in VARNAME (bash 5.1+).
-fWith job control on, wait until the job actually terminates, not just changes state.

Related