wait, Job Coordination, and Exit Status
Launching work in the background is easy. Correctly joining it back into the parent control flow is the real engineering task. `wait` is how asynchronous success and failure become visible again.
Learning objectives
By the end of this lesson
- Wait for specific background jobs.
- Capture exact asynchronous exit status.
- Track multiple PIDs safely.
- Associate PIDs with task identity.
- Define aggregate failure behavior.
1. wait joins asynchronous work back into control flow
sleep 2 &
pid=$!
printf 'doing other work\n'
wait "$pid"
printf 'child complete\n'wait blocks until the selected child completes and returns that child's status.
2. Preserve the exact child exit status
bash -c 'sleep 1; exit 7' &
pid=$!
if wait "$pid"; then
status=0
else
status=$?
fi
printf 'status=%d\n' "$status"3. Store many PIDs in an array
pids=()
for delay in 1 2 3; do
sleep "$delay" &
pids+=("$!")
done
for pid in "${pids[@]}"; do
wait "$pid"
done4. Associate PIDs with task names
declare -A name_by_pid
run_task api &
name_by_pid[$!]=api
run_task worker &
name_by_pid[$!]=worker
for pid in "${!name_by_pid[@]}"; do
name=${name_by_pid[$pid]}
if wait "$pid"; then
printf '%s=ok\n' "$name"
else
status=$?
printf '%s=failed status=%d\n' "$name" "$status" >&2
fi
done5. Logical negation can hide the original status
# If exact status matters, prefer:
if wait "$pid"; then
status=0
else
status=$?
fiWith a construct such as if ! wait "$pid"; then ..., the logical negation changes the immediate status seen by $?. Use an explicit success/else branch when you need the child's exact code.
6. Bare wait waits for outstanding children
job_a &
job_b &
job_c &
waitThis is concise when you only care that all outstanding jobs finish. It is less useful when task identity and individual statuses matter.
7. wait -n handles whichever child finishes next
task_a &
task_b &
task_c &
for _ in 1 2 3; do
if wait -n; then
printf 'one child succeeded\n'
else
printf 'one child failed\n' >&2
fi
donewait -n is Bash-specific and version-dependent, but it is valuable for bounded worker pools.
8. Concurrency needs an aggregate failure policy
9. Early parent exit must account for active children
If the parent exits before waiting, background children may remain active. The next lesson uses traps and signals to define cleanup and cancellation behavior.
10. Hands-on lab: coordinate three jobs
mkdir -p "$HOME/devops-academy/bash/chapter10/lesson02"
cd "$HOME/devops-academy/bash/chapter10/lesson02"
run_job() {
local name=$1 delay=$2 result=$3
sleep "$delay"
printf '%s finished\n' "$name"
return "$result"
}
declare -A name_by_pid
pids=()
run_job api 2 0 &
pids+=("$!")
name_by_pid[${pids[-1]}]=api
run_job worker 1 4 &
pids+=("$!")
name_by_pid[${pids[-1]}]=worker
run_job cache 3 0 &
pids+=("$!")
name_by_pid[${pids[-1]}]=cache
failed=0
for pid in "${pids[@]}"; do
name=${name_by_pid[$pid]}
if wait "$pid"; then
printf 'RESULT %s=ok\n' "$name"
else
status=$?
printf 'RESULT %s=failed status=%d\n' "$name" "$status" >&2
((failed += 1))
fi
done
printf 'failed_jobs=%d\n' "$failed"
(( failed == 0 )) || exit 1Verification checklist
11. Knowledge check
Question 1. What does wait PID return?
Question 2. Why store PIDs?
Question 3. When is bare wait not enough?
Question 4. Name three aggregate policies.
12. Summary
Background execution becomes reliable only when the parent tracks owned PIDs, waits for them, preserves their statuses, and applies an explicit aggregate failure policy.
13. Further reading
- GNU Bash Reference Manual —
waitand Job Control Builtins. - GNU Bash Reference Manual — Arrays.
- POSIX wait utility.
- Bash documentation for
wait -n.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.