これまでの連載(入門21〜23)では、ログの保存と標準出力・エラー出力の制御(tee)、cron による定期自動実行と二重起動防止(flock)、そして logger コマンドを用いたOSシステムログ連携を学んできました。
サーバー上で定期的にタスクを自動実行し、その実行履歴を安全に記録・監視する基盤が整ったところで、次に挑戦したいのが定期運用でよくある「データの自動バックアップ」です。
バックアップ処理は、単にアーカイブコマンドを実行するだけなら難しくありません。しかし、サーバー運用の現場では「古いバックアップが溜まってディスク容量が枯渇した」「途中で中断されて中途半端な破損ファイルが残った」といったトラブルを防ぐための安全設計が求められます。
今回は、Linux標準の tar コマンドと find コマンドを組み合わせ、ディスク容量の事前チェックや世代管理(ローテーション)、エラー時の自動後始末を備えた実用的な自動バックアップスクリプトの作成手順を分かりやすく解説します!
バックアップスクリプトに求められる4つの安全要件
手動で1回実行するバックアップと、cron で毎日無人稼働させるバックアップとでは、考慮すべき前提が大きく異なります。長期にわたり安定して運用を続けるためには、以下の4つを考慮する必要があります。
| 要件 | 目的 | 主な対策コマンド・手法 |
|---|---|---|
| 1. 空き容量チェック | バックアップ作成中にディスクが満杯になるのを未然に防ぐ | df コマンドで空き容量を事前に確認 |
| 2. 相対パス指定 | 展開時に意図しないルート直下の上書きが発生するリスクを防ぐ | tar -C オプションによるディレクトリ移動 |
| 3. 世代管理(削除) | 古いバックアップが蓄積してストレージが枯渇するのを防ぐ | find コマンドによる更新日時(-mtime)判定 |
| 4. 異常終了の後始末 | 処理中断時に作成途中の破損ファイルが残らないようにする | trap コマンドによるシグナル検知と自動削除 |
これらの要件をスクリプト内に組み込んでおくことで、サーバーのディスクトラブルを防ぎ、いざという時に役立つ信頼性の高いバックアップスクリプトを作成できます。
tarコマンドによる安全なアーカイブ作成
Linux環境で複数のファイルやディレクトリを1つのファイルにまとめ、圧縮して保存する定番ツールが tar です。
基本オプション「-czf」
バックアップ処理で最も頻繁に使われる tar -czf は、3つの単独オプション(-c、-z、-f)を1つにまとめた指定方法です。それぞれの役割と意味は以下の通りです。
| オプション | 名称・英語 | 主な役割・特徴 |
|---|---|---|
-c | create(作成) | 新しいアーカイブファイルを新規作成します。 |
-z | gzip(圧縮) | アーカイブ化と同時に gzip 形式で圧縮を行います(拡張子は .tar.gz)。 |
-f | file(ファイル名指定) | 作成するアーカイブのファイル名を指定します。 |
注意点:-f オプションの位置ルール-f は「直後に指定された文字列をアーカイブファイル名として受け取る」という仕様を持っています。そのため、オプションをまとめて記述する際は、-f を末尾(一番後ろ)に配置するのが一般的です。
# -f の直後にバックアップファイル名を指定
tar -czf backup.tar.gz /target/dir※なお、圧縮形式を変更したい場合は、-z(gzip)の代わりに -j(bzip2: より高圧縮率・処理時間は長め)や -J(xz: さらに高圧縮率・処理時間は長め)を指定することも可能です。一般的なバックアップ運用では、処理速度と圧縮率のバランスに優れた -z(gzip)の組み合わせが広く採用されています。
タイムスタンプ付きのファイル名設計
定期バックアップでは、実行された日時が一目で分かり、名前が重複しないように date コマンドでタイムスタンプを付与するのが一般的です。
# 実行日時を取得(例: 20260923_083000)
NOW=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="backup_${NOW}.tar.gz"
絶対パスの指定を避け「-C」オプションを使う
tar を使う際に注意したいのが、アーカイブ対象を「絶対パス」で指定してしまうケースです。
# 非推奨例:絶対パスで指定
tar -czf /backup/data.tar.gz /var/www/html
上記のように実行すると、多くのLinux環境では次のような警告が表示されます。
tar: Removing leading `/' from member names
これは、アーカイブを展開した際にルートディレクトリ(/)からの絶対パスでファイルが展開され、既存の重要ファイルが予期せず上書きされてしまう事故を防ぐためのセーフティ機能です。
この警告を回避し、安全な相対パスでアーカイブを作成するには、-C オプションを活用します。-C は「アーカイブ処理を行う前に、一時的に指定ディレクトリへ移動する」機能を持っています。
# 推奨例:-C で親ディレクトリに移動してから相対パスでアーカイブ
tar -czf "/backup/${BACKUP_FILE}" -C "/var/www" "html"
これにより、アーカイブ内には html/... という相対パスで格納されるため、別のサーバーやテスト環境の作業フォルダなど、任意の場所に安心して展開できるようになります。
不要ファイルを除外する「–exclude」オプション
バックアップ対象の中に一時キャッシュや膨大なログファイル、バージョン管理用ディレクトリ(.gitなど)が含まれていると、アーカイブサイズが無駄に肥大化してしまいます。
# .git や一時ファイルを除外してアーカイブ
tar -czf "/backup/${BACKUP_FILE}" \
--exclude=".git" \
--exclude="*.tmp" \
--exclude="logs/*" \
-C "/var/www" "html"
除外ルールを設定しておくことで、ストレージの消費を抑え、バックアップ処理にかかる時間を短縮できます。
補足:--exclude の書き方とクォートの重要性--exclude オプションの指定には、イコールで繋ぐ形式(--exclude="パターン")とスペースで区切る形式(--exclude "パターン")の2通りがありますが、どちらも同等(同じ動作)として解釈されます。
# 形式1: イコールで繋ぐ形式(オプションと値の組み合わせが分かりやすい)
tar -czf backup.tar.gz --exclude=".git" -C /var/www html
# 形式2: スペースで区切る形式
tar -czf backup.tar.gz --exclude ".git" -C /var/www htmlスクリプト内では「オプション名と除外値がセットであること」が視覚的に明確になるため、--exclude="パターン" のようにイコールで記述するスタイルが好まれる傾向があります。
また、*.tmp や logs/* のようにワイルドカード(*)を含める場合は、ダブルクォート(")またはシングルクォート(')で囲むことを推奨します。クォートを付けずに記述すると、コマンドが実行される前にシェル自身がカレントディレクトリ内のファイル名へと展開(グロブ展開)してしまい、意図しない除外パターンとなってしまうトラブルを防ぐためです。
除外対象が多い場合は「–exclude-from(-X)」でファイル指定
除外したいファイルやディレクトリが多数ある場合、コマンドラインに --exclude をいくつも並べるとスクリプトが読みにくくなります。そのような場合は、--exclude-from=ファイル名(短縮形: -X ファイル名)を使い、除外パターンをまとめた外部テキストファイルから一括で読み込ませる方法が便利です。
# 1. 除外リストファイル(例: exclude_list.txt)を作成
# 1行に1つのパターンを記述
.git
*.tmp
logs/*
cache/*
node_modules
# 2. tar コマンドでファイル名を指定して実行
# 長い形式
tar -czf backup.tar.gz --exclude-from="exclude_list.txt" -C /var/www html
# 短縮形式(-X)
tar -czf backup.tar.gz -X "exclude_list.txt" -C /var/www html
この手法を活用すると、シェルスクリプト本体を変更することなく、テキストファイルを編集するだけで除外対象を柔軟に追加・変更できるようになります。
※注意点:tar の環境によっては、リスト内のコメント行(#)や空行もそのままパターン文字として認識される場合があるため、除外リストファイル内には余分な空行やコメントを入れず、パターンのみを記載しておくのが安全です。
findコマンドによる自動世代管理(ローテーション)
バックアップを定期実行していると、古いファイルが日々蓄積されていきます。放置するといずれサーバーのストレージが満杯になり、Webサーバーやデータベースが停止する重大な障害を引き起こしかねません。
そこで活用するのが、条件に一致したファイルを検索・操作できる find コマンドです。
保持期間(日数)を基準にした自動削除
「作成から7日(または30日)を超えた古いバックアップを削除する」という運用ルールの場合、更新日時を条件に判定する -mtime オプションを使用します。
# 7日以上前のバックアップファイルを検索して削除
find "/backup" -maxdepth 1 -name "backup_*.tar.gz" -mtime +7 -delete
各オプションの意味は以下の通りです。
-maxdepth 1:サブディレクトリまで潜らず、バックアップ保存先直下のファイルのみを対象にする安全指定。-name "backup_*.tar.gz":指定した命名規則に一致するファイルのみに限定(誤削除の防止)。-mtime +7:最終更新から7日(24時間 × 7 = 168時間)以上経過しているファイルを選択。-delete:検索に一致したファイルをその場で安全に削除。
日付単位で厳密に判定する「-daystart」オプション
通常の find -mtime +7 は、「コマンドを実行した現在時刻」を起点にして 24時間 × 7(168時間)以上前かどうかを判定します。そのため、例えば「夜23時に作成されたバックアップ」は、7日後の朝9時にスクリプトを実行しても、まだ168時間が経過していないため削除対象になりません。
このような実行時刻による微妙な判定ズレを防ぎ、「カレンダー上の日付(今日の00:00)」を基準に経過日数を計算したい場合に役立つのが -daystart オプションです。
# 今日の00:00を起点にして7日以上前のファイルを削除
find "/backup" -maxdepth 1 -daystart -name "backup_*.tar.gz" -mtime +7 -delete
-daystart を指定すると、当日の00:00を基準に「昨日は1日前」「一昨日は2日前」と直感的な日付単位でカウントされるため、cron の実行タイミングが多少前後しても意図通りの世代ローテーションを行いやすくなります。
※指定順序の注意点:-daystart は、-mtime などの時間判定オプションよりも前(左側)に配置することが推奨されます(GNU findの仕様上、後ろに置くと効果が適用されないか警告の原因になります)。
安全な削除を行うための注意点
自動削除をスクリプトに組み込む際は、変数の未定義による誤削除トラブルに配慮する必要があります。例えば、変数 $BACKUP_DIR が空の状態で find "$BACKUP_DIR" ... を実行すると、思わぬ場所が対象になってしまう恐れがあります。
そのため、入門9で学んだ set -u(未定義変数の参照で即座にエラー停止)を有効にしておくか、以下のようにディレクトリの存在確認を事前に行うのが堅牢な書き方です。
# 保存先ディレクトリが確実に存在することを確認してから実行
if [ -d "${BACKUP_DIR}" ]; then
find "${BACKUP_DIR}" -maxdepth 1 -daystart -name "backup_*.tar.gz" -mtime +7 -delete
fi
事前チェックと異常時のクリーンアップ処理
バックアップ処理が途中で失敗した場合、中途半端なデータがディスクに残るとトラブルの原因になります。また、最初から容量が足りないことが分かっている場合は処理を開始すべきではありません。
空き容量の事前チェック(dfコマンド)
df コマンドを使用すると、バックアップ保存先パーティションの空き容量を取得できます。例えば、「空き容量が最低でも1GB(1048576 KB)以上あるか」を事前に確認するロジックは以下のようになります。
# 保存先ディレクトリの利用可能容量(1KB単位)を取得
AVAILABLE_KB=$(df -P "${BACKUP_DIR}" | awk 'NR==2 {print $4}')
REQUIRED_KB=1048576 # 1GB (1024 * 1024 KB)
if [ "${AVAILABLE_KB}" -lt "${REQUIRED_KB}" ]; then
echo "エラー: ディスクの空き容量が不足しています(空き: ${AVAILABLE_KB} KB)" >&2
exit 1
fi
df -P はPOSIX標準の1行形式で出力するオプションで、環境による改行のズレを防ぎ、awk による確実な数値抽出に役立ちます。
trapによる破損アーカイブの自動消去
圧縮中に手動で強制停止(Ctrl+C)されたり、予期せぬエラーでスクリプトが中断されたりした場合、作りかけのファイルは壊れたまま残ってしまいます。
入門10で学んだ trap コマンドを活用し、「正常終了以外でスクリプトが終了した場合は、作成途中の不完全なファイルを消去する」という後始末関数を設定します。
# 異常終了時のクリーンアップ関数
cleanup_on_failure() {
local exit_code=$?
if [ "${exit_code}" -ne 0 ] && [ -f "${TARGET_ARCHIVE}" ]; then
echo "警告: 異常終了を検知したため、破損した一時バックアップを削除します" >&2
rm -f "${TARGET_ARCHIVE}"
fi
}
# スクリプト終了時(EXIT)にクリーンアップ関数を実行するよう登録
trap cleanup_on_failure EXIT
アーカイブの整合性テスト
アーカイブが正常に完成したかどうかは、tar -tzf で中身の一覧をテスト読み込みすることで検証できます。
# アーカイブの破損チェック(正常に解凍・読み込めるか検証)
if tar -tzf "${TARGET_ARCHIVE}" > /dev/null 2>&1; then
echo "バックアップの整合性を確認しました"
else
echo "エラー: 作成されたバックアップファイルが破損しています" >&2
exit 1
fi
実践:安全設計を備えたバックアップスクリプトの実装
ここまでに解説した要素を1つに統合したサンプルバックアップスクリプト(smart_backup.sh)です。入門23で習得した logger コマンドによるシステムログ連携も組み込んでいます。
#!/usr/bin/env bash
# ==============================================================================
# smart_backup.sh - 安全設計を備えた自動バックアップスクリプト
# ==============================================================================
set -euo pipefail
# --- 設定項目 ---
SOURCE_PARENT="/var/www" # バックアップ対象の親ディレクトリ
TARGET_DIR_NAME="html" # バックアップ対象ディレクトリ名
BACKUP_DIR="/backup" # バックアップ保存先
RETENTION_DAYS=7 # 保持日数(7日以上前のファイルを削除)
MIN_FREE_KB=1048576 # 必要空き容量(1GB)
TAG_NAME="backup_task" # logger用タグ名
# 一意なファイル名とパス
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
TARGET_ARCHIVE="${BACKUP_DIR}/backup_${TARGET_DIR_NAME}_${TIMESTAMP}.tar.gz"
# ログ記録用関数(標準出力とOSログの両方に記録)
log_info() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [INFO] $1"
logger -t "${TAG_NAME}" -p user.info "$1"
}
log_error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [ERROR] $1" >&2
logger -t "${TAG_NAME}" -p user.err "$1"
}
# 異常終了時の後始末
cleanup() {
local status=$?
if [ "${status}" -ne 0 ]; then
log_error "バックアップ処理が異常終了しました(ステータス: ${status})"
if [ -f "${TARGET_ARCHIVE}" ]; then
log_error "作成途中の破損ファイルを削除します: ${TARGET_ARCHIVE}"
rm -f "${TARGET_ARCHIVE}"
fi
fi
}
trap cleanup EXIT
# ------------------------------------------------------------------------------
# 処理本体
# ------------------------------------------------------------------------------
log_info "バックアップ処理を開始します(対象: ${SOURCE_PARENT}/${TARGET_DIR_NAME})"
# 1. 保存先ディレクトリの作成・確認
if [ ! -d "${BACKUP_DIR}" ]; then
mkdir -p "${BACKUP_DIR}"
fi
# 2. ディスク空き容量のチェック
AVAILABLE_KB=$(df -P "${BACKUP_DIR}" | awk 'NR==2 {print $4}')
if [ "${AVAILABLE_KB}" -lt "${MIN_FREE_KB}" ]; then
log_error "空き容量不足のため中止します(空き: ${AVAILABLE_KB} KB, 必要: ${MIN_FREE_KB} KB)"
exit 1
fi
# 3. アーカイブの作成(相対パス・不要ファイル除外)
log_info "アーカイブを作成中: ${TARGET_ARCHIVE}"
tar -czf "${TARGET_ARCHIVE}" \
--exclude=".git" \
--exclude="*.tmp" \
--exclude="cache/*" \
-C "${SOURCE_PARENT}" "${TARGET_DIR_NAME}"
# 4. 整合性チェック
if ! tar -tzf "${TARGET_ARCHIVE}" > /dev/null 2>&1; then
log_error "作成されたアーカイブの整合性検証に失敗しました"
exit 1
fi
log_info "アーカイブの整合性を確認しました"
# 5. 世代管理(古いバックアップの自動整理)
log_info "${RETENTION_DAYS}日以上前のバックアップを整理します"
find "${BACKUP_DIR}" -maxdepth 1 -daystart -name "backup_${TARGET_DIR_NAME}_*.tar.gz" -mtime +"${RETENTION_DAYS}" -delete
log_info "バックアップ処理が正常に完了しました(サイズ: $(du -h "${TARGET_ARCHIVE}" | cut -f1))"
このスクリプトは、事前の容量チェックから整合性テスト、古い世代の自動整理、そして異常時の後始末まで体系的に組み合わせているため、ストレージトラブルや中途半端なデータ残存を心配する必要がありません。
tarコマンドによるバックアップの復元方法
バックアップは作成するだけでなく、障害やデータ破損が発生した際に迅速かつ安全に復元(リストア)できて初めて価値を持ちます。ここでは、tar コマンドを使った安全な復元手順を解説します。
アーカイブ内容の事前確認(-tオプション)
いきなり展開するのではなく、アーカイブ内にどのようなディレクトリ階層やファイルが含まれているかを -t オプションで確認するのが安全です。
# アーカイブ内のファイル一覧を確認
tar -tzf /backup/backup_mydata_20260924_023000.tar.gz
# 特定のファイルが含まれているか検索(grep併用)
tar -tzf /backup/backup_mydata_20260924_023000.tar.gz | grep "config.php"
スクリプト作成時に tar -C を使って相対パスでアーカイブしているため、先頭に余分な / や親ディレクトリ名が含まれず、意図通りの相対パス構成になっていることを事前に確認できます。
一時ディレクトリへのテスト展開(-Cオプション)
本番環境へ直接展開する前に、まずは一時ディレクトリに展開して中身を検証することを推奨します。展開先ディレクトリは -C オプションで指定します。
# 1. 検証用の一時ディレクトリを作成
mkdir -p /tmp/restore_test
# 2. 一時ディレクトリにアーカイブを展開(解凍)
tar -xzf /backup/backup_mydata_20260924_023000.tar.gz -C /tmp/restore_test
# 3. 展開されたファイルを確認
ls -la /tmp/restore_test/mydata
-x:アーカイブを展開(抽出:extract)する基本オプション。-z:gzip形式で圧縮されているアーカイブを自動解凍する。-f:対象のアーカイブファイル名を指定する。-C <ディレクトリ>:指定したディレクトリへ移動してから展開する。
元の場所へのデータ復元と注意点
一時展開での確認が完了したら、本来の配置場所(親ディレクトリ)に対して展開を実行します。
もしくは、一時展開したファイルから必要なファイルを抽出して元の配置場所に戻します。
# 元の親ディレクトリ(例: /var/www)へ展開してリストア
tar -xzf /backup/backup_mydata_20260924_023000.tar.gz -C /var/www
注意点:展開先に同名ファイルが存在する場合、tar コマンドは警告なく上書きします。障害復旧などで既存データの一部が残っている可能性がある場合は、復元前に現在のディレクトリ名をリネームして退避(例: mv /var/www/mydata /var/www/mydata_damaged)しておく運用が安全です。
まとめ
今回は、シェルスクリプトで自動バックアップを安定運用するために欠かせない実装パターンを解説しました。
- 相対パスによるアーカイブ(
tar -C):展開時の上書き事故や不要な警告を防ぐ。 - 保持日数による世代管理(
find -mtime):古いアーカイブを定期的に自動消去し、ディスク枯渇を防ぐ。 - 空き容量事前チェック(
df):処理の途中でディスクがパンクするのを未然に防ぐ。 - 異常終了の後始末(
trap):途中で失敗した場合に破損した中途半端なファイルを残さない。 - 復元手順の事前検証(
tar -xzf -C):いざという時に備え、一時ディレクトリへのテスト展開でリストア可能か確認しておく。
バックアップはストレージを圧迫せずに健全な状態を維持し続けられるかどうかが運用の要となります。ぜひ手元のサーバー環境に合わせてカスタマイズしてみてください。
なお、今回はディレクトリ単位の tar アーカイブと世代管理を中心に扱いましたが、データ容量が極めて大きい場合には「差分だけを高速に同期する rsync」が適していたり、ストレージ媒体全体の複製が必要な場合には「ブロック単位でコピーを行う dd」を活用する場面もあります。
次回以降の連載では、これらのツールを活用したバックアップ手法についても順次取り上げていく予定です!

コメント