MODELDRIFT.WATCH

Question 14 · Sep 21 · Day 12

Gemini on Question 14, Sep 21

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

The idiomatic way to clean up child processes when a Bash script exits (whether normally, due to an error, or from being killed) is to use the trap command hooked to the EXIT pseudo-signal.

Here are the three most standard ways to do this, depending on your exact needs.

1. The Safest Idiom: Kill all background jobs

If your script spawns background processes using &, you can use jobs -p to get their Process IDs (PIDs) and kill them. This is the most common and safest approach.

#!/bin/bash

# Set a 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 foreground work..."
sleep 2

# When the script ends (or you press Ctrl+C), the EXIT trap fires 
# and automatically cleans up the sleeping background jobs.
  • •Why it works: The EXIT trap fires upon normal script completion, exit commands, or when the script receives a terminating signal (like SIGINT from Ctrl+C or SIGTERM).
  • •Why 2>/dev/null: If the children have already finished naturally, kill will complain that the PID doesn't exist. This hides that harmless error.

2. The Selective Idiom: Track specific PIDs

If you only want to kill specific children (and leave others running), you should capture their PIDs using $! immediately after spawning them.

#!/bin/bash

# Initialize a variable to hold our specific child's PID
WORKER_PID=""

# Trap EXIT to kill only that specific PID
trap '[[ -n "$WORKER_PID" ]] && kill "$WORKER_PID" 2>/dev/null' EXIT

echo "Starting specific worker..."
sleep 100 &
WORKER_PID=$! # $! holds the PID of the last backgrounded process

echo "Doing work..."
sleep 2

3. The "Nuclear" Idiom: Kill the whole Process Group

Use case: If your child processes spawn their own child processes (grandchildren to your script), jobs -p will only kill the direct children, leaving the grandchildren orphaned.

To prevent this, you kill the entire process group. In Bash, sending a signal to -$$ (negative script PID) sends it to the script's process group.

#!/bin/bash

# Enable job control so the script runs in its own process group
set -m 

# Trap EXIT to kill the entire process group (children and grandchildren)
trap 'kill -- -$$ 2>/dev/null' EXIT

# A child that spawns another child (grandchild)
( sleep 100 & sleep 100 ) &

echo "Working..."
sleep 2

⚠️ Warning on Process Groups: If you run this script from a context where it *doesn't get its own process group (e.g., poorly configured CI pipelines), killing -$$ might inadvertently kill the parent terminal. Using set -m helps mitigate this by forcing the script to be a process group leader.*

Summary: Which should I use?

  • •Use Method 1 (kill $(jobs -p)) 95% of the time. It is standard, safe, and easily understood by other developers.
  • •Use Method 2 (Tracking $!) if your script spawns a mix of things and you only want to manage specific ones.
  • •Use Method 3 (kill -- -$$) only if you are experiencing issues with "grandchildren" processes surviving the death of your direct children.

sha256 342f9640d8fc… · 24 tokens in · 2,193 out · 18.4 s · via Google · stopped on its own

Sep 21, 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-21 09:00:20 UTC (matches the pin)
Date
2026-09-21 · run 20260921T090021Z-989a7c · scorer v1
Scored
answered
sha256
342f9640d8fc1667605171af90631cb3b0aa489e24868739c1a72323de53c335
Permalink
https://modeldrift.watch/r/2026-09-21/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-21 09:07:35 UTC
Run
20260921T090021Z-989a7c
Served by
Google · HTTP 200 · finish stop
Size
3,188 characters · 2,193 tokens out incl. hidden reasoning · 18.4 s
The idiomatic way to clean up child processes when a Bash script exits (whether normally, due to an error, or from being killed) is to use the **`trap`** command hooked to the **`EXIT`** pseudo-signal.
Here are the three most standard ways to do this, depending on your exact needs.
### 1. The Safest Idiom: Kill all background jobs
If your script spawns background processes using `&`, you can use `jobs -p` to get their Process IDs (PIDs) and kill them. This is the most common and safest approach.
```bash
#!/bin/bash
# Set a 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 foreground work..."
sleep 2
# When the script ends (or you press Ctrl+C), the EXIT trap fires
# and automatically cleans up the sleeping background jobs.
```
* **Why it works:** The `EXIT` trap fires upon normal script completion, `exit` commands, or when the script receives a terminating signal (like `SIGINT` from Ctrl+C or `SIGTERM`).
* **Why `2>/dev/null`:** If the children have already finished naturally, `kill` will complain that the PID doesn't exist. This hides that harmless error.
### 2. The Selective Idiom: Track specific PIDs
If you only want to kill *specific* children (and leave others running), you should capture their PIDs using `$!` immediately after spawning them.
```bash
#!/bin/bash
# Initialize a variable to hold our specific child's PID
WORKER_PID=""
# Trap EXIT to kill only that specific PID
trap '[[ -n "$WORKER_PID" ]] && kill "$WORKER_PID" 2>/dev/null' EXIT
echo "Starting specific worker..."
sleep 100 &
WORKER_PID=$! # $! holds the PID of the last backgrounded process
echo "Doing work..."
sleep 2
```
### 3. The "Nuclear" Idiom: Kill the whole Process Group
**Use case:** If your child processes spawn their *own* child processes (grandchildren to your script), `jobs -p` will only kill the direct children, leaving the grandchildren orphaned.
To prevent this, you kill the entire process group. In Bash, sending a signal to `-$$` (negative script PID) sends it to the script's process group.
```bash
#!/bin/bash
# Enable job control so the script runs in its own process group
set -m
# Trap EXIT to kill the entire process group (children and grandchildren)
trap 'kill -- -$$ 2>/dev/null' EXIT
# A child that spawns another child (grandchild)
( sleep 100 & sleep 100 ) &
echo "Working..."
sleep 2
```
*⚠️ **Warning on Process Groups:** If you run this script from a context where it *doesn't* get its own process group (e.g., poorly configured CI pipelines), killing `-$$` might inadvertently kill the parent terminal. Using `set -m` helps mitigate this by forcing the script to be a process group leader.*
### Summary: Which should I use?
* Use **Method 1 (`kill $(jobs -p)`)** 95% of the time. It is standard, safe, and easily understood by other developers.
* Use **Method 2 (Tracking `$!`)** if your script spawns a mix of things and you only want to manage specific ones.
* Use **Method 3 (`kill -- -$$`)** only if you are experiencing issues with "grandchildren" processes surviving the death of your direct children.

End of response · 72 lines · 3,188 characters · sha256 342f9640d8fc1667605171af90631cb3b0aa489e24868739c1a72323de53c335