MODELDRIFT.WATCH

Question 14 · Sep 27 · Day 18

Gemini on Question 14, Sep 27

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

The most idiomatic way to handle this in Bash is to use a trap on the EXIT pseudo-signal.

When you set a trap on EXIT, Bash will execute your cleanup code regardless of whether the script finishes normally, errors out, or is terminated by a standard signal (like SIGINT from Ctrl+C or SIGTERM).

Here are the three best approaches, depending on how complex your child processes are.

1. The Standard Way (Direct Children)

If your script simply spawns a few background processes (&) and you want them to die when the script dies, use jobs -p inside an EXIT trap.

#!/bin/bash

# Define the cleanup function
cleanup() {
    # Get PIDs of all background jobs started by this script
    local pids=$(jobs -p)
    if [[ -n "$pids" ]]; then
        echo "Cleaning up orphaned children..."
        # 2>/dev/null hides errors if a child already died naturally
        kill $pids 2>/dev/null 
    fi
}

# Register the cleanup function to run on EXIT
trap cleanup EXIT

# --- Your Script Here ---
sleep 100 &  # Child 1
sleep 200 &  # Child 2

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

2. The Process Group Way (Deep Process Trees)

The jobs -p method only kills direct children. If your child processes spawn their own children, those grandchildren will still be orphaned.

To kill the entire process tree, you must send a signal to the whole Process Group. You do this by negating the parent's PID (-$$).

Note: You must reset the trap inside the trap function to prevent a recursive loop, because sending a signal to the process group also sends it to the parent script itself.

#!/bin/bash

# Run this script in its own process group to avoid killing the caller's terminal
set -m 

cleanup() {
    echo "Killing entire process group..."
    # Disable the trap so we don't infinitely loop when we kill ourselves
    trap - EXIT
    # Send SIGTERM to the process group
    kill -- -$$ 2>/dev/null
}

trap cleanup EXIT INT TERM

# --- Your Script Here ---
# Even if this child spawns more children, they all die
bash -c 'sleep 100 & sleep 100' & 

echo "Parent doing work..."
sleep 10

3. The Flawless Linux Way (SIGKILL protection)

The big caveat with Bash traps: If your parent script is killed with kill -9 (SIGKILL), the trap will not fire, and your children will be orphaned.

If you are on Linux and need a 100% guarantee that children die even if the parent receives kill -9, Bash alone cannot do it. You have to tell the Linux kernel to issue a pdeathsig (Parent Death Signal).

You can achieve this idiomatically by wrapping the child execution in a tiny Python or Perl one-liner, or by using prlimit. Here is the easiest way using standard Linux tools (util-linux):

#!/bin/bash

# No trap needed for this specific child!
# `tail --pid=$$ -f /dev/null` waits for the parent ($$) to die.
# When the parent dies, tail exits, closing the pipe.
# The child process will receive a SIGPIPE or EOF and die.

(
    # This subshell runs the child command
    exec my_child_command
) < <(tail --pid=$$ -f /dev/null) 

# Alternatively, if you have systemd installed, you can use systemd-run:
# systemd-run --user --scope -p "Delegate=yes" ./your_script.sh

Summary Recommendation

  • •Use Method 1 (trap + jobs -p) for 95% of standard Bash scripts. It is the most readable and standard way to do things in Bash.
  • •Use Method 2 (kill -- -$$) if your children are spawning grandchildren.
  • •Worry about Method 3 only if you are building a robust daemon where kill -9 leaving orphans would cause a critical system failure.

sha256 24d4e2a6109e… · 24 tokens in · 2,506 out · 21.7 s · via Google · stopped on its own

Sep 27, 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-27 09:00:07 UTC (matches the pin)
Date
2026-09-27 · run 20260927T090007Z-7adb14 · scorer v1
Scored
answered
sha256
24d4e2a6109e05724c75e8c230b0d8ef4b65b12ef112f89e09f4b92b819956f4
Permalink
https://modeldrift.watch/r/2026-09-27/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-27 09:06:58 UTC
Run
20260927T090007Z-7adb14
Served by
Google · HTTP 200 · finish stop
Size
3,684 characters · 2,506 tokens out incl. hidden reasoning · 21.7 s
The most idiomatic way to handle this in Bash is to use a **`trap`** on the **`EXIT`** pseudo-signal.
When you set a trap on `EXIT`, Bash will execute your cleanup code regardless of whether the script finishes normally, errors out, or is terminated by a standard signal (like `SIGINT` from Ctrl+C or `SIGTERM`).
Here are the three best approaches, depending on how complex your child processes are.
### 1. The Standard Way (Direct Children)
If your script simply spawns a few background processes (`&`) and you want them to die when the script dies, use `jobs -p` inside an `EXIT` trap.
```bash
#!/bin/bash
# Define the cleanup function
cleanup() {
# Get PIDs of all background jobs started by this script
local pids=$(jobs -p)
if [[ -n "$pids" ]]; then
echo "Cleaning up orphaned children..."
# 2>/dev/null hides errors if a child already died naturally
kill $pids 2>/dev/null
fi
}
# Register the cleanup function to run on EXIT
trap cleanup EXIT
# --- Your Script Here ---
sleep 100 & # Child 1
sleep 200 & # Child 2
echo "Parent doing work. Press Ctrl+C to test."
sleep 10 # Parent work
```
### 2. The Process Group Way (Deep Process Trees)
The `jobs -p` method only kills **direct** children. If your child processes spawn their own children, those grandchildren will still be orphaned.
To kill the entire process tree, you must send a signal to the whole **Process Group**. You do this by negating the parent's PID (`-$$`).
*Note: You must reset the trap inside the trap function to prevent a recursive loop, because sending a signal to the process group also sends it to the parent script itself.*
```bash
#!/bin/bash
# Run this script in its own process group to avoid killing the caller's terminal
set -m
cleanup() {
echo "Killing entire process group..."
# Disable the trap so we don't infinitely loop when we kill ourselves
trap - EXIT
# Send SIGTERM to the process group
kill -- -$$ 2>/dev/null
}
trap cleanup EXIT INT TERM
# --- Your Script Here ---
# Even if this child spawns more children, they all die
bash -c 'sleep 100 & sleep 100' &
echo "Parent doing work..."
sleep 10
```
### 3. The Flawless Linux Way (`SIGKILL` protection)
**The big caveat with Bash traps:** If your parent script is killed with `kill -9` (`SIGKILL`), the `trap` will **not** fire, and your children will be orphaned.
If you are on Linux and need a 100% guarantee that children die even if the parent receives `kill -9`, Bash alone cannot do it. You have to tell the Linux kernel to issue a **`pdeathsig`** (Parent Death Signal).
You can achieve this idiomatically by wrapping the child execution in a tiny Python or Perl one-liner, or by using `prlimit`. Here is the easiest way using standard Linux tools (util-linux):
```bash
#!/bin/bash
# No trap needed for this specific child!
# `tail --pid=$$ -f /dev/null` waits for the parent ($$) to die.
# When the parent dies, tail exits, closing the pipe.
# The child process will receive a SIGPIPE or EOF and die.
(
# This subshell runs the child command
exec my_child_command
) < <(tail --pid=$$ -f /dev/null)
# Alternatively, if you have systemd installed, you can use systemd-run:
# systemd-run --user --scope -p "Delegate=yes" ./your_script.sh
```
### Summary Recommendation
* Use **Method 1 (`trap` + `jobs -p`)** for 95% of standard Bash scripts. It is the most readable and standard way to do things in Bash.
* Use **Method 2 (`kill -- -$$`)** if your children are spawning grandchildren.
* Worry about **Method 3** only if you are building a robust daemon where `kill -9` leaving orphans would cause a critical system failure.

End of response · 93 lines · 3,684 characters · sha256 24d4e2a6109e05724c75e8c230b0d8ef4b65b12ef112f89e09f4b92b819956f4