trap — Run a handler when the shell receives a signal or exits across all 5 shells
Equivalents in every shell
trap 'cleanup' EXITPOSIX builtin. Syntax: `trap <action> <signals...>`. Common signals: `EXIT`, `INT` (Ctrl-C), `TERM`, `ERR`, `DEBUG`. The action string is re-evaluated each time the signal fires, so be deliberate about whether `$var` should expand at trap-install time or trap-fire time.
trap 'cleanup' EXITSame as bash. Zsh additionally honours the function-name convention: defining `TRAPINT()`, `TRAPEXIT()`, etc. installs a trap for that signal — no `trap` command needed, just the right function name.
trap 'cleanup' EXITFish ships `trap` only as a thin POSIX-compat wrapper around its native event system. The idiomatic form is `function cleanup --on-event fish_exit; ...; end`, or `--on-signal SIGINT` for a specific signal.
try { script } finally { cleanup }No `trap` for OS signals. PowerShell's `trap { ... }` block catches EXCEPTIONS, not signals — same name as bash, different semantics. For Ctrl-C handling use `[Console]::CancelKeyPress`; for guaranteed cleanup use `try/finally`.
(commands) & cleanupCmd has no signal handling and no exit trap. Chaining with `&` (always run next) or `&&` (run on success) is the only sequencing primitive. Pressing Ctrl-C aborts the batch immediately — cleanup steps are skipped.
Worked examples
Run a cleanup function when the script exits
trap cleanup EXITtrap cleanup EXITfunction on_exit --on-event fish_exit; cleanup; endtry { run_script } finally { cleanup }Ignore Ctrl-C inside a critical section
trap '' INT; critical_task; trap - INTtrap '' INT; critical_task; trap - INT[Console]::TreatControlCAsInput = $true; critical_task; [Console]::TreatControlCAsInput = $falseLog a message whenever any command errors
trap 'echo "Error at line $LINENO" >&2' ERRtrap 'echo "Error at line $LINENO" >&2' ERR$ErrorActionPreference = 'Stop'; trap { Write-Host "Error: $_" }Gotchas
- `trap '' SIGNAL` IGNORES the signal; `trap - SIGNAL` RESETS it to the shell's default handler. The empty-string vs dash distinction is a frequent source of "why isn't my Ctrl-C working?" bugs in script debugging.
- The `EXIT` trap fires on ANY exit — `exit 0`, `exit 1`, normal end-of-script, and SIGTERM-during-shutdown — but NOT on `SIGKILL` (`kill -9`) and not on power loss. Cleanup code in EXIT is not a substitute for crash-safe state on disk.
- Bash trap actions run in the SAME shell context, so `local` variables and function state are visible. Inside a subshell `( ... )` traps reset to defaults — handlers defined in the parent will NOT fire for signals delivered to the subshell.
- PowerShell's `trap { ... }` block is NAMED LIKE bash's but BEHAVES like a global catch — it intercepts exceptions, not OS signals. Porting `trap INT` to a PowerShell `trap { ... }` block silently produces wrong code; use `[Console]::CancelKeyPress` for true signal handling.
- Fish's `trap` is a POSIX-compat shim — only a small set of signals (notably `EXIT` → `fish_exit`) map cleanly to fish events. For full coverage use `function ... --on-signal SIGTERM ... end` directly instead of the `trap` shim.
WSL & PowerShell Core notes
Related glossary
- Signals
Signals are tiny inter-process messages. Knowing which ones can be caught, which cannot, and which clean up vs. terminate ungracefully prevents a lot of "service won’t restart" bugs.
- Exit codes
An exit code is a one-byte unsigned integer (0–255) the parent reads via `wait()`. The shell exposes it as `$?` (bash/zsh/fish) or `$LASTEXITCODE` (PowerShell, for external programs). Conventions matter.
Common tasks using trap
- Enable strict mode for a shell script
Make scripts fail loudly on errors — abort on the first failed command, treat unset variables as errors, and propagate pipe failures. The opposite of "fail silently and corrupt data".
- Send a signal to a process
Deliver a specific Unix signal to a process — useful for graceful shutdown, config reload, and triggered behaviour.