Claude on Question 14, Sep 17
I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?For most scripts, the idiomatic approach is trap + process-group kill. The key insight is that killing a negative PID kills the whole process group, which catches grandchildren too — something explicit PID tracking misses.
Basic pattern
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
trap - EXIT INT TERM # avoid re-entry
kill -- -$$ 2>/dev/null || true
}
trap cleanup EXIT INT TERM
sleep 300 &
some_daemon &
waitMake sure you're actually a process-group leader
kill -- -$$ only does the right thing if your PID is the PGID. That's true when launched from an interactive shell (job control gives each job its own group), but not when your script is invoked from another script — you'd inherit the caller's group and kill your parent's siblings.
Guard it:
# Re-exec into our own process group if we aren't the leader if [[ "$$" != "$(ps -o pgid= -p $$ | tr -d ' ')" ]]; then exec setsid --wait "$0" "$@" fi
Or, if you don't want a new session, enable job control so background jobs land in their own groups and kill them individually:
set -m long_task & task_pgid=$! kill -- -"$task_pgid"
Explicit PID tracking (portable, but shallower)
pids=()
start() { "$@" & pids+=("$!"); }
cleanup() {
kill "${pids[@]}" 2>/dev/null || true
wait "${pids[@]}" 2>/dev/null || true
}
trap cleanup EXIT INT TERMThis is fine when children don't fork their own subprocesses. It won't reap grandchildren.
The gap: traps don't run on SIGKILL
If your script is kill -9'd or the box OOM-kills it, no trap fires and the children genuinely orphan (reparented to PID 1). If that's the scenario you're defending against, you need the kernel to do it:
# util-linux 2.33+ setpriv --pdeathsig TERM my_child &
PR_SET_PDEATHSIG makes the kernel signal the child when its parent thread exits, regardless of how. Caveat: it fires when the parent thread dies, and it's cleared across setuid execs.
For a heavier-duty guarantee, put the whole tree in a cgroup and let the supervisor tear it down:
systemd-run --scope --unit=myjob-$$ ./my_script.sh # systemctl stop myjob-$$ # kills everything in the scope
Gotchas worth knowing
- •Reap after killing. Follow
killwithwaitso you don't leave zombies while your script is still alive. - •
kill 0is shorthand for "my process group" and avoids the$$/PGID mismatch question, but it's equally dangerous if you're not the leader. - •Escalate, don't assume.
kill -TERM,sleep 2, thenkill -KILLfor stragglers. - •
trap ... EXITalone is not enough — addINTandTERM, and note thatERRunderset -eexits throughEXITanyway.
If your script is mostly spawning a handful of known background jobs, use the trap + kill -- -$$ version with the PG-leader guard. If children fork trees of their own, or you need survival across SIGKILL, go with setpriv --pdeathsig or a cgroup scope.
Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
- Scored
- answered
- Received
- 2026-09-17 09:07:22 UTC
- Run
- 20260917T090022Z-136fc3
- Served by
- Claude Platform on AWS · HTTP 200 · finish stop
- Size
- 2,984 characters · 2,323 tokens out incl. hidden reasoning · 32.7 s
End of response · 81 lines · 2,984 characters · sha256 8b4eba5f2828d4a8661cf56ffb08ba6817add71afd12b52ccf4ca3e6784a221e