0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

使用 set -euo pipefail 启用严格模式

启用快速失败行为,并准确了解严格模式的每个标志能捕获和遗漏哪些错误。

使用 set -euo pipefail 启用严格模式 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

为什么 Bash 默认会无声失败

默认情况下,即使命令失败,Bash 仍会继续运行。这会导致生产脚本出现细微且难以调试的灾难。

请看下面这个尝试创建备份的脚本:

  • 路径中的拼写错误导致 cp 失败
  • Bash 忽略该失败并继续运行
  • 尽管数据从未备份,脚本仍报告成功

这就是无声失败问题。严格模式通过让 Bash 的行为类似编译型语言来解决此问题:一旦出现问题就立即停止。

#!/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

三个核心标志:set -euo pipefail

在每个脚本开头附近加入以下行即可启用严格模式:

set -euo pipefail

这会启用三个不同的保护机制:

  • -e — 如果任何命令返回非零状态,则立即退出
  • -u — 将未设置的变量视为错误(而不是展开为空字符串)
  • -o pipefail — 如果管道中的任何命令失败,整个管道就会失败,而不只是最后一个命令失败

它们共同构成了严肃 Bash 脚本的标准防御性文件头。每个标志都能捕获一类不同的错误。

#!/usr/bin/env bash
set -euo pipefail

echo "Strict mode is now active"
echo "Every command failure will abort the script"

理解 set -e(errexit)

set -e(也可写作 set -o errexit)会使脚本在某个命令以非零状态退出时立即退出。

需要了解的关键行为:

  • 简单命令:false、grep pattern file(没有匹配结果)以及 ls /nonexistent 都会触发退出
  • 脚本中最后一个命令的退出代码会成为脚本的退出代码
  • if 条件中的命令不受此规则影响——-e 不会对测试表达式生效
  • 后面跟有 || true 的命令也不受此规则影响(见后续内容)

可以将 -e 理解为防止脚本在失败后无声地继续运行的第一道防线。

#!/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"

理解 set -u(nounset)

set -u(也可写作 set -o nounset)会使 Bash 将对未设置变量的任何引用视为致命错误。

没有 -u 时,像使用 $FLENAME 代替 $FILENAME 这样的拼写错误会无声地展开为空字符串,导致命令行为异常,甚至造成危险后果(试想在 $TMPDIR 未设置时执行 rm -rf "$TMPDIR/")。

重要例外:

  • ${VAR:-default} — 安全的默认值替换,不会触发 -u
  • ${VAR:+value} — 条件展开,同样安全
  • 未传入位置参数时,"$@" 和 "$*" 不受此规则影响
#!/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

理解 -o pipefail

没有 pipefail 时,管道的退出状态完全由最后一个命令决定。前面命令的失败会被无声地吞掉。

未启用 pipefail 时的示例:

  • cat /missing/file | wc -l
  • cat 以退出代码 1 失败,但 wc -l 以代码 0 成功
  • 管道返回 0——表示成功!尽管数据已经丢失。

启用 pipefail 后,Bash 会返回最右侧失败命令的退出代码。这样就能发现并捕获管道失败。

注意:pipefail 不是字母标志,必须使用 -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"

有意让命令失败——使用 || true

有时命令允许失败。使用 set -e 时,您必须明确指出哪些失败可以容忍,否则脚本会中止。

惯用的解决方案是 || true,它会追加一个始终成功的后备操作:

  • command || true — 完全忽略失败
  • command || echo "Warning: step failed, continuing" — 记录警告并继续
  • command || { echo "fatal"; exit 1; } — 自定义失败处理

这种模式会在代码中明确表达您的意图:普通命令表示“此命令必须成功”;|| true 表示“此命令可能失败,但没关系”。

#!/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"

set -e 无法捕获什么

set -e 存在一些众所周知的例外和陷阱。了解这些情况可以避免产生错误的信心:

  • if / while / until 条件中的命令——测试表达式按设计不受此规则影响
  • 使用 ! 取反的命令——! false 不会触发退出
  • || 之前的最后一个命令——例如 false || handle_error
  • 特定上下文中的子 Shell 退出状态——例如在某些 Bash 版本中,VAR=$(failing_command)
  • 函数返回值——只有函数中的最后一个命令会计入结果

严格模式不能替代显式的错误检查;它是一张安全网,可以捕获大多数意外失败。

#!/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"

严格模式下的子 Shell 与函数

严格模式设置会由子 Shell 继承,但在函数和命令替换中的行为较为微妙。

关键规则:

  • 函数会从调用它的 Shell 继承 -e、-u 和 pipefail
  • 函数返回非零值会导致调用方退出(当设置了 -e 时)——但如果调用位于条件中或位于 || 之后,则不会退出
  • 命令替换 $():在较旧的 Bash 中,$() 内部失败的命令可能不会触发父 Shell 的 -e;为确保安全,请先赋值,再单独使用
  • 显式子 Shell () 会继承所有标志
#!/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"

将严格模式与错误捕获结合使用

严格模式告诉 Bash 何时停止。在 ERR 上设置 trap,可以让您在脚本退出之前执行清理或诊断操作。

常见模式如下:

  • 在开头设置严格模式
  • 定义 cleanup 或 on_error 函数
  • 使用 trap 'on_error' ERR 注册该函数
  • 还可以选择针对 EXIT 设置捕获,以确保无论成功还是失败都能执行清理

重要:请使用 set -E(大写 E,也称为 errtrace),以便函数和子 Shell 也能继承 ERR 捕获;没有它,捕获操作只会在主 Shell 主体中触发。

#!/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"

在局部禁用严格模式

有时某段代码就是有意写得“不严格”——例如探测可选工具,或运行因非错误原因返回非零值的旧命令。您可以暂时禁用严格模式,并在之后恢复它。

安全模式如下:

  • 使用 set +e 保存状态(禁用 -e),运行代码块,然后使用 set -e 重新启用
  • 或者使用子 Shell ( set +e; ... ),这样就不会影响父 Shell 的标志
  • 风险代码块结束后应立即重新启用标志;让标志保持关闭是产生错误的常见原因

当代码块包含多个命令时,优先使用子 Shell 形式,因为它会在退出时自动恢复标志。

#!/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

完整的严格模式脚本模板

下面是一个适用于生产环境的模板,汇集了本课程介绍的所有严格模式最佳实践:

  • set -Eeuo pipefail — 包含 errtrace 在内的全部四个标志
  • IFS=$'\n\t' — 更安全的单词拆分(避免按空格拆分)
  • 用于诊断和清理的 ERR + EXIT 捕获
  • 为可选参数设置明确的默认值
  • 使用 readonly 和 local 限制变量作用域

请将此模板复制到每个非简单 Bash 脚本的开头,从而立即获得快速失败行为和可追踪的错误。

#!/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 "$@"

知识检查:pipefail 的行为

测试您对 pipefail 如何影响管道退出代码的理解。

回顾:使用 set -euo pipefail 的严格模式

在本课中,您学习了如何使用严格模式,让 Bash 脚本快速失败并明确报告失败。

三个标志及其防护内容:

  • -e(errexit)——任何返回非零状态的命令都会导致退出;在条件中以及 || 之后不适用
  • -u(nounset)——引用未设置的变量时中止;对于可选变量,请使用 ${VAR:-default}
  • -o pipefail——只要管道中的任一阶段失败,整个管道就会失败,而不仅仅是最后一个阶段失败

配套实践:

  • 添加 -E(errtrace),让 ERR 陷阱传递到函数中
  • 在 ERR 和 EXIT 上使用 trap 进行诊断和清理
  • 使用 || true 有意容忍失败
  • 在子 shell 中使用 set +e 临时禁用该功能,以处理旧代码或探测代码

严格模式并非万能药——您需要了解它的例外情况——但它是编写可靠且具有防御性的 Bash 脚本最有效的单一习惯。

常见问题解答

「使用 set -euo pipefail 启用严格模式」课时是免费的吗?

是的 — 「使用 set -euo pipefail 启用严格模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

「使用 set -euo pipefail 启用严格模式」这节课中我会学到什么?

启用快速失败行为,并准确了解严格模式的每个标志能捕获和遗漏哪些错误。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「使用 set -euo pipefail 启用严格模式」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?

能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 set -euo pipefail 启用严格模式
  2. 用于清理和处理信号的陷阱处理器
  3. 安全的临时文件与锁目录
  4. 幂等脚本与带退避的重试逻辑
← 返回 Linux Command Line & Bash Scripting Mastery