The Lost Feed

🌐Old Internet

The Strange Case of Bash's 'set -e' Command

Ever found Bash's 'set -e' not stopping scripts when you thought it would? Discover the surprising reasons why this common command can fail you.

10 views·6 min read·Jul 7, 2026
Why doesn't Bash’s ‘set -e’ do what I expected? (2021)

Ever written a script expecting it to stop dead in its tracks when something went wrong, only to see it keep chugging along? You're not alone. The set -e command in Bash is supposed to be your safety net, exiting the script immediately if any command fails. But sometimes, it just doesn't work the way you'd expect. It's a common frustration for many who rely on Bash for their daily tasks.

This isn't about a simple typo or a misunderstanding of basic commands. It's about the subtle, often hidden, ways Bash operates that can catch even experienced users off guard. Understanding these quirks is key to writing reliable scripts that won't unexpectedly continue after an error.

What is set -e Supposed to Do?

At its core, set -e is meant to make your Bash scripts safer. When you enable it, Bash will exit immediately if any command used in a script returns a non-zero exit status. A non-zero exit status typically means a command failed.

Think of it like a failsafe. If a command to delete a file fails, set -e should stop the script before it tries to do something else based on the assumption that the file is gone. This prevents potentially disastrous mistakes caused by unexpected command failures.

This simple behavior is incredibly useful for maintaining script integrity. It helps ensure that a series of operations only proceeds if each preceding step was successful. Without it, you might be running commands on data that wasn't properly prepared, leading to corrupted files or incorrect results.

When set -e Doesn't Seem to Work

Despite its clear purpose, there are several situations where set -e might not trigger an exit. These are the moments that leave scripters scratching their heads. It's not that the command is broken, but rather that the conditions under which it operates are more complex than they first appear.

One common scenario is when a command fails, but its failure is part of a larger construct that succeeds. For example, if a command is part of an if statement's condition, its failure might not cause the script to exit. Bash evaluates the overall condition, not just the individual command's success or failure in isolation.

Another tricky area involves commands that are expected to fail, like checking if a file exists before creating it. If the file doesn't exist, the check command will fail, but if this is handled correctly, set -e shouldn't stop the script. The trick is in how these expected failures are managed.

The

Logic of if Statements and set -e

Conditional statements like if are a prime example of where set -e can behave unexpectedly. When a command is used as the condition in an if statement, its exit status is interpreted differently. Bash checks if the *entire condition

  • is true or false, not just if the command itself succeeded.

For instance, if you have if grep 'pattern' file.txt; then ..., and grep doesn't find the pattern, it returns a non-zero exit status. However, in the context of an if statement, this non-zero status means the condition is false. Because the if statement itself evaluated to false, set -e doesn't see this as a script-stopping error.

This is a critical distinction. The command's failure is handled by the if structure, and the if structure as a whole has successfully (in its own way) completed its evaluation. The script continues because the if statement didn't "fail" in the sense that set -e is looking for.

Commands That Don't Trigger set -e

Not all commands that return a non-zero exit status will cause set -e to act. Some commands are designed to return specific exit codes that Bash interprets in special ways.

For example, commands that are part of a pipeline might not trigger set -e if the pipeline as a whole is considered successful. If you have command1 | command2, and command1 fails but command2 succeeds, the pipeline might not exit immediately. This depends on shell options and how Bash handles pipeline exit statuses.

Another area to watch out for is commands that are intentionally run in a way that ignores their exit status. This can happen with certain shell built-ins or when using operators that explicitly handle exit codes.

Handling Expected Failures Gracefully

So, how do you write scripts that use set -e effectively while still handling situations where a command might legitimately fail? The key is to use shell constructs that explicitly manage these cases.

One common technique is to use the || (OR) operator. If you want to run a command that might fail, but you want the script to continue if it does, you can do this:

command_that_might_fail || true

The true command always succeeds (returns a zero exit status). So, if command_that_might_fail fails, the || operator executes true, making the entire line appear successful to set -e. This effectively "absorbs" the error.

Another method is to use the if statement as mentioned before, but structure it carefully. If you need to perform an action only if a command fails, you can write:

if ! command_that_might_fail; then # Do something because it failed fi

This explicitly checks for the failure and allows you to handle it without exiting the script.

The pipefail Option: A Helpful Companion

For scripts that heavily rely on pipelines, the pipefail option is a lifesaver. By default, Bash only considers the exit status of the *last

  • command in a pipeline when determining success or failure. If an earlier command in the pipeline fails, but the last one succeeds, set -e might not trigger.

When you enable set -o pipefail, Bash will return the exit status of the *rightmost

  • command in the pipeline that failed. If all commands in the pipeline succeed, the pipeline's return status is zero.

Adding set -o pipefail to your script, usually near the top along with set -e, makes your pipeline error checking much more robust.

For example, if you have:

command1 | command2 | command3

Without pipefail, only command3's exit status matters. With pipefail, if command1 fails, the pipeline fails, and set -e will likely exit your script as expected.

Why This Matters for Reliable Scripting

Understanding these behaviors of set -e isn't just about trivia. It's about writing scripts that you can trust. When your script relies on a specific outcome, and set -e doesn't catch an unexpected failure, it can lead to corrupted data or broken processes down the line.

These subtle interactions are why many experienced scripters adopt a standard set of options at the beginning of their scripts. A common practice is to include:

set -e set -u set -o pipefail

set -u is another important option that causes Bash to exit if you try to use an unset variable. Together, these three commands create a much stricter and safer environment for running your scripts.

It’s easy to assume set -e is a simple on/off switch for errors. But Bash’s design often involves context. The if statement, pipelines, and specific command behaviors all play a role. Learning these rules helps you avoid unexpected script behavior and build more reliable automation.

So, the next time your set -e script keeps running after an error, don't assume it's magic. Take a closer look at the logic and the shell's specific rules. You might find that Bash was following its own precise, albeit sometimes surprising, instructions.

How does this make you feel?

Comments

0/2000

Loading comments...