シェルスクリプトはLinux環境での自動化やタスク管理に役立ちます。ただし、失敗を表示するだけでは、呼び出し元に成功と誤認されたり、不完全なファイルが後続処理へ渡ったりします。本記事では、終了ステータスの確認、if・case、trap、ログ、再試行を組み合わせ、失敗時に何を止め、何を残すかを具体例で解説します。
対象はLinux上のBashとGNU系コマンドです。[[ ... ]]、ERR、PIPESTATUSなどは、すべてのshに共通ではありません。保存した例はbash example.shで実行してください。特に指定がなければ例は独立しており、set -eを暗黙に前提としません。練習は一般ユーザーで新しい作業ディレクトリを用意し、既存の業務ファイルやシステム設定を対象にしないでください。
シェルスクリプトの基本的なエラー処理
エラーコードの確認と処理
終了ステータスは0が成功、0以外が失敗や特別な状態を表します。意味はコマンドごとに異なります。$?は直前のコマンドの値なので、メッセージ出力などを挟む前に保存します。通常は、コマンドを直接ifの条件にすると、確認する対象が明確になります。
#!/usr/bin/env bash
# 引数で指定した場所を作成。練習用の空ディレクトリ内で実行する。
dir=${1:-"./work dir"}
if mkdir -- "$dir"; then
printf '作成しました: %s\n' "$dir"
else
status=$?
printf 'ディレクトリ作成に失敗: %s (status=%d)\n' "$dir" "$status" >&2
exit "$status"
fiif ! command; thenも失敗の分岐に使えますが、そのthen内の$?は反転後の値です。元の終了ステータスが必要なら、上のようにif command; then ... else status=$?を使います。command; echo ...; status=$?ではechoの結果を保存してしまいます。
set -eオプションの利用と例外
set -e(errexit)は、対象となるコマンドが非0で終了したときにシェルを終了させますが、「すべての失敗で必ず停止」ではありません。ifの条件、while・untilの条件、!、&&や||の途中などには例外があります。次の例では、条件として検査したfalseでは止まらず、最後の単独のfalseで終了します。
#!/usr/bin/env bash
set -e
if false; then
printf 'ここには入りません\n'
fi
false || printf 'この失敗は明示的に処理しました\n'
printf 'ここまでは続行します\n'
false
printf 'ここは実行されません\n'関数をif my_functionやmy_function || ...のように検査すると、関数内でも-eに頼った停止が効かない場合があります。関数自身が重要な操作を検査し、失敗をreturnしてください。また、通常のBashではコマンド置換$(...)内の-e継承にも注意が必要です。set -uは未設定変数の検出、pipefailはパイプラインの判定に使う別の設定であり、まとめて指定しても明示的な失敗処理の代わりにはなりません。詳細はGNU Bashのsetの説明とinherit_errexitの説明を参照してください。
エラーメッセージの出力先を指定
通常の結果は標準出力、診断は標準エラー出力へ分けます。変数をprintfの書式文字列にせず、%sの引数として渡すと、値に含まれるパーセント記号などをそのまま扱えます。コマンド自身の診断をむやみに2>/dev/nullで捨てないことも重要です。
#!/usr/bin/env bash
file=${1:-"./input file.txt"}
printf '読み取り対象: %s\n' "$file"
if cat -- "$file"; then
:
else
status=$?
printf '読み取りに失敗: %s (status=%d)\n' "$file" "$status" >&2
exit "$status"
fiif文を使ったエラー処理の実例
存在確認は分かりやすい案内に役立ちますが、確認後にファイルの状態が変わることもあります。権限やディスク障害などもあるため、実際に行う操作の成否を必ず確認します。
ファイルの存在チェック
#!/usr/bin/env bash
file=${1:-"./input file.txt"}
if [[ ! -f "$file" || ! -r "$file" ]]; then
printf '読み取り可能な通常ファイルではありません: %s\n' "$file" >&2
exit 1
fi
if cat -- "$file"; then
:
else
status=$?
printf 'ファイルの読み取り中に失敗しました\n' >&2
exit "$status"
fi-fや-rだけでは、内容の正しさや、チェック後も同じファイルであることは保証できません。信頼できない利用者が書き換えられる場所を、管理者権限のスクリプトの入出力先にしないでください。
ディレクトリの存在チェック
必要なら作成する、という用途ではmkdir -pを直接検査できます。同じ名前の通常ファイルがある場合などは失敗します。作成済みディレクトリの所有者や権限が意図どおりかどうかは、別途確認が必要です。
#!/usr/bin/env bash
dir=${1:-"./output dir"}
if mkdir -p -- "$dir"; then
printf 'ディレクトリを利用できます: %s\n' "$dir"
else
status=$?
printf 'ディレクトリを準備できません: %s\n' "$dir" >&2
exit "$status"
fiコマンドの成功確認
コピーも同様に、cpの結果をその場で検査します。この短い例のcpは同名ファイルを上書きし得るため、練習用の新しい保存先に限って使ってください。実際のバックアップでは、後述のように実行ごとに保存先を分離します。
#!/usr/bin/env bash
src=${1:?コピー元ファイルを指定してください}
dst=${2:?練習用の新しい保存先ファイルを指定してください}
if cp -- "$src" "$dst"; then
printf 'コピー完了: %s\n' "$dst"
else
status=$?
printf 'コピーに失敗: %s → %s (status=%d)\n' "$src" "$dst" "$status" >&2
exit "$status"
ficase文によるエラー処理の応用例
ユーザー入力に基づくエラー処理
選択肢の分岐だけでなく、標準入力が閉じている場合も処理します。read -rはバックスラッシュを特別扱いせずに読み取り、IFS=は前後の空白を保持します。次は表示だけのデモで、サービスの起動・停止は行いません。
#!/usr/bin/env bash
printf '選択: (1)開始 (2)停止 (3)ステータス\n' >&2
if IFS= read -r option; then
case "$option" in
1) printf '開始を選択しました(デモ)\n' ;;
2) printf '停止を選択しました(デモ)\n' ;;
3) printf 'ステータスを選択しました(デモ)\n' ;;
*) printf '無効な選択です\n' >&2; exit 2 ;;
esac
else
printf '入力を読み取れませんでした\n' >&2
exit 1
fiファイルタイプに基づく処理の分岐
拡張子による分岐は処理の振り分けには使えますが、ファイル内容や安全性の検証ではありません。画像デコーダーなど、実際の処理側でも形式とエラーを検査します。
#!/usr/bin/env bash
file=${1:?ファイル名を指定してください}
case "$file" in
*.txt) printf 'テキスト用の処理へ渡します\n' ;;
*.jpg|*.png) printf '画像用の処理へ渡します\n' ;;
*) printf '未対応の拡張子です: %s\n' "$file" >&2; exit 2 ;;
esacgrepの「一致なし」とエラーを区別する
非0が必ず障害とは限りません。GNU grepは、通常0が一致、1が一致なし、2がエラーです。これを一律に「ファイルが読めない」と扱わず、caseで分けます。ここではテキストファイルを対象とし、-qではなく出力を捨てる形で、-qによる一致時の早期終了を避けています。GNU grepの終了ステータスも確認してください。
#!/usr/bin/env bash
file=${1:?検索対象ファイルを指定してください}
pattern=${2:-ERROR}
if grep -F -- "$pattern" "$file" >/dev/null; then
status=0
else
status=$?
fi
case "$status" in
0) printf '一致する行があります\n' ;;
1) printf '一致する行はありません\n' ;;
*) printf '検索に失敗しました (status=%d)\n' "$status" >&2
exit "$status" ;;
esacパイプラインの途中の失敗を検出する
通常、a | bの終了ステータスは最後のbのものです。Bashのset -o pipefailを有効にすると、すべて成功なら0、それ以外は最も右にある失敗したコマンドの値になります。時間的に最後に失敗したコマンド、という意味ではありません。
#!/usr/bin/env bash
set -o pipefail
producer() {
printf '途中まで生成したデータ\n'
return 7
}
if producer | cat >/dev/null; then
printf '生成処理は成功しました\n'
else
status=$?
printf 'パイプラインが失敗しました (status=%d)\n' "$status" >&2
exit "$status"
fi各段の値が必要なら、パイプラインの直後にstatuses=("${PIPESTATUS[@]}")で保存します。その前にechoや代入を挟むと更新されます。headなどが途中で読み取りを終える構成では、上流のSIGPIPEを想定どおりとするかも設計してください。pipefailだけで、途中出力の取り消しやデータ内容の検証はできません。GNU Bashのパイプライン仕様が基準になります。
トラップ(trap)コマンドの使用方法
trapコマンドの基本
trapはシグナルや終了時の処理を登録します。EXITは終了時の後片付け、INTは割り込み、TERMは終了要求などに使います。成功したechoのあとで引数なしのexitを呼ぶと、意図せず成功扱いになり得るため、終了値を明示します。
#!/usr/bin/env bash
trap 'printf "中断されました\n" >&2; exit 130' INT
trap 'printf "終了要求を受けました\n" >&2; exit 143' TERM
printf '30秒待機します。Ctrl+Cで中断できます。\n'
sleep 30ここではINTを130、TERMを143とする終了値の方針を採用しています。これはシグナルで直接終了した状態と完全に同じではありません。また、Bashが外部コマンドを待っている間はtrapの実行が遅れることがあり、子プロセスの停止や転送まで自動で管理する例ではありません。
一時ファイルのクリーンアップ
固定名の/tmp/temp_file.txtは、別の処理との衝突やシンボリックリンクによる事故につながります。mktemp -dで専用ディレクトリを作り、自分が作成したものだけを削除します。次の例は終了時に元のステータスを保存し、後片付けのコマンドで上書きしません。
#!/usr/bin/env bash
umask 077
work=$(mktemp -d) || exit 1
cleanup() {
status=$?
trap - EXIT
if rm -rf -- "$work"; then
:
else
printf '一時ディレクトリの削除に失敗: %s\n' "$work" >&2
# 主処理が成功していた場合だけ、後片付け失敗を終了値にする。
if [[ "$status" -eq 0 ]]; then status=1; fi
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
if printf 'テスト用データ\n' >"$work/input.txt"; then
:
else
status=$?
exit "$status"
fi
if cat -- "$work/input.txt"; then
:
else
status=$?
exit "$status"
fi
# bash example.sh 7 なら、片付け後も終了ステータスは7。
exit "${1:-0}"この例は通常のスクリプトとして実行し、sourceで読み込まないでください。trapは、捕捉できないSIGKILLやSIGSTOP、電源断などへの保証にはなりません。運用では残存一時ファイルの扱いも決めます。GNU mktempとBashのtrapを参照してください。
特定のシグナルを処理する例
上の例では、HUP・INT・TERMに非0でexitする処理を登録し、後片付けをEXITに集約しています。シグナルごとに同じ削除処理を重複させるより、二重削除や終了値の不整合を防ぎやすくなります。終了時に「正常終了」と表示するのは、主処理と必要な後処理の成功が確定した場合に限ります。
エラー処理を行う関数の作成
エラーログ関数の作成
まずは標準エラー出力へ出す小さな関数を用意すると、ログを呼び出し元で集約できます。関数が失敗を検出したらreturnで通知し、スクリプト全体を終了するかは呼び出し側で決めると再利用しやすくなります。
#!/usr/bin/env bash
log_error() {
printf 'ERROR: %s\n' "$1" >&2
}
check_file() {
if [[ -f "$1" && -r "$1" ]]; then
return 0
else
log_error "読み取り対象を確認してください: $1"
return 1
fi
}
file=${1:-"./input.txt"}
if check_file "$file"; then
printf '事前チェックを通過しました(実際の読み取り結果も要確認)\n'
else
status=$?
exit "$status"
fi汎用的なエラーハンドリング関数の作成
共通化する場合でも、失敗値を保存してからログを出します。次の関数はcpの失敗をそのままreturnします。関数の最後が成功したログ出力になると、本来の失敗が隠れてしまう点に注意してください。
#!/usr/bin/env bash
copy_file() {
local status
if cp -- "$1" "$2"; then
return 0
else
status=$?
printf 'コピー失敗 (status=%d): %s\n' "$status" "$1" >&2
return "$status"
fi
}
src=${1:?コピー元を指定してください}
dst=${2:?練習用の新しい保存先を指定してください}
if copy_file "$src" "$dst"; then
printf 'コピー完了\n'
else
status=$?
exit "$status"
fitrapと組み合わせたエラーハンドリング関数
ERRは予期しない失敗の診断に使えますが、例外をすべて捕捉する機構ではありません。if・!・||など、errexitと同様の除外条件があります。構文エラーやすべての展開エラーを一律に処理するものでもありません。set -EはERRトラップを関数などへ継承しますが、こうした除外条件はなくなりません。
#!/usr/bin/env bash
set -Ee
report_error() {
local status=$1
local line=$2
printf '予期しない失敗: line=%s status=%d\n' "$line" "$status" >&2
exit "$status"
}
trap 'report_error "$?" "$LINENO"' ERR
unexpected_failure() {
return 7
}
unexpected_failure
printf 'ここは実行されません\n'予想できる失敗はifで扱い、ERRは補助的な診断として使い分けます。BASH_COMMANDやset -xでコマンド全体を記録すると、引数中の認証情報などがログに入るおそれがあるため、上の例では行番号と終了値に限定しています。
実践的なシェルスクリプトのエラー処理例
バックアップスクリプトのエラー処理
以下は一時ディレクトリ内だけで完結するコピー練習です。source/*では通常ドットファイルを落とすため、GNU cpの-aとsource/.を使います。保存先は毎回新規作成し、過去のコピーに上書きしません。終了時に練習データごと削除するので、実ファイルのバックアップとしてそのまま使わないでください。
#!/usr/bin/env bash
umask 077
work=$(mktemp -d) || exit 1
cleanup() {
status=$?
trap - EXIT
if rm -rf -- "$work"; then
:
else
printf '練習データを削除できません: %s\n' "$work" >&2
if [[ "$status" -eq 0 ]]; then status=1; fi
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
source_dir="$work/source"
mkdir -- "$source_dir" || exit "$?"
printf '通常ファイル\n' >"$source_dir/note.txt" || exit "$?"
printf '隠しファイル\n' >"$source_dir/.settings" || exit "$?"
backup_dir=$(mktemp -d -- "$work/backup.XXXXXXXXXX") || exit 1
if cp -a -- "$source_dir/." "$backup_dir/"; then
# この小さな練習データについて内容を照合する。
cmp -- "$source_dir/note.txt" "$backup_dir/note.txt" || exit "$?"
cmp -- "$source_dir/.settings" "$backup_dir/.settings" || exit "$?"
printf '通常ファイルと隠しファイルのコピーを確認しました\n'
else
status=$?
printf 'コピーに失敗しました (status=%d)\n' "$status" >&2
exit "$status"
fi実運用ではコピー先をコピー元の外に置き、信頼できる保存場所、実行単位の世代管理、失敗した世代の扱いを決めます。cp -aの成功だけでは、更新中データの整合性、別媒体への保護、復元可能性までは保証できません。属性の保存も権限やファイルシステムに依存し、GNU cpの-aは一部属性の保存失敗を終了値に反映しないことがあります。データベース等は専用のバックアップ手順を使い、復元テストを行ってください。GNU cpの仕様を確認できます。
ユーザーアカウント作成スクリプトのエラー処理
アカウント作成は権限とシステム状態を変える管理作業です。練習としてsudo useraddを実行せず、まず参照だけの例で分岐を確認します。idの失敗は、名前が存在しない場合だけでなく照会基盤の問題などもあり、「存在しないので作成してよい」とは判断できません。
#!/usr/bin/env bash
username=${1:?照会するユーザー名を指定してください}
if id -- "$username"; then
printf '照会できました。アカウントの変更は行いません。\n'
else
status=$?
printf 'ユーザー情報を照会できません (status=%d)。作成は行いません。\n' "$status" >&2
exit "$status"
fi実際にuseraddを組み込む際は、管理者が対象名・権限・既存のローカル/ディレクトリアカウント・ホームディレクトリ・運用ポリシーを確認します。実行後は終了値だけでなく作成結果も確認し、失敗後にホームディレクトリなどの一部が残った場合は調査してから復旧します。無条件の再試行や、失敗したら既存アカウントを削除するようなロールバックは避けてください。
ファイルダウンロードスクリプトのエラー処理
curlは、通常はHTTP 404などの応答を受信できても成功扱いにします。次の例は--failに加えて最終HTTPステータス200を確認し、取得先・リダイレクト先をHTTPSに制限します。新規の専用保存先に.partとして受信し、成功時だけ完成名へ変更するため、失敗した部分ファイルや過去の成果物を取り違えにくくなります。実在する信頼できるダウンロードURLを引数に指定してください。
#!/usr/bin/env bash
url=${1:?信頼できるHTTPSダウンロードURLを指定してください}
umask 077
outdir=$(mktemp -d -- "./download.XXXXXXXXXX") || exit 1
cleanup() {
status=$?
trap - EXIT
if [[ "$status" -ne 0 ]]; then
if ! rm -rf -- "$outdir"; then
printf '未完了データの削除に失敗: %s\n' "$outdir" >&2
fi
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
if http_status=$(curl -q --fail --silent --show-error --location \
--max-redirs 5 --proto '=https' --proto-redir '=https' \
--connect-timeout 10 --max-time 60 \
--output "$outdir/file.part" --write-out '%{http_code}' -- "$url"); then
:
else
status=$?
printf '取得に失敗しました (curl status=%d)\n' "$status" >&2
exit "$status"
fi
if [[ "$http_status" != 200 ]]; then
printf '想定外のHTTPステータス: %s\n' "$http_status" >&2
exit 1
fi
# 必要な署名・ハッシュ・形式の検証は、完成名へ移す前に追加する。
if mv -- "$outdir/file.part" "$outdir/file.bin"; then
printf '取得完了: %s/file.bin\n' "$outdir"
else
status=$?
printf '完成ファイル名への変更に失敗しました\n' >&2
exit "$status"
fiこの例はHTTP 200のファイル取得に限定し、204・206等を成功とみなすAPIにはそのまま使いません。HTTPSとHTTP 200は、期待する内容であることの証明ではありません。配布元が提供する署名や信頼できる経路で得たハッシュなどを検証し、取得したスクリプトを自動実行しないでください。-qは先頭のオプションとして標準設定ファイルの読み込みを抑止します。--failには認証時などの注意事項もあるため、curl公式マニュアルと利用環境のバージョンで確認します。
エラー処理に関するベストプラクティス
一貫したエラーハンドリング
- 想定内の状態(検索結果なし等)、再試行可能な障害、即時停止する障害を分ける。
- 元の終了値を保存し、ログ出力や後片付けによる上書きを防ぐ。
- 関数はreturn、スクリプトの終了判断は呼び出し側、など責務をそろえる。
- 変数展開は原則として二重引用符で囲み、対応するコマンドには
--でオプション終端を指定する。 - 失敗をログに残すだけで成功として続行せず、出力の採用可否を明示する。
エラーメッセージの明確化
診断には「どの処理」「どの対象」「終了値」を含めると調査しやすくなります。ただし、パスやURLにも個人情報・認証トークンが含まれる場合があるため、必要最小限にします。メッセージの文面を機械的に解析するより、終了値や構造化した結果を呼び出し元へ渡す設計を優先します。
ログファイルの利用
ログへ書き込めない場合も考慮します。次はmktempで新しいログを作り、umask 077で他ユーザーからの読み取りを制限する練習例です。既存ファイルの権限はumaskでは変更されません。運用の固定ログは、所有者と権限を管理した専用ディレクトリに置いてください。
#!/usr/bin/env bash
umask 077
log_file=$(mktemp -- "./error-log.XXXXXXXXXX") || exit 1
log_error() {
local timestamp
timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ') || return "$?"
printf '%s ERROR %s\n' "$timestamp" "$1" >>"$log_file"
}
# 終了値7の失敗を模擬する。
simulate_error() { return 7; }
if simulate_error; then
:
else
status=$?
if ! log_error "処理が失敗しました (status=$status)"; then
printf 'ログにも書き込めませんでした\n' >&2
fi
printf '処理失敗。ログ: %s\n' "$log_file" >&2
exit "$status"
fiexitステータスの適切な使用
成功は0、失敗は非0で返し、独自の値を使うなら意味を決めて文書化します。Bashでは終了値は0〜255の範囲で扱われ、126・127やシグナル関連の値には慣習があります。「失敗したのに最後のechoが成功し、スクリプト全体は0」という状態を避けます。上の例はログに成功しても7を返します。
エラー処理のためのテストとデバッグ
正常系だけでなく、次の条件も一時ディレクトリとテスト用コマンドで確認します。実際の管理者アカウントや業務データに障害を起こす必要はありません。権限のテストは一般ユーザーで行い、rootでは同じ結果にならない点にも注意してください。
- 存在しない入力、空の入力、空白や先頭ハイフンを含むファイル名。
- grepの一致・一致なし・読み取りエラー、パイプラインの途中の失敗。
- 主処理が非0でも後片付け後に値が残ること、後片付け自身の失敗。
- HTTPエラー・時間切れ・途中切断で完成ファイルが残らないこと。
- 再試行が上限で止まり、最後の失敗値を返すこと。
エラー処理を強化するためのツール
シェルチェック (ShellCheck)
ShellCheckは引用符の不足や終了値の確認方法などを指摘する静的解析ツールです。インストール済みであれば、まず構文チェックと合わせて使います。解析で問題が出なくても、権限・ネットワーク・外部コマンドの実際の失敗動作は別途テストが必要です。
bash -n example.sh
shellcheck -s bash example.sh判定を直接ifに書く理由はShellCheck SC2181、条件式から呼ぶ関数とset -eの注意点はSC2310にも説明があります。警告は理由を理解して修正し、単に一括抑制しないでください。
デバッグモードの活用
bash -x example.shやset -xは、実行するコマンドと展開後の引数を追跡します。テスト用の無害なデータに限定し、秘密情報を扱う箇所では無効にします。トレースをログへ保存・共有する前にも内容を確認してください。構文確認だけなら、実行を伴わないbash -nを使います。
ログローテーションの設定
logrotateは設定ファイルを置くだけで常時動くわけではなく、cronやsystemd timer等で定期実行されます。次はrootで動く専用スクリプトだけが書くログを想定した設定例で、シェルのコードではありません。一般ユーザーの処理に使う場合は、保存場所・実際の書き手・所有者・グループを運用に合わせて変更します。
# 管理者が内容を確認して設置する設定例
/var/log/myscript.log {
daily
missingok
rotate 7
compress
notifempty
create 0600 root root
}ログには機密情報が入り得るため、全ユーザーが読める644を既定にしません。まずlogrotate --debug ./myscript.logrotateで設定を確認します。このモードはログも状態ファイルも変更しません。プロセスがログを開きっぱなしの場合は、ローテーション後の再オープン手順が必要です。copytruncateにはコピーと切り詰めの間にログを失う可能性があり、安易な解決策にはしません。詳細はlogrotate公式マニュアルで確認してください。
エラーハンドリングライブラリ
外部ライブラリを使う場合は、実在する配布元、提供する関数名、対応Bash、保守状況、固定したバージョンの内容を確認します。sourceはそのファイルを現在のシェルで実行する操作であり、単に関数一覧を読むだけではありません。出所不明のコードをcloneしてすぐsourceする手順は避け、小規模な処理なら本記事のような短い関数から始める方法もあります。
リトライ機能の実装
再試行は一時的な障害に限定し、上限と待ち時間を設けます。次の例では、自分の関数が75を「再試行可」と定義し、最大3回だけ実行します。75がすべての外部コマンドで同じ意味とは限りません。最後の失敗値を保持し、認証エラー・入力誤りなどはすぐ返します。
#!/usr/bin/env bash
retry() {
local attempt status
if [[ "$#" -eq 0 ]]; then
printf '再試行するコマンドを指定してください\n' >&2
return 2
fi
for attempt in 1 2 3; do
if "$@"; then
return 0
else
status=$?
fi
# この例の契約では75だけが一時エラー。
if [[ "$status" -ne 75 || "$attempt" -eq 3 ]]; then
return "$status"
fi
printf '一時エラー。1秒後に再試行します (%d/3)\n' "$attempt" >&2
sleep 1 || return "$?"
done
}
# 外部状態を変更しないテスト用関数。3回目だけ成功する。
attempts=0
mock_operation() {
attempts=$((attempts + 1))
if [[ "$attempts" -lt 3 ]]; then return 75; fi
printf '3回目で成功しました\n'
}
if retry mock_operation; then
printf '処理完了\n'
else
status=$?
printf '再試行を終了しました (status=%d)\n' "$status" >&2
exit "$status"
fi回数の上限は、1回のコマンドが永久に待つことまでは防ぎません。ネットワーク処理には個別の時間制限も設けます。再実行しても結果が重複しない処理(冪等な処理)か確認し、送信・課金・アカウント作成などを無条件で再試行しないでください。相手が処理済みなのに応答だけ失われたケースもあります。また、retry内のコマンドはifの条件になるため、渡す関数の内部でも失敗を明示的に返す設計が必要です。
まとめ
Bashのエラー処理は、コマンドの結果を正しく判定し、終了値を保持して呼び出し元へ返すことが基本です。ifとcaseで想定内の状態を分け、pipefailでパイプラインの判定を補い、trapで自分が作った一時データを片付けます。set -eとERRには例外があるため、万能な保険とは考えないでください。
バックアップ、アカウント管理、ダウンロードでは、単なる「コマンドの成功」と「目的どおりの安全な完了」を区別します。ログを保護し、部分出力を完成品として扱わず、再試行対象と上限を決め、失敗系を一時的なテスト環境で確かめることが、運用しやすいスクリプトにつながります。


コメント