Skip to content
shellmap

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".

How to enable strict mode for a shell script in each shell

Bashunix
set -euo pipefail; IFS=$'\n\t'

`-e` exits on any non-zero (with caveats), `-u` errors on unset variables, `-o pipefail` propagates non-zero rc from any stage of a pipe (default behavior is to return only the LAST stage's rc). `IFS` newline+tab limits word-splitting hazards.

Zshunix
setopt err_exit nounset pipe_fail; IFS=$'\n\t'

zsh equivalents of bash flags use word names. Alternative one-liner: `emulate -L bash -o err_exit -o nounset -o pipe_fail` inside a function — emulates bash strict mode WITHOUT polluting the parent shell.

Fishunix
function check; or begin; echo "Failed: $argv" >&2; exit 1; end; end

Fish has NO `set -e` equivalent — design choice (the maintainers consider implicit failure propagation harmful). Pattern is `cmd; or return 1` per call, or `if not cmd; ...; end`. The `check` helper above wraps that ergonomically.

PowerShellwindows
$ErrorActionPreference = 'Stop'; Set-StrictMode -Version 3.0

`ErrorActionPreference=Stop` makes ALL cmdlet errors throw (default `Continue` warns + continues). `Set-StrictMode 3.0` errors on uninitialized variables, out-of-bounds array access, and method calls on `$null`. External-command non-zero rc still requires `if (-not $?) { ... }` — pwsh 7.4+ has `$PSNativeCommandUseErrorActionPreference = $true` to fix that.

cmd.exewindows
cmd /c "echo off & if errorlevel 1 exit /b %errorlevel%"

cmd has no `set -e` equivalent. Every command must be followed by `|| exit /b %errorlevel%` (caveat: `||` checks the previous `errorlevel`, doesn't propagate from a piped chain). For non-trivial scripts, switch to PowerShell.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`set -e` has surprising exceptions** — it does NOT trigger when the failing command is part of: an `if`/`while`/`until` condition, a `&&`/`||` chain (except the rightmost), the test of a `!` operator, or a function whose result is being assigned. So `if false; then …; fi` is fine, but `false && true` errors out only if `false` is the rightmost. The mental model: `set -e` triggers when a command "is being checked for success" — if you're doing the check yourself, bash assumes you'll handle the failure. Use `set -E` (capital E) + `trap "..." ERR` to also trigger inside functions (functions inherit ERR only with `-E`).
  • **`set -u` and intentionally-unset variables**: `${var:-default}` (use default if unset) and `${var:+alt}` (use alt if set) bypass the unset-variable error. So `: "${HOME:?HOME must be set}"` aborts cleanly with the message if HOME is unset; `${1:-}` defaults a missing arg to empty string. **GOTCHA**: `[[ -z "$var" ]]` triggers `set -u` if `$var` is unset (use `[[ -z "${var:-}" ]]`). Arrays: `${array[@]}` on an empty array errors under `set -u` on bash 4.3 and older — `"${array[@]:-}"` is the safe form (bash 4.4+ relaxed this).
  • **`set -o pipefail` is THE most important of the three** — without it, `cmd_that_fails | grep foo` returns 0 if grep matched, masking the upstream failure. Standard incident-causing pattern: `curl -sf URL | jq .data` where curl fails silently (network blip, 404) → jq receives empty input → returns null → downstream code processes null as "no data" instead of "error". With `pipefail`, the curl failure propagates. Trade-off: signals like SIGPIPE (rc 141) can cause spurious failures in `cmd | head -n1` (head exits after 1 line, breaks the pipe, cmd dies with SIGPIPE). Defend with explicit `|| true` on the upstream side if you actually want partial reads.
  • **pwsh strict mode has THREE versions**, each strictly more restrictive than the previous: `Set-StrictMode -Version 1.0` (errors on uninitialized variables in expressions, not strings); `2.0` (also errors on uninitialized variables in strings, function calls with parentheses, unrecognized properties); `3.0` (also errors on out-of-bounds array access and method calls on `$null`). For new scripts use `Set-StrictMode -Version Latest` (currently 3.0). The other half of pwsh strict mode is external-process error handling: `$PSNativeCommandUseErrorActionPreference = $true` (pwsh 7.4+) makes `& nonzeroExitProgram` throw — before 7.4, only cmdlets respected `$ErrorActionPreference`. Older pwsh wraps every native call: `& git push; if ($LASTEXITCODE -ne 0) { throw "git push failed" }`.
  • **Fish's anti-strict-mode philosophy**: fish refuses to implement `set -e` (see https://fishshell.com/docs/current/index.html#exit-status). The design view is that auto-exit hides where the failure actually was. Idiomatic fish error handling: `cmd; or return $status` per check, or build a helper function. For scripts that NEED bash-style strict behavior, just write them in bash and call from fish (`bash strict_script.sh`); fish is interactive-focused and the strictness gap is genuinely a deliberate design choice, not an oversight.

Related commands

Related tasks