set -euo pipefail — the Line That Would Have Saved Me from Deleting the Wrong Directory

set -euo pipefail — the Line That Would Have Saved Me from Deleting the Wrong Directory

Leader 1 5 28
calendar_today agoschedule3 min read

Here's a script that will ruin your day:

#!/bin/bash
cd /nonexistent/folder
rm -rf *
echo "Done"

The cd fails because the folder doesn't exist. Bash ignores the failure. rm -rf * runs in whatever directory you were already in. The script prints "Done." You have no idea anything went wrong until you notice your files are gone.

This isn't a contrived example. This is the actual failure mode of every bash script that doesn't have error handling. Bash's default behavior is to keep running after errors. Every command after a failure executes as if everything is fine. It's the most dangerous default in the entire language.

Two lines fix it.


The Template

#!/bin/bash
set -euo pipefail

trap 'echo "Error on line $LINENO — script stopped." >&2' ERR

# Your script starts here

Add this to the top of every script you write. Every single one. No exceptions.


What Each Flag Does

-e (errexit) — Stop the script immediately if any command exits with a non-zero status. This is the one that prevents the cd + rm disaster above. If cd fails, the script stops right there. rm never runs.

-u (nounset) — Treat any undefined variable as an error. Without this, $UNDEFINED_VAR silently becomes an empty string. Imagine this:

BACKUP_DIR=""  # Oops, forgot to set this
rm -rf "$BACKUP_DIR"/*

That expands to rm -rf /*. That's your entire filesystem. With -u, bash catches the empty variable and stops before the rm ever runs.

-o pipefail — Make a pipeline fail if ANY command in it fails, not just the last one. Without this:

cat nonexistent-file.txt | grep "pattern" | wc -l

cat fails, but grep gets empty input, and wc -l counts zero lines and exits successfully. The pipeline reports success even though the first command failed. With pipefail, the pipeline's exit status is the failure from cat, so -e catches it.


The trap Line

trap 'echo "Error on line $LINENO — script stopped." >&2' ERR

This tells bash: "When an error happens, before you exit, run this command." It prints which line number failed and writes to stderr (>&2). Without this, the script just stops silently and you have to guess which line caused it.

In a 200-line script, "it failed somewhere" is useless. "Error on line 47" is actionable.


The Real-World Difference

Without error handling:

#!/bin/bash
cd /tmp/build
make
make install
echo "Build complete"

If cd fails (the directory doesn't exist), make runs in the wrong directory. If make fails (compilation error), make install installs whatever was there from last time. The script prints "Build complete" regardless.

With error handling:

#!/bin/bash
set -euo pipefail
trap 'echo "Error on line $LINENO" >&2' ERR

cd /tmp/build
make
make install
echo "Build complete"

If cd fails, the script stops and prints "Error on line 5." If make fails, same thing. echo "Build complete" only runs if every single command before it succeeded.


The One Gotcha

set -e changes how you write conditional logic. This fails:

set -e
grep "pattern" file.txt  # exits 1 if pattern not found
echo "This never runs"

grep returns exit code 1 when it finds no matches. With -e, that's treated as an error and the script stops. But you didn't want it to stop — you just wanted to check if the pattern exists.

The fix is to use it in a conditional:

set -e
if grep -q "pattern" file.txt; then
  echo "Found it"
else
  echo "Not found"
fi

When a command is part of an if condition, -e doesn't trigger on its exit code. This is the standard pattern.

Or suppress the exit code explicitly:

grep "pattern" file.txt || true

The || true means "if grep fails, run true instead" — and true always succeeds, so -e doesn't trigger.


Where to Go from Here

Every script on BashSnippets.xyz uses set -euo pipefail and the CHECK="✓" / CROSS="✗" pattern. If you want a head start, the Bash Boilerplate Generator builds a complete script template with error handling, logging, and cleanup traps already configured. Pick your options, copy the output, and start writing from a safe foundation.


Full breakdown with more examples, the trap pattern, and common pitfalls:

bashsnippets.xyz/snippets/bash-error-handling.html

If you only take one thing from this: add set -euo pipefail to the top of every script. It takes 3 seconds and prevents the kind of failures that take hours to recover from.

2k Points34 Badges1 5 28
North Americabashsnippets.xyz
19Posts
23Comments
3Followers
3Connections
Linux user who got tired of Googling the same bash commands every time I sat down at a terminal. Started writing them down. That turned into bashsnippets.xyz -->
a free and growi... Show more
Build your own developer journey
Track progress. Share learning. Stay consistent.
Part 5 of 6 in Bash Snippets Pages

2 Comments

2 votes
1
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelskiverified - Apr 23

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

My Nginx Died at 2 AM and Nobody Noticed for 6 Hours. Now I Have a Watchdog Script

BashSnippets - May 21

Sovereign Intelligence: The Complete 25,000 Word Blueprint (Download)

Pocket Portfolio - Apr 1
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!