Launching and Tracking Background Jobs
Appending & to a command launches it in the background, immediately returning control to the shell while the job runs asynchronously; bash tracks each backgrounded process in a job table accessible via the jobs builtin, which lists jobs with an index like [1] and their state (Running, Stopped, Done). The special variable $! holds the PID of the most recently backgrounded process, which is essential for later checking on it, waiting for it, or sending it a signal with kill.
Cricket analogy: Sending a specialist fielder to warm up in the outfield while the main game continues is like backgrounding a job with &: the main shell (batting side) keeps going while that process runs independently, tracked on the team sheet like bash tracks it in the jobs table.
#!/usr/bin/env bash
# Launch three downloads in parallel
curl -sO https://example.com/file1.tar.gz &
pid1=$!
curl -sO https://example.com/file2.tar.gz &
pid2=$!
curl -sO https://example.com/file3.tar.gz &
pid3=$!
jobs -l # show job table with PIDs
# Wait for all three, capturing individual exit codes
fail=0
for pid in "$pid1" "$pid2" "$pid3"; do
if ! wait "$pid"; then
echo "Job with PID $pid failed" >&2
fail=1
fi
done
exit "$fail"Job Control: fg, bg, and Signals
In an interactive shell, Ctrl-Z suspends the foreground job by sending SIGTSTP, moving it into a Stopped state visible in jobs; bg %1 resumes job 1 in the background while fg %1 brings it back to the foreground, and %+/%- refer to the most recent and second-most-recent jobs respectively. Sending signals to a specific job uses kill %1 or kill -TERM $pid, and wait without arguments blocks until every background job the shell knows about finishes, while wait -n (bash 4.3+) returns as soon as any single one completes.
Cricket analogy: Ctrl-Z is like a captain calling for an unofficial drinks break mid-over, temporarily pausing play (Stopped state), while bg is like resuming play at a relaxed net-practice pace off to the side, and fg is bringing that same match back to the main center-wicket action.
Use disown to remove a job from the shell's job table without killing it, useful when you want a background process to survive the shell exiting (SIGHUP won't be sent to it). Combine with nohup command & or setsid command & for processes that must truly outlive the terminal session.
wait and Collecting Exit Statuses
wait $pid returns the exit status of that specific process, which is the standard way to know whether a parallel background task actually succeeded rather than merely finished; calling wait with no arguments waits for all currently tracked background jobs and always returns 0, so per-job status must be captured individually if failure detection matters. In bash 4.3+, wait -n blocks until the next background job completes and returns its exit status, which combined with a loop lets you build a simple worker pool that reacts to jobs finishing in any order rather than a fixed sequence.
Cricket analogy: Checking each specialist bowler's individual figures at the end of an innings (wait $pid) tells you exactly who took wickets and who was expensive, unlike just checking that the innings ended (bare wait), which tells you nothing about individual performance.
wait with no arguments always returns 0 even if every backgrounded job failed — it only reports whether the wait itself succeeded, not the jobs' outcomes. Always capture PIDs with $! and wait them individually (or use wait -n in a loop) if you need to detect partial failures in a parallel batch.
command &backgrounds a process immediately, returning control to the shell and setting$!to its PID.jobs -llists tracked background jobs with their PIDs and states (Running, Stopped, Done).- Ctrl-Z sends SIGTSTP to suspend the foreground job;
bgresumes it in the background,fgbrings it back. wait $pidreturns that job's actual exit status; barewaitalways returns 0 regardless of job outcomes.wait -n(bash 4.3+) waits for the next job to finish, enabling simple as-completed worker pool patterns.disownremoves a job from the job table so it survives shell exit without receiving SIGHUP.- Use
nohuporsetsidfor background processes that must outlive the terminal session entirely.
Practice what you learned
1. What does `$!` contain immediately after `long_task &`?
2. Why does `wait` with no arguments fail to detect that a background job actually failed?
3. What signal does Ctrl-Z send to the foreground job, and what state does it enter?
4. What does `disown` accomplish that plain backgrounding with `&` does not?
Was this page helpful?
You May Also Like
Subshells and Command Grouping
Learn how bash isolates execution with subshells using parentheses versus grouping commands in the current shell with braces, and why the distinction matters for variables, cd, and exit status.
Parallel Execution with xargs
Use xargs's -P flag and related options to run commands across many inputs concurrently, controlling batching, argument placement, and error handling.
trap and Signal Handling
Master bash's `trap` builtin to intercept signals like SIGINT and EXIT, enabling reliable cleanup, graceful shutdown, and robust error handling in scripts.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics