Strict Mode with set -euo pipefail
Enable fail-fast behavior and understand exactly which errors each strict-mode flag catches and misses.
Strict Mode with set -euo pipefail is a free Linux Command Line & Bash Scripting Mastery lesson on CoddyKit. This is lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Linux Command Line & Bash Scripting Mastery learning path, and your progress syncs across the web and the CoddyKit app. The Linux Command Line & Bash Scripting Mastery course includes 4 lessons in total.
Why Bash Fails Silently by Default
By default, Bash keeps running even when commands fail. This leads to subtle, hard-to-debug disasters in production scripts.
Consider this script that tries to create a backup:
- A typo in a path causes
cpto fail - Bash ignores the failure and continues
- The script reports success even though data was never backed up
This is the silent failure problem. Strict mode solves it by making Bash behave like a compiled language: stop immediately when something goes wrong.
#!/usr/bin/env bash
# Without strict mode — dangerous default behavior
cp /important/data /backups/data # fails (path doesn't exist)
echo "Backup complete" # still prints — false confidence!
rm -rf /tmp/staging # still runs — potentially destructiveThe Three Core Flags: set -euo pipefail
Strict mode is enabled by placing this line near the top of every script:
set -euo pipefail
This activates three distinct guards:
-e— Exit immediately if any command returns a non-zero status-u— Treat unset variables as an error (instead of expanding to empty string)-o pipefail— A pipeline fails if any command in it fails, not just the last one
Together they form the standard defensive header for serious Bash scripts. Each flag catches a different class of bug.
#!/usr/bin/env bash
set -euo pipefail
echo "Strict mode is now active"
echo "Every command failure will abort the script"Understanding set -e (errexit)
set -e (also written as set -o errexit) causes the script to exit immediately when a command exits with a non-zero status.
Key behaviors to know:
- Simple commands:
false,grep pattern file(no match),ls /nonexistentall trigger exit - The exit code of the last command in the script becomes the script's exit code
- Commands in
ifconditions are exempt —-edoes not fire for the test expression - Commands followed by
|| trueare also exempt (see next scenes)
Think of -e as your first line of defense against silently continuing after a failure.
#!/usr/bin/env bash
set -e
echo "Before failure"
ls /this/path/does/not/exist # exits here with code 2
echo "This line never runs"Understanding set -u (nounset)
set -u (also written as set -o nounset) makes Bash treat any reference to an unset variable as a fatal error.
Without -u, a typo like $FLENAME instead of $FILENAME silently expands to an empty string, causing commands to behave unexpectedly — or dangerously (imagine rm -rf "$TMPDIR/" when $TMPDIR is unset).
Important exceptions:
${VAR:-default}— safe default substitution, does not trigger-u${VAR:+value}— conditional expansion, also safe"$@"and"$*"are exempt when no positional arguments are passed
#!/usr/bin/env bash
set -euo pipefail
# Safe: provide a default for optional vars
OUTPUT_DIR="${1:-/tmp/output}"
LOG_LEVEL="${LOG_LEVEL:-info}"
echo "Writing to: $OUTPUT_DIR"
echo "Log level: $LOG_LEVEL"
# This would abort the script:
# echo "$UNDEFINED_VAR" # bash: UNDEFINED_VAR: unbound variableUnderstanding -o pipefail
Without pipefail, a pipeline's exit status is determined solely by the last command. Earlier failures are silently swallowed.
Example without pipefail:
cat /missing/file | wc -lcatfails with exit code 1, butwc -lsucceeds with code 0- The pipeline returns 0 — success! Even though data was lost.
With pipefail enabled, Bash returns the exit code of the rightmost command that failed. This makes pipeline failures visible and catchable.
Note: pipefail is not a letter flag — it must be set with -o pipefail.
#!/usr/bin/env bash
set -euo pipefail
# With pipefail: this aborts if grep finds nothing (exit 1)
# grep returns 1 when no match found
ps aux | grep "[n]ginx" | awk '{print $2}'
echo "If we reach here, nginx is running"Intentional Command Failures — Using || true
Sometimes a command is allowed to fail. With set -e, you must be explicit about tolerated failures; otherwise the script aborts.
The idiomatic solution is || true, which appends a fallback that always succeeds:
command || true— ignore failure entirelycommand || echo "Warning: step failed, continuing"— log and continuecommand || { echo "fatal"; exit 1; }— custom failure handling
This pattern makes your intent explicit in the code: a plain command means "this must succeed"; a || true means "this may fail and that's okay".
#!/usr/bin/env bash
set -euo pipefail
# Remove temp dir if it exists — OK if it doesn't
rm -rf /tmp/my_workspace || true
mkdir -p /tmp/my_workspace
# Check if a service is running — OK if not
if systemctl is-active --quiet nginx 2>/dev/null || true; then
echo "nginx is active"
fi
# Grep that may find nothing — OK
grep 'ERROR' /var/log/app.log || true
echo "Done"What set -e Does NOT Catch
set -e has well-known exceptions and pitfalls. Understanding them prevents false confidence:
- Commands in
if/while/untilconditions — the test expression is exempt by design - Commands negated with
!—! falsedoes not trigger exit - The last command before
||— e.g.false || handle_error - Subshell exit status in certain contexts — e.g.
VAR=$(failing_command)in some Bash versions - Function return values — only the last command in a function counts
Strict mode is not a replacement for explicit error checking — it is a safety net that catches the majority of accidental failures.
#!/usr/bin/env bash
set -euo pipefail
# These do NOT trigger -e:
if false; then echo "never"; fi # -e exempt in conditions
! false # negation exempts
false || echo "handled" # || exempts the left side
# This DOES trigger -e (no condition, no ||):
# false
echo "Script continues after exempted failures"Subshells and Functions with Strict Mode
Strict mode settings are inherited by subshells but behave subtly in functions and command substitutions.
Key rules:
- Functions inherit
-e,-u, andpipefailfrom the calling shell - A function's non-zero return causes the caller to exit (when
-eis set) — unless the call is in a condition or after|| - Command substitution
$(): in older Bash, a failing command inside$()may not trigger-ein the parent; assign then use separately to be safe - Explicit subshells
()inherit all flags
#!/usr/bin/env bash
set -euo pipefail
setup_workspace() {
local dir="$1"
mkdir -p "$dir" # fails here if permissions denied
cd "$dir"
echo "Ready in $(pwd)"
}
# Safe pattern: assign result then use it
TODAY=$(date +%Y-%m-%d) # capture separately
WORKDIR="/tmp/run_${TODAY}"
setup_workspace "$WORKDIR"
echo "Workspace: $WORKDIR"Combining Strict Mode with Error Trapping
Strict mode tells Bash when to stop. A trap on ERR lets you run cleanup or diagnostics before the script exits.
The common pattern is:
- Set strict mode at the top
- Define a
cleanuporon_errorfunction - Register it with
trap 'on_error' ERR - Optionally also trap
EXITfor guaranteed cleanup regardless of success or failure
Important: use set -E (capital E, also called errtrace) so that the ERR trap is also inherited by functions and subshells — without it, traps only fire in the main shell body.
#!/usr/bin/env bash
set -Eeuo pipefail
on_error() {
local exit_code=$?
local line_number=${BASH_LINENO[0]}
echo "ERROR: command failed with code ${exit_code} at line ${line_number}" >&2
}
cleanup() {
echo "Cleaning up temporary files..." >&2
rm -rf /tmp/my_run_dir 2>/dev/null || true
}
trap on_error ERR
trap cleanup EXIT
mkdir -p /tmp/my_run_dir
echo "hello" > /tmp/my_run_dir/output.txt
cat /tmp/my_run_dir/output.txt
echo "Done"Disabling Strict Mode Locally
Sometimes a block of code is intentionally "messy" — for example, probing for optional tools or running legacy commands that return non-zero for non-error reasons. You can temporarily disable strict mode and restore it afterward.
The safe pattern:
- Save state with
set +e(disables-e), run the block, then re-enable withset -e - Or use a subshell
( set +e; ... )so the parent shell's flags are never affected - Always re-enable flags as soon as the risky block ends — leaving flags off is a common source of bugs
Prefer the subshell form when the block involves multiple commands, as it automatically restores flags on exit.
#!/usr/bin/env bash
set -euo pipefail
# Probe for optional tools without aborting
HAS_JQ=false
(
set +e
command -v jq > /dev/null 2>&1
[[ $? -eq 0 ]] && echo "jq_found"
) && HAS_JQ=true || true
if [[ "$HAS_JQ" == "true" ]]; then
echo "jq is available — using JSON output"
else
echo "jq not found — using plain text"
fiA Complete Strict-Mode Script Template
Here is a production-ready template that brings together all the strict-mode best practices covered in this lesson:
set -Eeuo pipefail— all four flags includingerrtraceIFS=$'\n\t'— safer word splitting (avoids splitting on spaces)- ERR + EXIT traps for diagnostics and cleanup
- Explicit default values for optional parameters
readonlyandlocalto limit variable scope
Copy this template at the start of every non-trivial Bash script to immediately benefit from fail-fast behavior and traceable errors.
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
# ── Constants ────────────────────────────────────────────
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# ── Trap handlers ────────────────────────────────────────
err_handler() {
echo "[${SCRIPT_NAME}] ERROR on line ${BASH_LINENO[0]}: exit ${?}" >&2
}
cleanup() {
echo "[${SCRIPT_NAME}] Exiting" >&2
}
trap err_handler ERR
trap cleanup EXIT
# ── Defaults ─────────────────────────────────────────────
ENV="${1:-production}"
MAX_RETRIES="${MAX_RETRIES:-3}"
# ── Main ─────────────────────────────────────────────────
main() {
echo "Running in env=${ENV}, max_retries=${MAX_RETRIES}"
echo "Script dir: ${SCRIPT_DIR}"
}
main "$@"Knowledge Check: pipefail Behavior
Test your understanding of how pipefail affects pipeline exit codes.
Recap: Strict Mode with set -euo pipefail
In this lesson you learned how to make Bash scripts fail fast and fail loudly using strict mode.
The three flags and what they guard:
-e(errexit) — exits on any non-zero command status; exempt in conditions and after||-u(nounset) — aborts on unset variable references; use${VAR:-default}for optional vars-o pipefail— makes an entire pipeline fail if any stage fails, not just the last
Complementary practices:
- Add
-E(errtrace) so ERR traps propagate into functions - Use
traponERRandEXITfor diagnostics and cleanup - Use
|| trueto intentionally tolerate failures - Temporarily disable with
set +einside subshells for legacy or probe code
Strict mode is not a silver bullet — know its exceptions — but it is the single most effective habit for writing reliable, defensive Bash scripts.
Learn Bash with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 22
- Lessons
- 88
Frequently Asked Questions
Is the “Strict Mode with set -euo pipefail” lesson free?
Yes — the full text of “Strict Mode with set -euo pipefail” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Linux Command Line & Bash Scripting Mastery course, upgrade to CoddyKit PRO. The Linux Command Line & Bash Scripting Mastery course includes 4 lessons in total.
What will I learn in “Strict Mode with set -euo pipefail”?
Enable fail-fast behavior and understand exactly which errors each strict-mode flag catches and misses. You practise Linux Command Line & Bash Scripting Mastery with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Linux Command Line & Bash Scripting Mastery?
No prior experience is required. Linux Command Line & Bash Scripting Mastery on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 4.
How long does the “Strict Mode with set -euo pipefail” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Linux Command Line & Bash Scripting Mastery lesson?
Yes. Every Linux Command Line & Bash Scripting Mastery lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Strict Mode with set -euo pipefail
- Trap Handlers for Cleanup and Signals
- Safe Temporary Files and Lock Directories
- Idempotent Scripts and Retry-with-Backoff Logic