That `set -e` at the top of your script isn’t always doing what you think.

Shell Scripting & Automation

That `set -e` at the top of your script isn’t always doing what you think.

Technical Briefing | 9/24/2026

Most of us have a go-to template for shell scripts. For many, that starts with #!/bin/bash and set -e. And for a long time, I blindly slapped set -e at the top of everything, figuring it’d make my scripts bulletproof against unexpected failures. It’s a common assumption: if something goes wrong, the script should just stop, right? But I’ve seen enough scripts silently proceed to cause bigger problems in production because set -e wasn’t actually doing its job in specific scenarios. That’s when you learn the hard way that understanding *why* a script exits, or *doesn’t* exit, is more important than just hoping for the best.

The Unspoken Rules of `set -e` (and Where It Falls Short)

The idea behind `set -e`, or `set -o errexit`, is simple: exit immediately if a command exits with a non-zero status. Sounds great. It stops scripts from continuing with partial data or a broken state. But it’s got quirks. For one, if you run `command || true`, `set -e` is essentially ignored for `command` because `|| true` ensures the overall pipeline always succeeds. Same goes for commands in `if` conditions or `while` loops — their exit status doesn’t trigger `errexit`. I’ve been bitten by this when a critical `grep` command failed to find anything, but because it was inside an `if` statement, the script merrily continued, processing an empty result set and eventually creating a bad configuration file on a dozen servers. You need to be explicit about handling errors, even with `set -e` enabled.

#!/bin/bash
set -e


echo "Demonstrating how set -e can be bypassed."
echo "--- Scenario 1: Using '|| true' ---"
# This command *will* fail, but the '|| true' ensures its exit status is ignored by set -e
false || echo "The 'false' command failed, but this line runs, and set -e is bypassed."
echo "Script continues after 'false || true'."


echo
echo "--- Scenario 2: Command in 'if' condition ---"
# The 'false' command here will *not* cause set -e to exit the script
if false; then
echo "This block won't run."
fi
echo "Script continues after 'if false'."


echo
echo "--- Scenario 3: Manual Error Handling with explicit exit ---"
# The correct way to handle a potential failure and ensure exit
if ! ls /definitely_not_here_123 &>/dev/null; then
echo "Caught the 'ls' failure, exiting explicitly."
exit 1 # This is how you guarantee exit on failure
fi
echo "This line will NOT be reached if the 'ls' command fails and we explicitly exit."

The Unsung Heroes: `set -u` and `set -o pipefail`

While `set -e` gets most of the attention, `set -u` (or `set -o nounset`) and `set -o pipefail` are arguably more important for preventing subtle, hard-to-debug issues. `set -u` makes sure you can’t use an unset variable. No more `rm -rf /$SOME_VAR_THAT_WASNT_SET` accidents that silently delete your root filesystem because `$SOME_VAR` expanded to nothing. This bit me when I forgot to pass an environment variable to a cleanup script; it just ran `rm -rf /some/path/` without the critical final directory component, wiping out more than intended. And `set -o pipefail`? That’s golden. By default, in `cmd1 | cmd2 | cmd3`, the pipe’s exit status is just `cmd3`’s. If `cmd1` fails to even produce output, `cmd2` might succeed on empty input, and `cmd3` might succeed, leaving you thinking everything worked. `pipefail` fixes that, ensuring the entire pipe’s exit status is the rightmost non-zero status. It’s a lifesaver for data transformation pipelines.

  • They catch mistakes early: Typos, forgotten environment variables, or silent upstream pipeline failures are exposed immediately.
  • They enforce predictable failure: Your scripts become more predictable because unexpected conditions lead to explicit exits, not silent data corruption.
  • They make debugging easier: Instead of chasing down *why* your data looks weird, you’ll know *where* your script stopped and *what* command failed.
  • They’re a form of self-documentation: By requiring variables to be set, your script’s input requirements become clearer.

So, next time you’re templating a new script, don’t just copy-paste `set -e` and call it a day. Spend a minute thinking about what you’re actually trying to achieve with error handling. Understand what each of these options does, and more importantly, what they *don’t* do. Sometimes you need `|| true`, sometimes you need explicit `if ! command; then exit 1; fi`. But by combining `set -e`, `set -u`, and `set -o pipefail` thoughtfully, you’ll build automation that fails loud and proud, rather than quietly creating a mess for you to untangle at 3 AM.

Linux Admin Automation  |  © www.ngelinux.com  |  9/24/2026

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Newest
Oldest Most Voted