【Shell Script】実践:ファイルを自動でバックアップするシェルスクリプトを作る

Shell Script

こんにちは、かつコーチです。

シェル関数・sed/awk・set -eによるエラーハンドリング・cronによる定期実行と、ここまで一通りの部品を解説してきました。

今回はシェルスクリプト編の最終回として、これらの部品を組み合わせ、実際に使える自動バックアップスクリプトを組み立てます。

前提知識ありきで進めるので、各要素の基礎に不安がある場合は、それぞれの回に戻って確認してから読み進めてください。

実践編の全体設計

これまでの部品を組み合わせる

料理に例えるなら、ここまでの各回は「切り方」「火の通し方」「味付け」を個別に練習してきたようなものです。

今回はそれらを組み合わせて、実際に食卓に出せる1皿(=実用に耐えるバックアップシステム)に仕上げます。

具体的には、次の要素を組み合わせます。

  • シェル関数(SH11):バックアップ処理・削除処理を関数として整理する
  • set -euo pipefail(SH13):途中の失敗でスクリプトを確実に止める
  • cron(SH14):毎日決まった時刻に自動実行する

バックアップスクリプトに必要な要件

今回作るスクリプトは、次の要件を満たすものとします。

  1. 指定したディレクトリをtarで圧縮し、タイムスタンプ付きのファイル名で保存する
  2. 保存先の空き容量が足りない場合はエラーとして検知する
  3. 古いバックアップ(7日以上前のもの)を自動削除する
  4. cronで毎日深夜に自動実行する

基本の書き方・実装手順

手順1:バックアップ対象と保存先を決める

まず、バックアップ対象のディレクトリと保存先ディレクトリを変数として定義します。

#!/bin/bash
set -euo pipefail

SOURCE_DIR="/home/user/app/data"
BACKUP_DIR="/home/user/backups"
RETENTION_DAYS=7

RETENTION_DAYSは、バックアップをどれだけの期間残しておくかを示す設定です。

手順2:tarで圧縮してタイムスタンプ付きファイル名を作る関数

バックアップ本体の処理を、関数として切り出します。

create_backup() {
    local timestamp
    timestamp="$(date +%Y%m%d_%H%M%S)"
    local backup_file="${BACKUP_DIR}/backup_${timestamp}.tar.gz"

    echo "[INFO] バックアップを作成します: ${backup_file}"
    tar -czf "$backup_file" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
    echo "[INFO] バックアップ完了: $(du -h "$backup_file" | cut -f1)"
}

tar -czfcは作成、zはgzip圧縮、fは出力ファイル名の指定を意味します。

-Cで圧縮元の親ディレクトリに移動してから相対パスで指定することで、tarファイルの中身が絶対パスまみれにならず、展開時に扱いやすくなります。

手順3:保存先の空き容量をチェックする関数

バックアップ処理の前に、保存先ディスクの空き容量を確認する関数を用意します。

check_disk_space() {
    local required_mb=500
    local available_mb
    available_mb="$(df --output=avail -m "$BACKUP_DIR" | tail -n 1 | tr -d ' ')"

    if (( available_mb < required_mb )); then
        echo "[ERROR] 空き容量が不足しています(残り${available_mb}MB)" >&2
        exit 1
    fi
}

df --output=avail -mで、指定ディレクトリが属するディスクの空き容量をMB単位で取得しています。

set -eが効いているので、このexit 1が実行された時点でスクリプト全体が確実に止まります。

手順4:古いバックアップを自動削除する関数

保存先が増え続けないよう、一定期間より古いファイルを削除する関数も用意します。

cleanup_old_backups() {
    echo "[INFO] ${RETENTION_DAYS}日より古いバックアップを削除します"
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime "+${RETENTION_DAYS}" -print -delete
}

find-mtime +7は、「更新日時が7日より前」のファイルを対象にする条件です。

手順5:関数を組み合わせてtrapとcronで仕上げる

ここまでの関数を組み合わせ、trapでエラー発生時の通知も仕込んだ完成版が次のスクリプトです。

#!/bin/bash
set -euo pipefail

SOURCE_DIR="/home/user/app/data"
BACKUP_DIR="/home/user/backups"
RETENTION_DAYS=7
LOG_FILE="/home/user/logs/backup.log"

trap 'echo "[ERROR] ${LINENO}行目でバックアップ処理が失敗しました" >> "$LOG_FILE"' ERR

check_disk_space() {
    local required_mb=500
    local available_mb
    available_mb="$(df --output=avail -m "$BACKUP_DIR" | tail -n 1 | tr -d ' ')"
    if (( available_mb < required_mb )); then
        echo "[ERROR] 空き容量が不足しています(残り${available_mb}MB)" >&2
        exit 1
    fi
}

create_backup() {
    local timestamp
    timestamp="$(date +%Y%m%d_%H%M%S)"
    local backup_file="${BACKUP_DIR}/backup_${timestamp}.tar.gz"
    echo "[INFO] バックアップを作成します: ${backup_file}"
    tar -czf "$backup_file" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
}

cleanup_old_backups() {
    echo "[INFO] ${RETENTION_DAYS}日より古いバックアップを削除します"
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime "+${RETENTION_DAYS}" -print -delete
}

mkdir -p "$BACKUP_DIR"
check_disk_space
create_backup
cleanup_old_backups
echo "[INFO] バックアップ処理が完了しました"

これをcronに登録すれば、毎日決まった時刻に自動でバックアップが実行されます。

chmod +x /home/user/scripts/backup.sh
crontab -e
# 0 3 * * * /home/user/scripts/backup.sh >> /home/user/logs/backup.log 2>&1

よくあるつまずきポイント・エラー対処

find -mtimeの日数指定を間違えて全部消えかけた実体験

find-mtimeオプションは、符号の付け方で意味が正反対になります。

❌ Before:符号を付け忘れて、意図と違う範囲を指定してしまう

find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime 7 -delete

+を付けずに-mtime 7と書くと、これは「ちょうど7日前」のファイルだけを指す条件になります。

実際に私がやってしまったミスはこの逆パターンで、「7日以内を残す」つもりで-mtime -7ではなく符号なしの7のまま-deleteを実行し、想定より広い範囲のファイルが消えかけたことがあります。

幸い-deleteの前に必ず-printだけで一度リストアップして目視確認する習慣にしていたため、実害は出ずに済みましたが、確認なしでいきなり-deleteを実行していたら重要なバックアップまで消えていたところでした。

✅ After:符号の意味を確認してから、必ずdry-run(-print)で確認する

# +7 = 7日"より前"(古い)ファイルが対象
# まずは-printだけで対象ファイルを確認する
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime "+${RETENTION_DAYS}" -print

# 確認して問題なければ -delete を付けて実行する
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime "+${RETENTION_DAYS}" -delete

-mtime +Nは「N日より古い」、-mtime -Nは「N日以内」、-mtime Nは「ちょうどN日前」という3種類の意味を持ちます。

削除系の操作をfindで組むときは、本番実行の前に必ず-printだけで対象を確認する、というワンクッションを入れることを強くおすすめします。

バックアップ先ディスク容量不足のエラー処理

手順3で用意したcheck_disk_space関数がなかった場合、ディスク容量が不足した状態でtarコマンドが実行されると、書き込み途中で失敗し、次のようなエラーが出ます。

tar: backup_20260908_030001.tar.gz: Wrote only 2048 of 10240 bytes
tar: Error is not recoverable: exiting now

このエラーはset -eによってスクリプトを止めてはくれるものの、不完全なtar.gzファイルがディスク上に残ってしまう問題があります。

事前にcheck_disk_spaceで空き容量をチェックしておくことで、そもそも書き込みを始める前に安全に処理を止められ、壊れたバックアップファイルが残ることも防げます。

応用・一歩先の使い方

リモートサーバーへのバックアップ転送

今回のスクリプトはローカルディスクへの保存までですが、実務ではrsyncコマンドを組み合わせて、別サーバーやNASにもコピーしておくことが一般的です。

sync_to_remote() {
    rsync -avz "$BACKUP_DIR"/ "backup-user@remote-host:/backups/app/"
}

同じサーバー内にしかバックアップがない場合、そのサーバー自体に障害が起きるとバックアップごと失われてしまいます。

「バックアップは複数の場所に残す」という考え方は、シェルスクリプトの技術というより運用設計の話になりますが、実践で自動化を組むときにはあわせて意識しておきたいポイントです。

まとめ

この記事のポイント

  • バックアップスクリプトは、関数化(SH11)・エラーハンドリング(SH13)・cron(SH14)の組み合わせで実用レベルに仕上がる
  • find -mtimeは符号(+-・なし)で意味が変わるため、削除系の操作は必ず-printで事前確認してから-deleteを実行する
  • ディスク容量不足はtar実行後にエラーとして現れるより、事前チェックで防いだほうが安全
  • バックアップの保管場所は、ローカルだけでなくrsyncなどでリモートにも分散させると安心

これでシェルスクリプト編(全15本)は完結です。

シェルの基本文法から制御構文、引数・入出力、関数、そして今回の実践的な自動化まで、一通り実務で使える土台を解説してきました。

ここから先は、実際に自分の作業を1つシェルスクリプトに置き換えてみることで、知識が経験に変わっていきます。

次に読むべき記事

  • シェル関数の定義と使い方(振り返り)
  • Docker編(次シリーズ、近日公開予定)

タグ: Shell, 上級者向け, 実践・自動化

タイトルとURLをコピーしました