set -euo pipefailによる厳格モード
fail-fast動作を有効にし、各厳格モードフラグが捕捉するエラーと見逃すエラーを正確に理解します。
「set -euo pipefailによる厳格モード」はCoddyKit上の無料DevOps Bootcampレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDevOps Bootcamp学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DevOps Bootcampコースには全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 destructive3つの基本フラグ:set -euo pipefail
厳格モードは、すべてのスクリプトの先頭付近に次の行を置いて有効にします。
set -euo pipefail
これにより、3つの異なる安全機構が有効になります。
-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がない場合、$FILENAMEの代わりに$FLENAMEとタイプミスすると、空文字列に黙って展開されます。その結果、コマンドが予期せぬ、あるいは危険な動作をする可能性があります($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 -lcatは終了コード1で失敗しますが、wc -lはコード0で成功します- データが失われているにもかかわらず、パイプラインは0(成功)を返します。
pipefailを有効にすると、Bashは失敗したコマンドのうち最も右側にあるものの終了コードを返します。これにより、パイプラインの失敗を可視化し、検出できるようになります。
注意:pipefailは1文字のフラグではないため、-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です- 特定のコンテキストにおけるサブシェルの終了ステータス — たとえば一部の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"厳格モードでのサブシェルと関数
厳格モードの設定はサブシェルに継承されますが、関数やコマンド置換では注意が必要です。
主なルール:
- 関数は呼び出し元のシェルから
-e、-u、pipefailを継承します - 関数がゼロ以外を返すと、(
-eが設定されている場合)呼び出し元は終了します。ただし、条件内または||の後で呼び出した場合を除きます - コマンド置換
$():古いBashでは、$()内の失敗したコマンドが親シェルの-eを発動させない場合があります。安全のため、代入と使用を分けてください - 明示的なサブシェル
()はすべてのフラグを継承します
#!/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とも呼ばれます)を使用して、ERRトラップも関数やサブシェルに継承されるようにしてください。これがない場合、トラップはメインシェル本体でしか発動しません。
#!/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で再有効化します- または、サブシェル
( set +e; ... )を使い、親シェルのフラグに影響を与えないようにします - 危険なブロックが終わったら、必ずすぐにフラグを再有効化します。フラグを無効のままにすることは、バグのよくある原因です
複数のコマンドを含むブロックでは、終了時にフラグが自動的に復元されるため、サブシェル形式を推奨します。
#!/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を含む4つのフラグをすべて設定します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スクリプトをすぐに失敗させ、エラーを明確に報告する方法を学びました。
3つのフラグと、それぞれが防ぐ問題:
-e(errexit)— いずれかのコマンドがゼロ以外のステータスを返すと終了します。ただし、条件式内や||の後では例外となります-u(nounset)— 未設定の変数を参照すると中止します。任意の変数には${VAR:-default}を使用します-o pipefail— 最後の段階だけでなく、パイプライン内のいずれかの段階が失敗した場合に、パイプライン全体を失敗させます
組み合わせて使うプラクティス:
-E(errtrace)を追加して、ERRトラップが関数内にも伝播するようにしますERRとEXITにtrapを使用して、診断とクリーンアップを行います- 意図的に失敗を許容するには
|| trueを使用します - レガシーコードや検査用コードでは、サブシェル内で
set +eを使用して一時的に無効にします
厳格モードは万能ではなく、例外を把握しておく必要があります。それでも、信頼性が高く防御的なBashスクリプトを書くうえで、最も効果的な習慣です。
よくある質問
「set -euo pipefailによる厳格モード」レッスンは無料ですか?
はい。「set -euo pipefailによる厳格モード」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DevOps Bootcampコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DevOps Bootcampコースには全4レッスンが含まれています。
「set -euo pipefailによる厳格モード」で何を学びますか?
fail-fast動作を有効にし、各厳格モードフラグが捕捉するエラーと見逃すエラーを正確に理解します。 ブラウザで直接実行するハンズオンコードでDevOps Bootcampを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
DevOps Bootcampを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのDevOps Bootcampは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「set -euo pipefailによる厳格モード」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このDevOps Bootcampレッスンでコードを書いて実行できますか?
はい。すべてのDevOps Bootcampレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- set -euo pipefailによる厳格モード
- クリーンアップとシグナル処理のためのtrapハンドラー
- 安全な一時ファイルとロックディレクトリ
- 冪等なスクリプトと指数バックオフによるリトライ