シェルスクリプトでバックアップの整合性を自動検証する設計|sha256sumとgzip -tで破損を検知する実践パターン

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > シェルスクリプト > シェルスクリプトでバックアップの整合性を自動検証する設計|sha256sumとgzip -tで破損を検知する実践パターン
「バックアップは取っていたのに、いざ復旧しようとしたらファイルが壊れていて使えなかった。」
サーバー運用の現場でこの話を聞くたびに背筋が冷たくなります。定期的にrsyncやtarでバックアップを取っていても、ディスクエラー・転送中の破損・ファイルシステムの不整合などで、気づかないままアーカイブが壊れていることはあります。

この記事では、バックアップが本当に正常かを確認するシェルスクリプトの設計を解説します。sha256sumによるチェックサム照合とgzip -t / tar -tzfによるアーカイブ構造の検証、さらに実際のリストアテスト(一時ディレクトリへの展開・ファイル数・サイズ確認)を組み合わせ、trap ERRとtrap EXITで障害通知と一時ファイルの自動クリーンアップを実装する実践パターンを紹介します。
RHEL 9.4 / Rocky Linux 9 / Ubuntu 24.04 LTS を想定しています。

この記事のポイント

・sha256sum -c でチェックサムを照合し、バックアップファイルのビット破損を自動検知できる
・tar -tzf でgzip圧縮とtarアーカイブ構造を1コマンドで走査し、読み出し不能な破損を発見できる
・一時ディレクトリへ展開してファイル数・サイズを検証するリストアテストで「実際に展開できる」まで確認できる
・trap EXIT を使うとエラー時・正常終了時の両方で一時ファイル削除と通知を確実に実行できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

バックアップが「取れているつもり」になっていないか

「バックアップは毎晩自動で取っています」と言うシステム管理者に「最後に復元テストをしたのはいつですか?」と聞くと、「……したことがありません」と返ってくることが多いです。

バックアップファイルが壊れる原因は思った以上に多様です。

・HDD/SSDの不良セクタによるサイレントビット破損
・ネットワーク転送中のパケット損失(rsync転送後に破損)
・バックアップ実行中のディスクフル(アーカイブが途中で切れる)
・cronジョブの途中終了(シグナル受信でtarが中断)
・マウントポイントの未設定(バックアップ先が空のローカルディレクトリに書かれてしまう)

ls -la /backup/data.tar.gz を実行してファイルが存在してもサイズが大きくても、中身が壊れていれば障害時には何の役にも立ちません。バックアップの価値はリストアできて初めて証明されます。

バックアップの真の完成形は「リストアできると確認できた時点」です。チェックサム照合やアーカイブ構造の検証だけでも大幅にリスクを下げられますが、最終的には一時ディレクトリへの展開・ファイル数とサイズの確認まで踏み込むことで、「確かにリストアできる」という保証が得られます。

手動での復元テストは工数がかかりますが、「検証専用シェルスクリプト」を整備してcronで定期実行することで、壊れたバックアップを無人で早期発見できます。

バックアップ検証の3つのアプローチ

バックアップ検証には性格の異なるアプローチがあります。用途に合わせて組み合わせて使うのが設計のポイントです。

アプローチ 確認できること コマンド例
チェックサム照合 バックアップ直後から変化がないかを確認(ビット破損検知) sha256sum -c ファイル.sha256
アーカイブ構造検証 アーカイブを展開せずに構造の整合性を確認(読み出し不能の発見) tar -tzf archive.tar.gz > /dev/null
リストアテスト 実際に展開してファイル数・サイズを確認(「復旧できる」の実証) tar xzf archive.tar.gz -C /tmp/restore_test_$$

チェックサム照合はバックアップ時点のハッシュ値と現在のファイルのハッシュ値を比較します。ネットワーク転送後の破損や保存期間中のサイレント破損を発見できます。ただし、バックアップを取った時点ですでに壊れていた場合は見抜けません。

アーカイブ構造検証はtarのブロック構造やgzip圧縮の整合性を実際に走査して確認します。「展開しようとしたら途中でエラー」という問題を、本番リストア前に発見できます。

リストアテストは実際に一時ディレクトリへ展開し、展開後のファイル数と総サイズが基準値以上かを確認します。「チェックサムは正常なのに展開後のデータが欠落していた」という事態を防げます。

3つを組み合わせることで、「バックアップ直後から変化していない」かつ「アーカイブとして読み出せる」かつ「実際にリストアできる」という3段階の保証が得られます。

sha256sumチェックサムで整合性を検証する

1. バックアップ直後にチェックサムファイルを生成する

バックアップスクリプトの最後でチェックサムファイルを生成します。これがあとの照合の基準になります。

#!/bin/bash # backup_with_checksum.sh — バックアップ実行+チェックサム生成 BACKUP_DIR="/backup/daily" TARGET="/var/lib/myapp/data" DATE=$(date +%Y%m%d) ARCHIVE="${BACKUP_DIR}/data_${DATE}.tar.gz" mkdir -p "${BACKUP_DIR}" tar -czf "${ARCHIVE}" "${TARGET}" # チェックサムファイルを生成(ファイル名のみ渡すため cd する) cd "${BACKUP_DIR}" sha256sum "$(basename "${ARCHIVE}")" > "$(basename "${ARCHIVE}").sha256" echo "バックアップ完了: ${ARCHIVE}" echo "チェックサム: $(cat "$(basename "${ARCHIVE}").sha256")"

生成されたチェックサムファイルの中身はこのような形式です。

$ cat /backup/daily/data_20261013.tar.gz.sha256 a3f8c1d9e2b074563...(64桁のSHA-256ハッシュ) data_20261013.tar.gz

設計の注意点:
・チェックサムファイルはアーカイブと別のストレージにコピーしておくのが理想です。同じディスクに入れておくとディスク障害時に両方失います。
・sha256sum はファイルパスもハッシュに含めるため、照合時にカレントディレクトリや絶対パスの違いに注意が必要です(後述)。上記スクリプトでは cd でディレクトリに移動してからファイル名のみを渡しているため、照合時も同じ手順で実行できます。

2. 検証スクリプトでチェックサムを照合する

sha256sum -c(または sha256sum --check)は指定したチェックサムファイルを読み取り、現在のファイルと照合します。

# チェックサムファイルのあるディレクトリに移動してから照合する cd /backup/daily sha256sum -c data_20261013.tar.gz.sha256

以下は実際の実行結果です。正常時と破損時で出力が変わります。

# 正常時 $ sha256sum -c data_20261013.tar.gz.sha256 data_20261013.tar.gz: OK # 破損時(ファイルが途中から変化している) $ sha256sum -c data_20261013.tar.gz.sha256 data_20261013.tar.gz: FAILED sha256sum: WARNING: 1 computed checksum did NOT match

sha256sum -c は照合失敗時に終了コード1を返します。set -e を使ったスクリプトではこのコードを捕捉してtrapを発動させられます。--quietオプションを付けると正常時の出力を抑制し、エラー時のみメッセージを表示します。

gzip -tとtar -tzfでアーカイブ構造を検証する

1. gzip -tで圧縮ヘッダーを検証する

gzip -t は.gz / .tar.gzファイルのgzip圧縮部分のヘッダーとフッターを検査します。ファイルを解凍せずに「正しく圧縮されているか」を確認できます。

# gzip圧縮の整合性を確認する(-vで詳細表示) gzip -t /backup/daily/data_20261013.tar.gz echo "終了コード: $?" # 詳細表示付き gzip -tv /backup/daily/data_20261013.tar.gz

# 正常時(-v 付き) $ gzip -tv /backup/daily/data_20261013.tar.gz /backup/daily/data_20261013.tar.gz: OK # 末尾が切れたファイルの場合 $ gzip -tv /backup/daily/data_20261013.tar.gz /backup/daily/data_20261013.tar.gz: gzip: /backup/daily/data_20261013.tar.gz: unexpected end of file

gzip -t は高速ですが、gzip層の外側しか確認しません。gzip圧縮自体は正常でもtar内部のファイルブロックが壊れている場合は検知できません。

2. tar -tzfでアーカイブ全体を走査する

tar -tzf はgzip解凍後にtarアーカイブ全体のブロック構造を走査します。すべてのファイルエントリを読み込むため、途中ブロックが壊れていれば発見できます。

# tar -tzf でアーカイブ全体を走査(/dev/null に捨てることで内容を表示しない) tar -tzf /backup/daily/data_20261013.tar.gz > /dev/null echo "終了コード: $?"

# 正常時 $ tar -tzf /backup/daily/data_20261013.tar.gz > /dev/null $ echo $? 0 # アーカイブ途中でブロックが壊れている場合 $ tar -tzf /backup/daily/data_20261013.tar.gz > /dev/null tar: Unexpected EOF in archive tar: Error is not recoverable: exiting now $ echo $? 2

gzip -tとtar -tzfの使い分け
・gzip -t:高速(圧縮ヘッダーのみ)。大量ファイルの一括チェックに向く
・tar -tzf:確実(全ブロックを走査)。重要バックアップの精密検証に向く

両方を組み合わせると「圧縮→tar構造」の2層を順に検証できます。
tar コマンドの実用例で基本的なオプションの使い方も確認してください。

リストアテストで展開内容を確認する

チェックサム照合とアーカイブ構造検証だけでは「内容が正しく展開できるか」まで確認できません。実際に一時ディレクトリへ展開し、ファイル数と総サイズが基準値以上かを確認するリストアテストを加えることで、バックアップの信頼性をさらに高められます。

1. 一時ディレクトリへの展開

リストアテスト用の一時ディレクトリには、スクリプトのPID($$)を付加して複数実行時の衝突を防ぎます。

RESTORE_TMP="/tmp/restore_test_$$" # $$ = このスクリプトのPID mkdir -p "${RESTORE_TMP}" # -C オプションで展開先ディレクトリを指定する tar xzf /backup/daily/data_20261013.tar.gz -C "${RESTORE_TMP}" echo "展開完了: ${RESTORE_TMP}"

2. 展開内容のファイル数・サイズ確認

展開後のファイル数と総サイズを基準値と比較します。基準値は事前に正常なバックアップで計測した値を設定します。「通常時の70%を下回ったらアラート」という設定がよく使われます。

EXPECTED_MIN_FILES=100 # 展開後に期待する最低ファイル数 EXPECTED_MIN_BYTES=1048576 # 展開後に期待する最低サイズ(1MB) file_count=$(find "${RESTORE_TMP}" -type f | wc -l) total_bytes=$(du -sb "${RESTORE_TMP}" | cut -f1) echo "展開ファイル数: ${file_count}, 総サイズ: ${total_bytes} bytes" if [ "${file_count}" -lt "${EXPECTED_MIN_FILES}" ]; then echo "ERROR: ファイル数が期待値を下回っています(${file_count} < ${EXPECTED_MIN_FILES})" exit 1 fi if [ "${total_bytes}" -lt "${EXPECTED_MIN_BYTES}" ]; then echo "ERROR: 展開サイズが期待値を下回っています(${total_bytes} < ${EXPECTED_MIN_BYTES})" exit 1 fi echo "内容確認 OK"

3. trap EXITで一時ディレクトリを確実に削除する

リストアテストでは一時ディレクトリが大きくなることがあります。trap cleanup EXIT を使うと、正常終了・エラー終了・Ctrl+Cのどの場合でも一時ディレクトリを確実に削除できます。

RESTORE_TMP="/tmp/restore_test_$$" cleanup() { local exit_code=$? rm -rf "${RESTORE_TMP}" if [ "${exit_code}" -ne 0 ]; then echo "リストアテスト失敗(終了コード: ${exit_code})" >&2 fi } # EXITシグナルに登録(どの終了パターンでも必ず実行される) trap cleanup EXIT

trap 'on_error "$LINENO"' ERR(コマンド失敗時の通知)と trap cleanup EXIT(終了時クリーンアップ)は目的が異なります。リストアテストを含む場合は両方を組み合わせることで、障害通知と一時ファイルの削除を確実に実行できます。

自動検証スクリプトの完全実装

1. trapで検証失敗時の通知を設定する

検証スクリプトに trap を仕込んでおくと、sha256sum照合失敗・tar走査失敗のどちらが起きても、同じ通知処理を確実に走らせられます。

# ERRシグナルで任意のコマンドが非ゼロを返した時に発動 trap 'on_error "$LINENO"' ERR on_error() { local line=$1 echo "[ERROR] 検証失敗 (line: ${line}) — ${CURRENT_FILE}" >&2 # mailコマンドで通知(要sendmail/postfix設定) echo "バックアップ検証失敗: ${CURRENT_FILE} (line: ${line})" \ | mail -s "[ALERT] backup verify failed on $(hostname)" admin@example.com exit 1 }

2. 検証スクリプト全体のコード(リストアテスト対応版)

#!/bin/bash # verify_backup.sh — バックアップ整合性自動検証スクリプト(リストアテスト対応版) # 使い方: ./verify_backup.sh /backup/daily set -euo pipefail BACKUP_DIR="${1:?使い方: $0 バックアップディレクトリ}" LOG_FILE="/var/log/backup_verify.log" NOTIFY_TO="admin@example.com" CURRENT_FILE="" RESTORE_TMP="" # リストアテストの基準値(事前に正常バックアップで計測して設定する) EXPECTED_MIN_FILES=100 # 期待する最低ファイル数 EXPECTED_MIN_BYTES=1048576 # 期待する最低サイズ(1MB) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" } cleanup() { local exit_code=$? if [[ -n "${RESTORE_TMP}" && -d "${RESTORE_TMP}" ]]; then log " クリーンアップ: ${RESTORE_TMP} を削除" rm -rf "${RESTORE_TMP}" fi } on_error() { local line=$1 log "ERROR: 検証失敗 (line: ${line}) — ファイル: ${CURRENT_FILE}" echo "バックアップ検証失敗: ${CURRENT_FILE} (line: ${line}) on $(hostname)" \ | mail -s "[ALERT] backup verify failed" "${NOTIFY_TO}" 2>/dev/null || true } # EXITで一時ディレクトリを確実に削除、ERRで障害通知 trap cleanup EXIT trap 'on_error "$LINENO"' ERR log "検証開始: ${BACKUP_DIR}" # バックアップディレクトリの存在確認 if [[ ! -d "${BACKUP_DIR}" ]]; then log "ERROR: ディレクトリが存在しません: ${BACKUP_DIR}" exit 1 fi FAILED=0 SUCCESS=0 # .sha256 ファイルを基点に対応するアーカイブを検証する for SHA_FILE in "${BACKUP_DIR}"/*.sha256; do [[ -e "${SHA_FILE}" ]] || { log "WARN: .sha256ファイルが見つかりません"; break; } ARCHIVE="${SHA_FILE%.sha256}" CURRENT_FILE="${ARCHIVE}" if [[ ! -f "${ARCHIVE}" ]]; then log "ERROR: アーカイブが存在しません: ${ARCHIVE}" FAILED=$((FAILED + 1)) continue fi log "--- 検証中: $(basename "${ARCHIVE}") ---" # Step 1: sha256sum チェックサム照合 log " [1/3] sha256sum照合..." cd "$(dirname "${ARCHIVE}")" if sha256sum -c "$(basename "${SHA_FILE}")" > /dev/null 2>&1; then log " sha256sum: OK" else log " ERROR: sha256sum照合失敗" FAILED=$((FAILED + 1)) continue fi # Step 2: tar アーカイブ構造の走査 log " [2/3] tar構造走査..." if tar -tzf "${ARCHIVE}" > /dev/null 2>&1; then log " tar -tzf: OK" else log " ERROR: tarアーカイブ破損" FAILED=$((FAILED + 1)) continue fi # Step 3: リストアテスト(実際に展開してファイル数・サイズを確認) log " [3/3] リストアテスト..." RESTORE_TMP="/tmp/restore_test_$$_$(basename "${ARCHIVE}" .tar.gz)" mkdir -p "${RESTORE_TMP}" if tar xzf "${ARCHIVE}" -C "${RESTORE_TMP}" > /dev/null 2>&1; then file_count=$(find "${RESTORE_TMP}" -type f | wc -l) total_bytes=$(du -sb "${RESTORE_TMP}" | cut -f1) log " 展開結果: ファイル数=${file_count}, サイズ=${total_bytes}bytes" if [ "${file_count}" -lt "${EXPECTED_MIN_FILES}" ]; then log " ERROR: ファイル数が期待値を下回っています(${file_count} < ${EXPECTED_MIN_FILES})" FAILED=$((FAILED + 1)) elif [ "${total_bytes}" -lt "${EXPECTED_MIN_BYTES}" ]; then log " ERROR: サイズが期待値を下回っています(${total_bytes} < ${EXPECTED_MIN_BYTES})" FAILED=$((FAILED + 1)) else log " リストアテスト: OK" SUCCESS=$((SUCCESS + 1)) fi else log " ERROR: 展開失敗" FAILED=$((FAILED + 1)) fi # cleanup trapで一時ディレクトリは自動削除される done log "検証完了: 正常=${SUCCESS} / 失敗=${FAILED}" if [[ "${FAILED}" -gt 0 ]]; then echo "バックアップ検証結果: 失敗${FAILED}件 on $(hostname) — ${BACKUP_DIR}" \ | mail -s "[ALERT] backup verify: ${FAILED} failed" "${NOTIFY_TO}" 2>/dev/null || true exit 1 fi exit 0

このスクリプトをcronに登録するとき、バックアップ実行の翌朝に検証が走るよう時刻をずらします。リストアテスト(実際の展開)はディスクを消費するため、/tmp のパーティションに十分な空き容量があることを事前に確認してください。

# バックアップ実行: 毎日 02:00 0 2 * * * /usr/local/bin/backup_with_checksum.sh # チェックサム+構造検証(軽量): 毎日 06:00 0 6 * * * /usr/local/bin/verify_backup.sh /backup/daily # リストアテスト含む完全検証: 毎週月曜 03:00(ディスク消費を考慮して週次) 0 3 * * 1 /usr/local/bin/verify_backup.sh /backup/daily

バックアップ検証を含むシェルスクリプトの設計力を体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える設計パターンを体系的に学べます。

トラブルシュート・よくあるエラー

sha256sum: 照合がFAILEDになるがファイルは壊れていない

sha256sumはファイルパスも含めてチェックサムファイルに記録します。チェックサム生成時と照合時でカレントディレクトリや絶対パスが違うと照合に失敗します。

解決方法: チェックサムファイルのあるディレクトリに cd してから sha256sum -c を実行する。またはスクリプト内で生成時・照合時のどちらもファイル名のみ(パスなし)を使うようにする。

# 生成時:ディレクトリに移動してからファイル名だけで生成 cd /backup/daily sha256sum data_20261013.tar.gz > data_20261013.tar.gz.sha256 # 照合時:同じディレクトリで実行 cd /backup/daily sha256sum -c data_20261013.tar.gz.sha256

tar -tzf が途中でエラーになるがgzip -tはOKと言う

gzip圧縮は正常でも、tar内部のブロック構造が壊れているケースです。gzip -tが通っても tar -tzf が失敗する場合は、アーカイブ内部に実際の問題があります。バックアップスクリプトが途中でkillされたか、書き込み中にディスクフルになったかを調査してください。

展開で「No space left on device」が出る

/tmp のパーティションが枯渇しています。RESTORE_TMP を空き容量の十分な別パーティションに変更します。

# /tmp の空き容量確認 df -h /tmp # Filesystem Size Used Avail Use% Mounted on # tmpfs 1.8G 1.6G 200M 89% /tmp # /var/tmp を代わりに使う(再起動後も残るため手動削除が必要) RESTORE_TMP="/var/tmp/restore_test_$$"

trap cleanup EXIT を設定していても RESTORE_TMP が /var/tmp にある場合、再起動後は自動削除されません。find /var/tmp -name "restore_test_*" -mtime +1 -exec rm -rf {} \; を定期実行するcronを追加して古い残骸を除去してください。

検証スクリプトがERRトラップを通らずに終了する

set -e を設定していても、if文やwhile文の条件式内では終了コードが非ゼロでもtrapが発動しません。条件式の外でコマンドを実行するパターンで回避します。

# NG: if文の条件でset -eのtrapが発動しない if sha256sum -c "${SHA_FILE}"; then ... fi # OK: 別途実行してから結果を判定する sha256sum -c "${SHA_FILE}" # ここで失敗するとtrap ERRが発動する

「バックアップファイルが見つかりません」が出る(cronで実行時)

date コマンドによる日付取得でタイムゾーンの設定によってはバックアップファイル名と一致しない場合があります。タイムゾーンを確認してから対処します。

# タイムゾーン確認 timedatectl | grep "Time zone" # Time zone: Asia/Tokyo (JST, +0900) # バックアップファイルの命名規則を確認 ls -lt /backup/daily/*.tar.gz | head -5 # 最新のファイルを動的に取得する方法(命名規則が不定の場合) ARCHIVE=$(ls -t /backup/daily/*.tar.gz | head -1)

本記事のまとめ

バックアップ整合性検証の設計をまとめます。

やりたいこと コマンド・設計
バックアップ時にチェックサムを生成する sha256sum archive.tar.gz > archive.tar.gz.sha256
チェックサムを照合して破損を検知する sha256sum -c archive.tar.gz.sha256
gzip圧縮の整合性を確認する gzip -t archive.tar.gz
tarアーカイブ全体を走査して検証する tar -tzf archive.tar.gz > /dev/null
実際に展開してリストアテストを行う tar xzf archive.tar.gz -C /tmp/restore_test_$$
展開後のファイル数・サイズを確認する find 展開先 -type f | wc -l / du -sb 展開先
検証失敗時に自動通知を送る trap 'on_error "$LINENO"' ERR でエラーハンドラを登録
一時ファイルを確実に削除する trap cleanup EXIT でクリーンアップ関数を登録
照合時のパス不一致を防ぐ チェックサムファイルのディレクトリに cd してから照合する

バックアップの本当の価値は「リストアできる」ことで初めて証明されます。sha256sum照合・tar構造検証・リストアテストを組み合わせた検証スクリプトをcronで定期実行するだけで、「取れているつもり」のバックアップリスクを大幅に下げられます。検証スクリプトを整備することは、障害対応時間の短縮にもつながります。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。