MODELDRIFT.WATCH

Question 14 · Sep 20 · Day 11

Gemini on Question 14, Sep 20

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

The most idiomatic and reliable way to handle this in a Bash script is using the trap command hooked to the EXIT pseudo-signal, combined with jobs -p.

Here is the standard, best-practice pattern:

#!/bin/bash

# Define a cleanup function
cleanup() {
    echo "Parent dying, cleaning up children..."
    
    # Get PIDs of all background jobs started by this script
    local pids=$(jobs -p)
    
    if [[ -n "$pids" ]]; then
        # Send SIGTERM to all children, suppress errors if they already died
        kill $pids 2>/dev/null
    fi
}

# Trap the EXIT signal to run the cleanup function
trap cleanup EXIT

# --- Your script logic below ---

# Example child processes
sleep 100 &
sleep 200 &

echo "Parent doing work... (Press Ctrl+C to test)"
sleep 10

Why this is idiomatic:

  • 1.trap ... EXIT: In Bash, the EXIT trap triggers when the script ends naturally, but it also triggers if the script is killed via SIGINT (Ctrl+C) or SIGTERM (a standard kill).
  • 2.jobs -p: This safely prints the Process IDs of all background jobs currently managed by this specific shell instance.
  • 3.2>/dev/null: If a child process happens to finish right before the script exits, kill will complain that the process doesn't exist. This suppresses that harmless error.

Advanced Edge Cases

Depending on your needs, the standard idiom above might have a few blind spots. Here is how to address them:

1. What if your children spawn their own children (Grandchildren)?

jobs -p only tracks direct children. If your script launches a Python script in the background, and that Python script launches a subprocess, the grandchild will be orphaned.

To kill the entire process tree, you can kill the Process Group. By default, scripts run in the same process group as the terminal that spawned them, so you must explicitly put your background process in its own group using set -m (monitor mode):

#!/bin/bash
set -m # Enable job control so background jobs get their own process group

cleanup() {
    local pids=$(jobs -p)
    if [[ -n "$pids" ]]; then
        # The negative sign (-) tells kill to target the whole process group
        # This kills the child and any grandchildren it spawned
        kill -- -${pids//$'\n'/ -} 2>/dev/null
    fi
}
trap cleanup EXIT

# ...

2. The SIGKILL (kill -9) problem

No script can trap SIGKILL (kill -9). If your parent script is killed with -9, the EXIT trap will not run, and the children will be orphaned.

If absolute guarantee against orphaning is required, you must rely on Linux kernel features rather than Bash features. The standard modern way to do this is to run your script as a Systemd transient service:

# Instead of running: ./myscript.sh
# Run it like this:
systemd-run --user --scope ./myscript.sh

When you do this, Linux places the script and all its children/grandchildren into a cgroup. If the parent dies (even by kill -9), systemd will mercilessly purge every child process in that cgroup.

sha256 23a0a55e8d58… · 25 tokens in · 2,268 out · 32.1 s · via Google AI Studio · stopped on its own

Sep 20, 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-20 09:00:19 UTC (matches the pin)
Date
2026-09-20 · run 20260920T090019Z-bbedf3 · scorer v1
Scored
answered
sha256
23a0a55e8d5834a93425cd63a3491e2c55354d199ce31714368effddbec8d7c1
Permalink
https://modeldrift.watch/r/2026-09-20/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-20 09:07:33 UTC
Run
20260920T090019Z-bbedf3
Served by
Google AI Studio · HTTP 200 · finish stop
Size
3,061 characters · 2,268 tokens out incl. hidden reasoning · 32.1 s
The most idiomatic and reliable way to handle this in a Bash script is using the `trap` command hooked to the `EXIT` pseudo-signal, combined with `jobs -p`.
Here is the standard, best-practice pattern:
```bash
#!/bin/bash
# Define a cleanup function
cleanup() {
echo "Parent dying, cleaning up children..."
# Get PIDs of all background jobs started by this script
local pids=$(jobs -p)
if [[ -n "$pids" ]]; then
# Send SIGTERM to all children, suppress errors if they already died
kill $pids 2>/dev/null
fi
}
# Trap the EXIT signal to run the cleanup function
trap cleanup EXIT
# --- Your script logic below ---
# Example child processes
sleep 100 &
sleep 200 &
echo "Parent doing work... (Press Ctrl+C to test)"
sleep 10
```
### Why this is idiomatic:
1. **`trap ... EXIT`**: In Bash, the `EXIT` trap triggers when the script ends naturally, but it *also* triggers if the script is killed via `SIGINT` (Ctrl+C) or `SIGTERM` (a standard `kill`).
2. **`jobs -p`**: This safely prints the Process IDs of all background jobs currently managed by this specific shell instance.
3. **`2>/dev/null`**: If a child process happens to finish right before the script exits, `kill` will complain that the process doesn't exist. This suppresses that harmless error.
---
### Advanced Edge Cases
Depending on your needs, the standard idiom above might have a few blind spots. Here is how to address them:
#### 1. What if your children spawn their own children (Grandchildren)?
`jobs -p` only tracks direct children. If your script launches a Python script in the background, and that Python script launches a subprocess, the grandchild will be orphaned.
To kill the entire process tree, you can kill the **Process Group**. By default, scripts run in the same process group as the terminal that spawned them, so you must explicitly put your background process in its own group using `set -m` (monitor mode):
```bash
#!/bin/bash
set -m # Enable job control so background jobs get their own process group
cleanup() {
local pids=$(jobs -p)
if [[ -n "$pids" ]]; then
# The negative sign (-) tells kill to target the whole process group
# This kills the child and any grandchildren it spawned
kill -- -${pids//$'\n'/ -} 2>/dev/null
fi
}
trap cleanup EXIT
# ...
```
#### 2. The `SIGKILL` (kill -9) problem
No script can trap `SIGKILL` (`kill -9`). If your parent script is killed with `-9`, the `EXIT` trap will **not** run, and the children will be orphaned.
If absolute guarantee against orphaning is required, you must rely on Linux kernel features rather than Bash features. The standard modern way to do this is to run your script as a **Systemd transient service**:
```bash
# Instead of running: ./myscript.sh
# Run it like this:
systemd-run --user --scope ./myscript.sh
```
When you do this, Linux places the script and all its children/grandchildren into a cgroup. If the parent dies (even by `kill -9`), systemd will mercilessly purge every child process in that cgroup.

End of response · 77 lines · 3,061 characters · sha256 23a0a55e8d5834a93425cd63a3491e2c55354d199ce31714368effddbec8d7c1