Shell Scripts Don't Fail Gracefully by Default — Here's How to Make Them
Shell scripts are the glue in most production pipelines, yet they ship with zero guardrails by default. A script that silently continues after a failed `cd` or a typo in a variable name can corrupt data or mask outages for hours. The patterns here — `set -euo pipefail`, `trap` cleanup, stderr logging — are the minimum bar for any script that runs without a human watching.
Most shell scripts start with an implicit assumption that every command succeeds. That assumption breaks the moment a script runs in crontab, CI/CD, or on someone else's machine. The fix is a combination of exit code conventions, the `set -euo pipefail` safety net, and explicit error-handling patterns like `trap` for cleanup and `die()` for controlled exits.
A hardened script also needs logging that answers when, where, and what went wrong — with timestamps, severity levels, and output to stderr so diagnostics don't mix with data. The article walks through a before-and-after refactor of a CSV processing script, showing how argument validation, temporary file cleanup, and invalid-row tracking turn a fragile one-liner into something that can run unattended.
The difference between "it ran" and "it ran correctly" is these guardrails. Silent failures — missing files, typo'd variable names, swallowed pipeline errors — are the most common source of production shell bugs, and they're all preventable with a few lines at the top of every script.
Shell's default behavior is optimized for interactive use, not automation — it silently ignores undefined variables and pipeline failures, which is the opposite of what production scripts need.
The `set -e` controversy exists because its edge cases (function returns, arithmetic expressions, conditional contexts) are genuinely surprising, but the alternative — manual error checks on every line — is worse.
Defensive error handling that swallows failures is more dangerous than fail-fast scripts that crash loudly; a script that produces wrong output without errors is harder to debug than one that refuses to run.
Logging to stderr rather than stdout is a simple convention that separates data from diagnostics, yet most shell tutorials never mention it, leading to scripts where errors vanish into redirected output files.