Gemini on Question 14, Sep 12
I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?The most idiomatic and standard way to clean up orphaned child processes in Bash is to use the trap command to catch the EXIT signal, and then use jobs -p to kill any background processes.
Here are the best approaches, from the standard idiomatic way to more advanced solutions for complex process trees.
1. The Idiomatic Way (Direct Children)
If your script spawns standard background jobs (using &), the cleanest way is to trap EXIT. The EXIT pseudo-signal triggers when the script ends, whether it finishes naturally or is killed (e.g., by Ctrl+C or SIGTERM).
#!/bin/bash
# Define the cleanup function
cleanup() {
# Get PIDs of all running background jobs (-p gets PIDs, -r gets only running)
local child_pids=$(jobs -pr)
if [ -n "$child_pids" ]; then
echo "Purging orphaned children: $child_pids"
kill $child_pids 2>/dev/null
fi
}
# Trap the EXIT signal
trap cleanup EXIT
# --- Example Usage ---
sleep 100 &
sleep 200 &
echo "Parent doing work... Press Ctrl+C to kill parent."
sleep 5Why this is good:
- •
trap ... EXITcatches both normal script termination and most signals (likeSIGINT/SIGTERM). - •
jobs -prensures you only try to kill jobs that belong to the current shell and are still running. - •
2>/dev/nullsuppresses errors in case a child process dies naturally milliseconds before thekillcommand is issued (a common race condition).
2. The Process Group Way (Nested Children)
The method above only kills direct children. If your background process spawned its own children (grandchildren of your script), those grandchildren will survive and become orphans.
To kill the entire process tree under your script, you need to send a signal to the Process Group.
By default, Bash scripts usually share a process group with their parent terminal. To safely kill all children, you should start the script in its own process group using setsid, or enable Job Control (set -m).
Here is the Job Control method:
#!/bin/bash
# Enable monitor mode (Job Control).
# This forces every background process into its own process group.
set -m
cleanup() {
# Send SIGTERM to the process group (indicated by the negative sign) of every job
for pid in $(jobs -pr); do
kill -TERM "-$pid" 2>/dev/null
done
}
trap cleanup EXIT
# --- Example Usage ---
# This wrapper script will spawn its own children.
# Without `set -m` and `kill -$pid`, its children would survive.
bash -c 'sleep 100 & sleep 200 & wait' &
echo "Parent doing work... Press Ctrl+C."
sleep 53. The Linux Kernel Way (Bulletproof, Linux only)
Both methods above fail if the parent script is killed with SIGKILL (kill -9), because SIGKILL cannot be trapped.
If you absolutely must guarantee that a child dies when the parent dies (even on kill -9), you must rely on the Linux kernel using PR_SET_PDEATHSIG. Because pure Bash cannot make direct syscalls, you use the standard utility prlimit or bwrap if available, but a more common workaround in bash is wrapping the child in tail --pid:
#!/bin/bash
# Child process wrapper
# It waits for its parent ($$) to die, then kills the actual workload
run_child() {
local parent_pid=$1
# Do the actual work in the background
sleep 100 &
local worker_pid=$!
# Wait for the parent to die, then kill the worker
tail --pid=$parent_pid -f /dev/null
kill $worker_pid 2>/dev/null
}
# Pass the parent's PID ($$) to the child function
run_child $$ &
echo "Even if you 'kill -9 $$', the child will die."
sleep 60Summary of Best Practices
- 1.Use
trap '...' EXIT: It is much cleaner than trappingSIGINT,SIGTERM, etc., individually. - 2.Use
jobs -pr: It prevents your script from accidentally trying to kill system PIDs. - 3.Never use
kill 0blindly: You will see tutorials suggestingkill 0to kill the current process group. If your script wasn't launched with its own process group,kill 0will kill your terminal session or SSH connection!
Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
- Scored
- answered
- Received
- 2026-09-12 09:07:10 UTC
- Run
- 20260912T090021Z-c0c86e
- Served by
- Google · HTTP 200 · finish stop
- Size
- 4,101 characters · 2,702 tokens out incl. hidden reasoning · 23.9 s
End of response · 106 lines · 4,101 characters · sha256 434c4e3ef8d943839f11cdeb6ab2d7ea5c5c4b529f5b862900931258d5f65e52