MODELDRIFT.WATCH

Question 14 · Sep 26 · Day 17

Gemini on Question 14, Sep 26

I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?

In Bash, the most idiomatic way to clean up child processes when the parent script terminates is to use the trap command hooked to the EXIT pseudo-signal.

Depending on your process tree, there are three main ways to do this.


1. The Standard Way: jobs -p (Best for direct children)

If your script spawns background tasks using &, Bash keeps track of them in its internal jobs list. You can kill them all cleanly using jobs -p.

#!/bin/bash

cleanup() {
    echo "Cleaning up..."
    # Kill all background jobs spawned by this script
    kill $(jobs -p) 2>/dev/null
}

# Trigger the cleanup function when the script exits (normally or via signal)
trap cleanup EXIT

# Example workload
sleep 100 &
sleep 200 &

echo "Doing some work. Press Ctrl+C to exit."
sleep 10
  • •Pros: Extremely safe. It only targets direct children spawned by this specific script.
  • •Cons: It does not kill grandchildren. If your background process spawns its own children, those will become orphaned.

2. The Unix Way: Process Groups (Best for deep process trees)

If your background jobs spawn their own children (grandchildren of your script) and you want to obliterate the entire process tree, you should signal the Process Group.

By default, sending a signal to 0 sends it to the entire process group the script belongs to.

#!/bin/bash

# Ignore SIGTERM in the parent so the script doesn't kill itself before finishing cleanup
trap '' TERM

cleanup() {
    echo "Killing entire process group..."
    # kill 0 sends SIGTERM to all processes in the current process group
    kill 0 2>/dev/null
}

trap cleanup EXIT

# Example workload
./some_script_that_spawns_other_scripts.sh &
sleep 100 &

wait
  • •Pros: Kills children, grandchildren, and great-grandchildren.
  • •Cons: Never source this script. If you run . ./script.sh in your terminal, the terminal shares the process group, and kill 0 will close your terminal. Always execute it as ./script.sh.

3. The Granular Way: Tracking PIDs (Best for selective killing)

If you only want to kill specific children and leave others running, you should save their Process IDs ($!) into an array immediately after spawning them.

#!/bin/bash

# Array to hold PIDs
declare -a PIDS=()

cleanup() {
    echo "Cleaning up specific children..."
    for pid in "${PIDS[@]}"; do
        # Check if process is still running, then kill it
        if kill -0 "$pid" 2>/dev/null; then
            kill "$pid"
        fi
    done
}

trap cleanup EXIT

# Spawn a child we WANT to kill on exit
sleep 100 &
PIDS+=($!)

# Spawn a child we want to SURVIVE the exit
nohup sleep 300 >/dev/null 2>&1 &

wait "${PIDS[@]}"

⚠️ The SIGKILL Caveat (Important)

It is crucial to understand that Bash cannot trap SIGKILL (kill -9).

If the user, the system administrator, or the Linux OOM (Out Of Memory) killer sends a SIGKILL to your parent script, the EXIT trap will not run, and your children will be orphaned.

If you absolutely must guarantee children die even if the parent gets SIGKILL, Bash alone isn't enough. You have two options:

  • 1.Linux prctl: Wrap your script in a tool like pdeath or write a tiny C/Python wrapper that sets PR_SET_PDEATHSIG, which tells the kernel to kill children when the parent thread dies.
  • 2.Systemd: Run your script as a systemd service. Systemd manages processes via Control Groups (cgroups) and will flawlessly tear down all orphaned grandchildren when the main service dies.

sha256 daeb2f1b015a… · 24 tokens in · 2,520 out · 21.1 s · via Google · stopped on its own

Sep 26, the whole dayQuestion 14, every day

Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
Model
google/gemini-3.1-pro-preview
pinned google/gemini-3.1-pro-preview-20260219; alias resolved to google/gemini-3.1-pro-preview-20260219 at 2026-09-26 09:00:06 UTC (matches the pin)
Date
2026-09-26 · run 20260926T090007Z-89c0b7 · scorer v1
Scored
answered
sha256
daeb2f1b015a8a8c629dc4acd8c77a3cd5671078bd4c4bf7843a2bae63d14398
Permalink
https://modeldrift.watch/r/2026-09-26/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-26 09:06:47 UTC
Run
20260926T090007Z-89c0b7
Served by
Google · HTTP 200 · finish stop
Size
3,576 characters · 2,520 tokens out incl. hidden reasoning · 21.1 s
In Bash, the most idiomatic way to clean up child processes when the parent script terminates is to use the **`trap`** command hooked to the **`EXIT`** pseudo-signal.
Depending on your process tree, there are three main ways to do this.
---
### 1. The Standard Way: `jobs -p` (Best for direct children)
If your script spawns background tasks using `&`, Bash keeps track of them in its internal jobs list. You can kill them all cleanly using `jobs -p`.
```bash
#!/bin/bash
cleanup() {
echo "Cleaning up..."
# Kill all background jobs spawned by this script
kill $(jobs -p) 2>/dev/null
}
# Trigger the cleanup function when the script exits (normally or via signal)
trap cleanup EXIT
# Example workload
sleep 100 &
sleep 200 &
echo "Doing some work. Press Ctrl+C to exit."
sleep 10
```
* **Pros:** Extremely safe. It only targets direct children spawned by this specific script.
* **Cons:** It **does not** kill grandchildren. If your background process spawns its own children, those will become orphaned.
---
### 2. The Unix Way: Process Groups (Best for deep process trees)
If your background jobs spawn their own children (grandchildren of your script) and you want to obliterate the entire process tree, you should signal the **Process Group**.
By default, sending a signal to `0` sends it to the entire process group the script belongs to.
```bash
#!/bin/bash
# Ignore SIGTERM in the parent so the script doesn't kill itself before finishing cleanup
trap '' TERM
cleanup() {
echo "Killing entire process group..."
# kill 0 sends SIGTERM to all processes in the current process group
kill 0 2>/dev/null
}
trap cleanup EXIT
# Example workload
./some_script_that_spawns_other_scripts.sh &
sleep 100 &
wait
```
* **Pros:** Kills children, grandchildren, and great-grandchildren.
* **Cons:** **Never `source` this script.** If you run `. ./script.sh` in your terminal, the terminal shares the process group, and `kill 0` will close your terminal. Always execute it as `./script.sh`.
---
### 3. The Granular Way: Tracking PIDs (Best for selective killing)
If you only want to kill *specific* children and leave others running, you should save their Process IDs (`$!`) into an array immediately after spawning them.
```bash
#!/bin/bash
# Array to hold PIDs
declare -a PIDS=()
cleanup() {
echo "Cleaning up specific children..."
for pid in "${PIDS[@]}"; do
# Check if process is still running, then kill it
if kill -0 "$pid" 2>/dev/null; then
kill "$pid"
fi
done
}
trap cleanup EXIT
# Spawn a child we WANT to kill on exit
sleep 100 &
PIDS+=($!)
# Spawn a child we want to SURVIVE the exit
nohup sleep 300 >/dev/null 2>&1 &
wait "${PIDS[@]}"
```
---
### ⚠️ The `SIGKILL` Caveat (Important)
It is crucial to understand that **Bash cannot trap `SIGKILL` (`kill -9`)**.
If the user, the system administrator, or the Linux OOM (Out Of Memory) killer sends a `SIGKILL` to your parent script, the `EXIT` trap will **not** run, and your children will be orphaned.
If you absolutely must guarantee children die even if the parent gets `SIGKILL`, Bash alone isn't enough. You have two options:
1. **Linux `prctl`:** Wrap your script in a tool like `pdeath` or write a tiny C/Python wrapper that sets `PR_SET_PDEATHSIG`, which tells the kernel to kill children when the parent thread dies.
2. **Systemd:** Run your script as a `systemd` service. Systemd manages processes via Control Groups (cgroups) and will flawlessly tear down all orphaned grandchildren when the main service dies.

End of response · 104 lines · 3,576 characters · sha256 daeb2f1b015a8a8c629dc4acd8c77a3cd5671078bd4c4bf7843a2bae63d14398