MODELDRIFT.WATCH

Question 14 · Sep 23 · Day 14

Gemini on Question 14, Sep 23

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

The idiomatic way to handle this in Bash is using the trap command combined with the EXIT pseudo-signal.

When you set a trap on EXIT, Bash will execute your cleanup command whenever the script terminates—whether it finishes normally, errors out, or is terminated by a standard signal (like SIGINT/Ctrl+C or SIGTERM).

Here are the three most common and idiomatic approaches, depending on how aggressive you need the cleanup to be.

1. The Standard Way: Kill direct background jobs

If your script spawns background tasks using &, the most idiomatic way is to use jobs -p to get their Process IDs (PIDs) and kill them.

#!/bin/bash

# Set the trap to kill all background jobs when the script exits
trap 'kill $(jobs -p) 2>/dev/null' EXIT

echo "Starting background workers..."
sleep 100 &
sleep 200 &

echo "Doing some work in the parent..."
sleep 5

# When the script ends here (or if you press Ctrl+C), 
# the trap fires and kills the sleeping children.

Note: 2>/dev/null is used to suppress error messages in case the background jobs have already finished naturally before the script exits.

2. The "Nuke" Way: Kill the entire Process Group

The method above (jobs -p) only kills direct children. If your children spawn their own children (grandchildren), those grandchildren will still become orphaned.

To kill the script, its children, and its grandchildren, you send a signal to the entire process group using kill 0.

#!/bin/bash

# 1. Remove the trap so we don't trigger an infinite loop
# 2. Kill the entire process group (kill 0)
trap 'trap - EXIT; kill 0' EXIT

echo "Starting a child that spawns grandchildren..."
(sleep 100 & sleep 200) &

sleep 5

Warning: kill 0 sends a SIGTERM to every process in the same process group as the script. This is highly effective but should be used with care if your script is invoked by another script that shares the same process group.

3. The Surgical Way: Track specific PIDs

If you have background processes that you want to leave running, and others you want to kill, you should track the PIDs manually using $! (which holds the PID of the last backgrounded process).

#!/bin/bash

# Initialize an empty array for PIDs
CHILD_PIDS=()

# The cleanup function
cleanup() {
    for pid in "${CHILD_PIDS[@]}"; do
        kill "$pid" 2>/dev/null
    done
}

trap cleanup EXIT

# Start child 1 and record PID
sleep 100 &
CHILD_PIDS+=($!)

# Start child 2 and record PID
sleep 200 &
CHILD_PIDS+=($!)

sleep 5

The Unsolvable Edge Case: SIGKILL (kill -9)

It is important to know that Bash cannot trap SIGKILL (kill -9). If your parent script is killed with -9, the trap will not fire, and the children will be orphaned.

If you are on Linux and need 100% guarantees that children die even if the parent gets kill -9, Bash alone cannot do it. You have to rely on Linux kernel features. The most modern, robust way to handle this is to run your script via systemd.

By running your script using systemd-run, systemd places the script and all its children into a cgroup. If the script dies, systemd automatically slaughters everything left in that cgroup:

systemd-run --user --scope ./your_script.sh

sha256 e992804f48cc… · 24 tokens in · 2,192 out · 19.3 s · via Google · stopped on its own

Sep 23, 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-23 09:00:22 UTC (matches the pin)
Date
2026-09-23 · run 20260923T090022Z-9b7b3a · scorer v1
Scored
answered
sha256
e992804f48cca1235ff9f96c1a2ea525eb35b5f2c41a9a1657c6ff97c8d779ff
Permalink
https://modeldrift.watch/r/2026-09-23/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-23 09:07:55 UTC
Run
20260923T090022Z-9b7b3a
Served by
Google · HTTP 200 · finish stop
Size
3,280 characters · 2,192 tokens out incl. hidden reasoning · 19.3 s
The idiomatic way to handle this in Bash is using the **`trap`** command combined with the **`EXIT`** pseudo-signal.
When you set a trap on `EXIT`, Bash will execute your cleanup command whenever the script terminates—whether it finishes normally, errors out, or is terminated by a standard signal (like `SIGINT`/Ctrl+C or `SIGTERM`).
Here are the three most common and idiomatic approaches, depending on how aggressive you need the cleanup to be.
### 1. The Standard Way: Kill direct background jobs
If your script spawns background tasks using `&`, the most idiomatic way is to use `jobs -p` to get their Process IDs (PIDs) and kill them.
```bash
#!/bin/bash
# Set the trap to kill all background jobs when the script exits
trap 'kill $(jobs -p) 2>/dev/null' EXIT
echo "Starting background workers..."
sleep 100 &
sleep 200 &
echo "Doing some work in the parent..."
sleep 5
# When the script ends here (or if you press Ctrl+C),
# the trap fires and kills the sleeping children.
```
*Note: `2>/dev/null` is used to suppress error messages in case the background jobs have already finished naturally before the script exits.*
### 2. The "Nuke" Way: Kill the entire Process Group
The method above (`jobs -p`) only kills *direct* children. If your children spawn their own children (grandchildren), those grandchildren will still become orphaned.
To kill the script, its children, and its grandchildren, you send a signal to the entire **process group** using `kill 0`.
```bash
#!/bin/bash
# 1. Remove the trap so we don't trigger an infinite loop
# 2. Kill the entire process group (kill 0)
trap 'trap - EXIT; kill 0' EXIT
echo "Starting a child that spawns grandchildren..."
(sleep 100 & sleep 200) &
sleep 5
```
*Warning: `kill 0` sends a `SIGTERM` to every process in the same process group as the script. This is highly effective but should be used with care if your script is invoked by another script that shares the same process group.*
### 3. The Surgical Way: Track specific PIDs
If you have background processes that you *want* to leave running, and others you want to kill, you should track the PIDs manually using `$!` (which holds the PID of the last backgrounded process).
```bash
#!/bin/bash
# Initialize an empty array for PIDs
CHILD_PIDS=()
# The cleanup function
cleanup() {
for pid in "${CHILD_PIDS[@]}"; do
kill "$pid" 2>/dev/null
done
}
trap cleanup EXIT
# Start child 1 and record PID
sleep 100 &
CHILD_PIDS+=($!)
# Start child 2 and record PID
sleep 200 &
CHILD_PIDS+=($!)
sleep 5
```
### The Unsolvable Edge Case: `SIGKILL` (kill -9)
It is important to know that **Bash cannot trap `SIGKILL` (`kill -9`)**. If your parent script is killed with `-9`, the `trap` will not fire, and the children *will* be orphaned.
If you are on Linux and need 100% guarantees that children die even if the parent gets `kill -9`, Bash alone cannot do it. You have to rely on Linux kernel features. The most modern, robust way to handle this is to run your script via **systemd**.
By running your script using `systemd-run`, systemd places the script and all its children into a `cgroup`. If the script dies, systemd automatically slaughters everything left in that `cgroup`:
```bash
systemd-run --user --scope ./your_script.sh
```

End of response · 85 lines · 3,280 characters · sha256 e992804f48cca1235ff9f96c1a2ea525eb35b5f2c41a9a1657c6ff97c8d779ff