🎓 A1Learner
🔍
0 of 45 lessons completed

Bash Signals to Children

Signal forwarding is the practice of passing a signal a parent receives on to its children. A parent that dies without forwarding leaves its children running with no supervisor.

A child trap catches TERM

The child traps TERM and prints one line when it arrives. The parent sends TERM only after the child signals it is ready.

bash -c 'trap "kill $s 2>/dev/null; echo child caught TERM; exit 0" TERM; printf ready > ready.txt; sleep 10 & s=$!; wait $s; echo child slept out' &
pid=$!
until [ -f ready.txt ]; do sleep 0.05; done
kill -TERM "$pid"
wait "$pid"
echo "parent done"

A supervisor forwards TERM

The supervisor traps TERM, stops its worker, and waits for the worker before printing. The wait inside the trap fixes the order of the two lines.

(
  trap 'kill -TERM "$worker" 2>/dev/null; wait "$worker" 2>/dev/null; echo "supervisor forwarding TERM"' TERM
  bash -c 'trap "kill $s 2>/dev/null; echo worker caught TERM; exit 0" TERM; printf ready > ready.txt; sleep 10 & s=$!; wait $s; echo worker slept out' &
  worker=$!
  until [ -f ready.txt ]; do sleep 0.05; done
  printf supervisor-ready > sup.txt
  wait "$worker"
  echo "supervisor done"
) &
sup=$!
until [ -f sup.txt ]; do sleep 0.05; done
kill -TERM "$sup"
wait "$sup"
echo "main done"

An EXIT trap cleans up children

The subshell traps EXIT to kill its child when the subshell ends. The child never reaches its write, so the file stays absent.

(
  bash -c 'sleep 0.5; printf survived > survived.txt' &
  inner=$!
  trap "kill $inner 2>/dev/null" EXIT
  sleep 0.1
)
sleep 0.8
if [ -f survived.txt ]; then echo "child survived"; else echo "child cleaned up"; fi

Example

(
  trap 'kill -TERM "$worker" 2>/dev/null; wait "$worker" 2>/dev/null; echo "supervisor stopping worker"' TERM
  trap "kill $worker 2>/dev/null" EXIT
  bash -c 'trap "kill $s 2>/dev/null; echo worker stopped cleanly; exit 0" TERM; printf ready > ready.txt; sleep 10 & s=$!; wait $s' &
  worker=$!
  until [ -f ready.txt ]; do sleep 0.05; done
  printf supervisor-ready > sup.txt
  wait "$worker"
  echo "supervisor done"
) &
sup=$!
until [ -f sup.txt ]; do sleep 0.05; done
kill -TERM "$sup"
wait "$sup"
echo "main done"

Key points

  • Trap timing: a trap runs at once while the shell waits in wait, but only after a foreground command finishes.
  • Forwarding rule: a parent trap should kill each child by its saved identifier, then wait for the child to exit.
  • Saved identifiers: store a child's identifier in a variable at launch, and never print it.
  • Orphan risk: a child whose parent dies without forwarding keeps running until it finishes or is killed.
  • EXIT trap: a trap on EXIT runs when its shell ends, so it can stop children during cleanup.
  • Signal choice: TERM asks a child to stop cleanly, while KILL forces it with no cleanup.
  • Wait inside a trap: waiting for the child inside the trap keeps the output order fixed.

In this example, a supervisor subshell sets two traps: one on TERM that stops its worker and waits for it, and one on EXIT that kills the worker during cleanup. The worker traps TERM, stops its own sleeper, and prints one clean line. The main script signals the supervisor, waits for it, and prints that it is done.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A parent receives TERM while its child sleeps. What stops the child from becoming an orphan?