MODELDRIFT.WATCH

Question 14 · Sep 11 · Day 2

Gemini on Question 14, Sep 11

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 to use the trap command combined with the EXIT pseudo-signal.

Here are the standard approaches, ranging from simple child processes to complex process trees.

1. The Standard Idiomatic Way (Direct Children)

If your script spawns background tasks and you want them to die when the script exits (normally, errors out, or receives Ctrl+C), trap the EXIT signal and kill the background jobs.

#!/bin/bash

# Define a cleanup function
cleanup() {
    # jobs -p lists the PIDs of all background jobs started by this script
    local pids=$(jobs -p)
    if [[ -n "$pids" ]]; then
        echo "Purging orphaned children..."
        kill $pids 2>/dev/null
    fi
}

# Trap the EXIT signal
trap cleanup EXIT

# --- Your script logic below ---
sleep 100 &  # Child 1
sleep 200 &  # Child 2

# Wait for children to finish (or script gets killed)
wait

Why this works: The EXIT trap in bash is special. It fires when the script finishes naturally, but it also fires if the script receives standard termination signals like SIGINT (Ctrl+C) or SIGTERM (standard kill).


2. The "Process Group" Way (Children of Children)

If your child processes spawn their own child processes, jobs -p isn't enough because it only knows about direct children. You need to kill the entire process group.

You can do this by sending a signal to 0 (which targets the current process group) or to -$$ (the negative PID of the current shell, which denotes its process group).

#!/bin/bash

cleanup() {
    echo "Purging entire process group..."
    # 1. Ignore the TERM signal so the parent doesn't kill itself recursively
    trap '' TERM 
    # 2. Send SIGTERM to the entire process group
    kill -TERM 0 2>/dev/null
    # Optional: wait a moment and send SIGKILL if they are stubborn
    # sleep 1; kill -KILL 0 2>/dev/null
}

trap cleanup EXIT

# --- Your script logic below ---
# Even if this script spawns children that spawn more children, 
# they will all be purged.
./some_complex_subscript.sh &

wait

3. The Uncatchable Exception: kill -9

Crucial Caveat: Bash scripts cannot catch SIGKILL (kill -9). If someone runs kill -9 <parent_pid>, the parent dies instantly, the trap never fires, and the children become orphans (re-parented to PID 1).

If you must guarantee that children die even if the parent receives kill -9, Bash alone cannot do it. You must rely on OS-level features:

  • •Linux prctl (PR_SET_PDEATHSIG): This tells the Linux kernel to send a signal to a child when its parent dies. You can't do this purely in Bash, but you can use a small wrapper. If you have Python installed, you can wrap your child process like this:
    ```bash
    python3 -c "import ctypes, os, sys; libc = ctypes.CDLL(None); libc.prctl(1, 9); os.execvp(sys.argv[1], sys.argv[1:])" my_child_command &
    ```
  • •systemd: If this is a daemon/service, let systemd manage it. By default, when a systemd service stops or is killed, it uses cgroups to ruthlessly terminate every child process associated with it.

Summary Recommendation

For 95% of use cases, Method 1 (trap cleanup EXIT with jobs -p) is exactly what you want. It is clean, POSIX-compliant, highly readable, and firmly accepted as bash idiom.

sha256 da612f10b86d… · 24 tokens in · 2,339 out · 20.5 s · via Google · stopped on its own

Sep 11, 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-11 09:00:01 UTC (matches the pin)
Date
2026-09-11 · run 20260911T090002Z-da0dd3 · scorer v1
Scored
answered
sha256
da612f10b86d7a64ab579fae1b9f864682c859ed99d5c6df623f87e7a697204a
Permalink
https://modeldrift.watch/r/2026-09-11/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-11 09:09:33 UTC
Run
20260911T090002Z-da0dd3
Served by
Google · HTTP 200 · finish stop
Size
3,348 characters · 2,339 tokens out incl. hidden reasoning · 20.5 s
The idiomatic way to handle this in Bash is to use the **`trap`** command combined with the **`EXIT`** pseudo-signal.
Here are the standard approaches, ranging from simple child processes to complex process trees.
### 1. The Standard Idiomatic Way (Direct Children)
If your script spawns background tasks and you want them to die when the script exits (normally, errors out, or receives `Ctrl+C`), trap the `EXIT` signal and kill the background jobs.
```bash
#!/bin/bash
# Define a cleanup function
cleanup() {
# jobs -p lists the PIDs of all background jobs started by this script
local pids=$(jobs -p)
if [[ -n "$pids" ]]; then
echo "Purging orphaned children..."
kill $pids 2>/dev/null
fi
}
# Trap the EXIT signal
trap cleanup EXIT
# --- Your script logic below ---
sleep 100 & # Child 1
sleep 200 & # Child 2
# Wait for children to finish (or script gets killed)
wait
```
**Why this works:** The `EXIT` trap in bash is special. It fires when the script finishes naturally, but it *also* fires if the script receives standard termination signals like `SIGINT` (Ctrl+C) or `SIGTERM` (standard `kill`).
---
### 2. The "Process Group" Way (Children of Children)
If your child processes spawn their *own* child processes, `jobs -p` isn't enough because it only knows about direct children. You need to kill the entire **process group**.
You can do this by sending a signal to `0` (which targets the current process group) or to `-$$` (the negative PID of the current shell, which denotes its process group).
```bash
#!/bin/bash
cleanup() {
echo "Purging entire process group..."
# 1. Ignore the TERM signal so the parent doesn't kill itself recursively
trap '' TERM
# 2. Send SIGTERM to the entire process group
kill -TERM 0 2>/dev/null
# Optional: wait a moment and send SIGKILL if they are stubborn
# sleep 1; kill -KILL 0 2>/dev/null
}
trap cleanup EXIT
# --- Your script logic below ---
# Even if this script spawns children that spawn more children,
# they will all be purged.
./some_complex_subscript.sh &
wait
```
---
### 3. The Uncatchable Exception: `kill -9`
**Crucial Caveat:** Bash scripts **cannot** catch `SIGKILL` (`kill -9`). If someone runs `kill -9 <parent_pid>`, the parent dies instantly, the `trap` never fires, and the children become orphans (re-parented to PID 1).
If you must guarantee that children die even if the parent receives `kill -9`, Bash alone cannot do it. You must rely on OS-level features:
* **Linux `prctl` (PR_SET_PDEATHSIG):** This tells the Linux kernel to send a signal to a child when its parent dies. You can't do this purely in Bash, but you can use a small wrapper. If you have Python installed, you can wrap your child process like this:
```bash
python3 -c "import ctypes, os, sys; libc = ctypes.CDLL(None); libc.prctl(1, 9); os.execvp(sys.argv[1], sys.argv[1:])" my_child_command &
```
* **systemd:** If this is a daemon/service, let `systemd` manage it. By default, when a `systemd` service stops or is killed, it uses cgroups to ruthlessly terminate every child process associated with it.
### Summary Recommendation
For 95% of use cases, **Method 1 (`trap cleanup EXIT` with `jobs -p`)** is exactly what you want. It is clean, POSIX-compliant, highly readable, and firmly accepted as bash idiom.

End of response · 77 lines · 3,348 characters · sha256 da612f10b86d7a64ab579fae1b9f864682c859ed99d5c6df623f87e7a697204a