In a bash script, what is the difference between `trap 'handler' TERM`, `trap '' TERM` and `trap - TERM`, and which of the three still applies to commands the script launches?
answer
- three actions: command, empty string, dash
- empty string means ignore, not no-op
- children inherit ignore, never a handler
- trap -p shows what is actually set
basics
~20 sA non-empty argument sets a handler, an empty string makes the shell ignore the signal, and a lone dash restores the default action. Only the ignore is inherited by commands the script launches; handlers are not.
solid answer
~40 sThe first argument to `trap` is the disposition. `trap 'handler' TERM` runs that command string when SIGTERM arrives. `trap '' TERM` — an explicitly empty string — sets the signal to *ignored*, so it is discarded and nothing runs. `trap - TERM` clears whatever you set and restores bash's default action, which for TERM is to die. The important asymmetry is inheritance: a handler is a property of this shell only, so a command the script starts runs with default dispositions and dies on TERM as usual, but an *ignored* signal is inherited across fork and exec, which means a child started while `trap '' TERM` is in force cannot be SIGTERMed either. `trap -p` prints the current settings, and `trap -p TERM` prints nothing when the signal is at its default.
code
bash · 10 lines#!/usr/bin/env bash
trap '' TERM # ignore, and children inherit the ignore
sleep 30 &
child=$!
kill -TERM "$child"
sleep 1
kill -0 "$child" 2>/dev/null && echo "child survived SIGTERM"
trap - TERM # back to bash's default: die on TERM
trap -p TERM # prints nothing: the signal is at its defaultgo deeper
Recall the three shapes and what each does: a command string handles, '' ignores, - restores the default. Being able to state that much clearly is most of what is asked here.
Explain the inheritance rule and why it is asymmetric: a handler is a bash command string that no child can run, whereas an ignored disposition is process state that survives fork and exec.
Show the operational consequence — an ignored signal left set makes a whole subtree resistant to ordinary termination, and a handler on KILL is coverage theatre. Reach for trap -p when auditing an inherited script.
Own the convention: decide where in your scripting standards ignoring a signal is ever acceptable, how critical sections are bracketed and reset, and how termination behaviour is made uniform across a fleet of scripts.
## The three forms `trap` takes an action followed by one or more conditions. The action decides everything: ```bash trap 'echo caught; exit 1' TERM # handler: run this command string trap '' TERM # ignore: discard the signal, run nothing trap - TERM # reset: restore the shell's default action ``` The distinction people miss is between the second and a *do-nothing handler*. `trap : TERM` looks equivalent to `trap '' TERM` — the handler `:` is the null command — but it is not. `trap : TERM` installs a handler; the signal is still delivered and the shell still wakes up to run a no-op. `trap '' TERM` sets the disposition to *ignored* at the OS level, and that difference is exactly what changes for child processes. ## What crosses into child processes A trap handler is a command string stored in this shell's trap table. A newly created process cannot inherit a bash command string — it may not even be a shell — so every command your script launches starts with the signals bash had at *default*, not with your handler. A background job the script starts will die on SIGTERM no matter what your script's TERM handler says. An ignored disposition is different, because ignoring is a property the kernel keeps for the process and it survives both fork and exec. Run this and the child outlives the signal: ```bash trap '' TERM sleep 30 & kill -TERM "$!" # discarded: the sleep keeps running ``` That asymmetry is worth internalising because it cuts both ways. It is useful when you deliberately want a subtree protected. It is a nasty surprise when you set `trap '' TERM` around a critical section, forget to clear it, and every process the script later starts quietly becomes unkillable by ordinary means — the operator reaches for `kill -9` and wonders why. ## Clearing and inspecting `trap - TERM` restores the default; `trap -` with no condition, or `trap - SIGNAL…` with a list, clears those specific ones. `trap -p` prints every trap currently set, in a form you can paste back: ```bash $ trap 'echo x' SIGTERM; trap -p TERM trap -- 'echo x' SIGTERM ``` `trap -p TERM` printing nothing means the signal is at its default. This is how you audit a long script or a sourced library that may have installed traps behind your back. ## Naming the signals Bash accepts three spellings for the same condition: the number (`15`), the bare name (`TERM`), and the `SIG`-prefixed name (`SIGTERM`). `trap -p` normalises its output to the prefixed form. Prefer the bare name in scripts you might run under a non-bash `/bin/sh`: POSIX specifies the un-prefixed names, and numbers are the worst choice of the three because the mapping is not identical on every platform. ## Signals you cannot set at all SIGKILL and SIGSTOP cannot be caught or ignored — that is enforced by the kernel, not the shell, so it holds for every program, not just bash. Bash does not protect you from writing it: `trap 'echo hi' KILL` is accepted silently, returns 0, and even shows up in `trap -p` output, but the handler can never run because the signal is never delivered to userspace. Treat any code that traps KILL as a bug wearing a disguise, and design for the fact that some terminations simply do not run your cleanup. ## Where the forms earn their keep The ignore form is for narrow, deliberate critical sections — a stretch of a script that must not be interrupted between two writes — bracketed by an immediate reset: ```bash trap '' INT write_both_halves trap - INT ``` The handler form is for cleanup and for forwarding signals to work the script is supervising. The reset form is for undoing either one, and for the re-raise idiom where a handler clears its own trap and then signals itself so the script dies with the honest 128+N status instead of a fabricated 0.
- Is `trap : TERM` the same as `trap '' TERM`?No. `trap : TERM` installs a handler that runs the null command, so the signal is still delivered and this shell simply does nothing useful with it. `trap '' TERM` sets the disposition to ignored, which is a real kernel-level state and is inherited by every process the script starts. The visible difference shows up on children, not on the script itself.
- How do you check what traps are in force after sourcing someone else's shell library?Run `trap -p`, which lists every currently set trap in re-usable form, or `trap -p TERM` for one condition — empty output means it is at its default. Sourced code runs in your shell, so it can install or clear traps behind your back; this is the only way to see what you actually ended up with.
- What happens if you write `trap 'cleanup' KILL`?Bash accepts it silently, returns 0, and will even list it under `trap -p`, but the handler can never run: SIGKILL cannot be caught or ignored by any process, and that is enforced by the kernel rather than the shell. It gives a false sense of coverage, so treat it as a defect in review.
saying these in an interview costs you the question
- Thinks `trap : TERM` and `trap '' TERM` are the same
- Expects child processes to inherit the parent's handler
- Reads `trap - TERM` as ignoring the signal
- Believes `trap 'x' KILL` gives cleanup coverage on kill -9
- Uses signal numbers everywhere and assumes they are portable