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 DevOps Bootcamp lesson on CoddyKit — 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 DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
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.
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, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.
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 DevOps Bootcamp 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 DevOps Bootcamp?
No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
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 DevOps Bootcamp lesson?
Yes. Every DevOps Bootcamp 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