Linux Command Line & Bash Scripting Mastery · Lesson

Strict Mode with set -euo pipefail

Enable fail-fast behavior and understand exactly which errors each strict-mode flag catches and misses.

Lesson 1 of 413 steps

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 cp to 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 destructive

The 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 /nonexistent all trigger exit
  • The exit code of the last command in the script becomes the script's exit code
  • Commands in if conditions are exempt — -e does not fire for the test expression
  • Commands followed by || true are 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 variable

Understanding -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 -l
  • cat fails with exit code 1, but wc -l succeeds 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 entirely
  • command || echo "Warning: step failed, continuing" — log and continue
  • command || { 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 / until conditions — the test expression is exempt by design
  • Commands negated with ! — ! false does 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, and pipefail from the calling shell
  • A function's non-zero return causes the caller to exit (when -e is set) — unless the call is in a condition or after ||
  • Command substitution $(): in older Bash, a failing command inside $() may not trigger -e in 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 cleanup or on_error function
  • Register it with trap 'on_error' ERR
  • Optionally also trap EXIT for 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 with set -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"
fi

A 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 including errtrace
  • IFS=$'\n\t' — safer word splitting (avoids splitting on spaces)
  • ERR + EXIT traps for diagnostics and cleanup
  • Explicit default values for optional parameters
  • readonly and local to 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 trap on ERR and EXIT for diagnostics and cleanup
  • Use || true to intentionally tolerate failures
  • Temporarily disable with set +e inside 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.

Free to start

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

  1. Strict Mode with set -euo pipefail
  2. Trap Handlers for Cleanup and Signals
  3. Safe Temporary Files and Lock Directories
  4. Idempotent Scripts and Retry-with-Backoff Logic
← Back to Linux Command Line & Bash Scripting Mastery