Bash Error Handling with set -euo pipefail

error-handlingbest-practicesset
4 min read

Quick Answer

By default bash ignores failed commands and keeps running, which lets a single typo cascade into data loss. Adding set -euo pipefail on the second line of any script changes three behaviours: -e exits immediately when any command returns a non-zero exit code, -u treats unset variables as errors instead of silently substituting an empty string, and -o pipefail makes the whole pipeline fail if any stage fails rather than only checking the last command. The classic disaster this prevents: a script that runs cd /nonexistent (fails, ignored), then rm -rf * (now runs in the wrong directory). With set -e the script stops at the failed cd and rm never executes. Adding trap 'echo "Error on line $LINENO" >&2' ERR gives you the exact line number on failure. Works in bash 4.0 and newer — the default on Ubuntu 22.04 LTS, Debian 12, Fedora 39, and macOS Ventura (via homebrew bash).

The Template — Add This to Every Script

This is the safest way to start any bash script. Copy this header and build from here.

bash
#!/bin/bash set -euo pipefail # Print an error message and the line number when something fails trap 'echo "Error on line $LINENO — script stopped." >&2' ERR # Your script starts here

What you just added

set -euo pipefail combines three safety flags into one line. trap ... ERR tells bash what to print when something fails, including the exact line number. Without these, bash will happily run 50 more lines after an error as if nothing happened.

Step-by-Step Setup

Step 1 — Create a new script file

bash
nano safe-script.sh

Paste the template above, then press Ctrl+X → Y → Enter to save.

Step 2 — Add it to an existing script

Open any script you already have and paste set -euo pipefail and the trap line immediately after #!/bin/bash:

bash
#!/bin/bash set -euo pipefail trap 'echo "Error on line $LINENO — script stopped." >&2' ERR # ... rest of your existing script unchanged

That is all you need. Two lines change every script from "silently broken" to "stops and tells you where it failed."

Step 3 — Make it executable and run it

bash
chmod +x safe-script.sh ./safe-script.sh

The script exits immediately on the first failed command and prints the line number. Without an error, it completes normally — you will not see any extra output from the trap.

Step 4 — Test the error trap deliberately

Add a failing command to verify the trap fires:

bash
#!/bin/bash set -euo pipefail trap 'echo "Error on line $LINENO — script stopped." >&2' ERR ls /this/path/does/not/exist # will fail echo "This line should never print"

Run it. You should see:

text
ls: cannot access '/this/path/does/not/exist': No such file or directory Error on line 5 — script stopped.

The second echo never executes. That is set -e working correctly. Remove the test line and build from there.

What Each Flag Does

FlagLong formWhat it does
-eset -o errexitStop immediately if any command exits non-zero. Without this, bash ignores failures and keeps running.
-uset -o nounsetTreat any unset variable as an error. Without this, $UNDEFINED_VAR silently becomes an empty string — which can cause catastrophic path errors.
-o pipefailpipeline failureA pipeline fails if ANY command in it fails, not just the last one. Without this, false | true succeeds because only true is checked.

Why This Actually Matters — Real Examples

The dangerous default (no flags)

bash
#!/bin/bash # No safety flags — this is dangerous cd /nonexistent/folder # This FAILS silently rm -rf * # This runs in the WRONG directory echo "Done" # Script reports success

What happens without -e

The cd fails. Bash ignores it and stays in the current directory. rm -rf * then runs in whatever directory you were already in — potentially deleting everything there. The script prints "Done." You have no idea anything went wrong.

With set -e (safe version)

bash
#!/bin/bash set -euo pipefail trap 'echo "Error on line $LINENO" >&2' ERR cd /nonexistent/folder # This FAILS → script stops here rm -rf * # This NEVER runs

What happens with -e

The cd fails. The trap fires and prints "Error on line 5." The script stops completely. rm -rf * never runs. Your files are safe.

The undefined variable trap (why -u matters)

bash
#!/bin/bash # Without -u, this is a disaster waiting to happen TARGET_DIR="/home/user/backups" # Typo: TARGER_DIR instead of TARGET_DIR rm -rf "$TARGER_DIR/"* # Expands to: rm -rf /*

Without -u, a typo can delete your entire system

$TARGER_DIR is undefined so bash replaces it with an empty string. The command becomes rm -rf /* — which attempts to delete everything from the root of your filesystem. With set -u, bash would immediately error: "TARGER_DIR: unbound variable" and stop.

Allowing Commands to Fail Intentionally

Sometimes you want a command to fail without stopping the script. There are two clean ways to do this:

Method 1 — Append || true

bash
#!/bin/bash set -euo pipefail # These are allowed to fail — the || true catches the error mkdir /tmp/myfolder || true # OK if folder already exists rm /tmp/oldfile.log || true # OK if file doesn't exist grep "keyword" file.txt || true # OK if no match found

Method 2 — Use an if statement

bash
#!/bin/bash set -euo pipefail # if/then handles the failure explicitly if ! mkdir /tmp/myfolder; then echo "Folder already exists — continuing" fi

The trap Command — Know Exactly What Line Failed

Adding trap gives you a stack trace when things go wrong. Here are the most useful versions:

bash
#!/bin/bash set -euo pipefail # Basic — prints which line failed trap 'echo "Error on line $LINENO" >&2' ERR # Verbose — prints line number AND the command that failed trap 'echo "Failed at line $LINENO: $BASH_COMMAND" >&2' ERR # Cleanup on exit — runs whether the script succeeds or fails trap 'rm -f /tmp/lockfile.pid' EXIT

Quick Reference — All Flags

FlagLong formWhat it does
set -eset -o errexitExit immediately on any error
set -uset -o nounsetError on undefined variables
set -o pipefailPipeline fails if any stage fails
set -xset -o xtracePrint every command before running it (debug mode)
set +eTurn off -e temporarily for a block
set +xTurn off debug mode

Frequently Asked Questions

What does set -euo pipefail do in bash?

set -e exits on any error. set -u treats undefined variables as errors instead of silently using empty strings. set -o pipefail makes pipelines fail if any stage fails rather than only checking the last command. Together they make bash scripts safe and predictable.

Why do bash scripts keep running after an error?

By default, bash ignores non-zero exit codes and continues executing. This is a design decision for interactive use — a typo in a shell session shouldn't kill your terminal. But in scripts it's dangerous. set -e changes this behavior so scripts stop on the first failure.

What is the difference between set -e and set -o errexit?

They are identical. set -e is shorthand for set -o errexit. Both cause immediate exit on command failure. The short form is more common in scripts.

How do I handle errors in a bash script properly?

Add set -euo pipefail right after #!/bin/bash. Add trap 'echo Error on line $LINENO' ERR for visibility. For commands that are allowed to fail, append || true or wrap them in if statements.

BashSnippets logo

Written by Anguishe

Creator of BashSnippets.xyz

bashsnippets.xyz/about

Run this script on a real Linux server

Get $200 free credit — DigitalOcean

Get $200 Free →

Affiliate link · we earn a commission

Need a domain for your next project?

Register with Namecheap — free WHOIS privacy included

Check Domain Prices →

Affiliate link · we earn a commission

Related Snippets

Frequently Asked Questions

How do I run this script?

Add set -euo pipefail and a trap line immediately after #!/bin/bash in any existing script, or save the template as safe-script.sh and chmod +x it.

Does this work on macOS?

Yes with bash 4.0+. macOS ships bash 3.2 by default — install newer bash via Homebrew for full pipefail support.

What does set -euo pipefail do in bash?

set -e exits on any error. set -u treats undefined variables as errors. set -o pipefail makes pipelines fail if any stage fails.

How do I handle errors in a bash script properly?

Add set -euo pipefail after #!/bin/bash, add trap for line numbers on failure, and use || true for commands allowed to fail.