patternbashMajor
Background jobs management: wait, job IDs, and avoiding zombie processes
Viewed 0 times
background jobswaitparallelPIDzombieexit codejob controlfork
Problem
Scripts that launch background jobs with & must properly wait for them and collect exit codes. Without wait, the script exits leaving children running or uncollected (zombies), and errors go undetected.
Solution
Collect PIDs and use 'wait PID' to gather exit codes. Use job control carefully in non-interactive scripts.
pids=()
for item in "${items[@]}"; do
process "$item" &
pids+=("$!")
done
failed=0
for pid in "${pids[@]}"; do
wait "$pid" || (( failed++ ))
done
(( failed == 0 )) || { echo "$failed jobs failed" >&2; exit 1; }
pids=()
for item in "${items[@]}"; do
process "$item" &
pids+=("$!")
done
failed=0
for pid in "${pids[@]}"; do
wait "$pid" || (( failed++ ))
done
(( failed == 0 )) || { echo "$failed jobs failed" >&2; exit 1; }
Why
'wait' with no arguments waits for all children; 'wait PID' waits for a specific child and returns its exit code. Without waiting, the shell does not collect exit codes and background jobs may outlive the script.
Gotchas
- In scripts (non-interactive mode) job control is disabled by default; use 'set -m' to enable
- $! gives the PID of the most recently backgrounded command — save it immediately
- wait returns the exit status of the waited process — check it
- Backgrounded pipelines: only the PID of the last pipeline command is available via $!
- Use a SIGTERM trap to kill children when the script is killed: trap 'kill ${pids[*]}' TERM INT
Code Snippets
Background job management with error collection
#!/usr/bin/env bash
set -euo pipefail
process_item() { sleep 1; echo "done $1"; }
pids=()
for i in 1 2 3 4 5; do
process_item "$i" &
pids+=("$!")
done
failed=0
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
echo "Process $pid failed" >&2
(( failed++ )) || true
fi
done
(( failed == 0 )) || exit 1Context
Running multiple processes concurrently in bash scripts
Revisions (0)
No revisions yet.