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...]
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"
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 $?"
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"
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
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
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: $?"
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: $?"
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: $?"
/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
waitwith 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.
waitonly 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 thewaitafter the pipeline has no jobs to wait for. Usewhile read ...; done < fileor process substitution.Forgetting to
waitat 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
waitandwait PIDare POSIX and work in every shell.-nneeds bash 4.3 (macOS's bundled bash 3.2 lacks it), and-pand-fneed bash 5.1 and 5.0. zsh has its own variants.tail --pidis GNU-only; on macOS, loop withwhile 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).
| Option | What it does |
|---|---|
-n | Wait for the next job to finish and return its status. |
-p VARNAME | Store the PID or job ID whose status is returned in VARNAME (bash 5.1+). |
-f | With job control on, wait until the job actually terminates, not just changes state. |
Related
jobsList the background and stopped jobs of the current shell, with their job numbers and state.killSend a signal to processes by PID or job number; by default TERM, which asks a process to exit.sleepPause for a given amount of time, in seconds by default, with m, h and d suffixes and fractions allowed.timeoutRun a command with a time limit, and kill it if it is still running when the time is up.xargsRead items from standard input and run a command with them as arguments, in batches or in parallel.- Exit codesWhat exit statuses mean, how $? works, the reserved values 126, 127 and 128+N, PIPESTATUS, and how to choose codes for your own scripts.
- Subshells and command groupingThe difference between ( ... ) and { ...; }, what a subshell inherits and what it cannot change, which constructs create one, and what that costs.