シェルスクリプトでディスク使用量を定期監視・自動クリーンアップする設計|閾値アラートと古いログ削除の実装パターン

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトでディスク使用量を定期監視・自動クリーンアップする設計|閾値アラートと古いログ削除の実装パターン
「深夜3時にサービスの応答がなくなった。調べてみると /var がディスクフルで、アプリログが書き込めずにデーモンが止まっていた」
「df -h を毎朝確認しているが、週末や連休は誰も見ていない。今月だけで2回、残り数GBになっていることに後で気づいた」

この問題は、ディスク使用量を自動監視して古いファイルを定期的に削除するシェルスクリプトを1本組み込むだけで、ほぼ解消できます。
この記事では、dfで使用率を数値取得して段階的にアラートを出す関数と、findで古いログを安全に削除するクリーンアップ設計を組み合わせた実装パターンを解説します。trapによるエラー処理と、cronに組み込む際の落とし穴も含め、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みです。

この記事のポイント

・dfの出力から使用率を数値で取得し、80%/90%/95%の段階アラートを実装できる
・findの-mtimeで削除対象を事前確認してから安全に-deleteできる設計
・trapとset -euoでスクリプトの異常終了を確実に検知してsyslogに残せる
・cronで動かすときのPATH・MAILTO・リダイレクトの3点を押さえれば安定する


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

なぜディスク監視スクリプトが必要なのか

本番サーバーでディスクフルが起きると、影響範囲は「ディスクが満杯になった」だけにとどまりません。

ログが書き込めなくなる→アプリが /tmp にも書き出せなくなる→syslogが止まる→SSHセッションの確立に失敗する。こうした連鎖で、「ディスクフルを検知しようとしていた監視ツール自身が動かなくなる」という状況に陥ることがあります。

私が現場で担当していたWebサーバーでも、Apacheのアクセスログが想定より速いペースで成長し、気づいたときには /var が95%を超えていたことがありました。その夜、cronの実行ログが全件詰まっていて復旧に1時間かかりました。

「ディスク容量不足になってから手動対処する」のではなく、あらかじめ自動監視して閾値を超えたら古いファイルを自動削除する仕組みを組み込むことが、安定運用の基本です。

Zabbix・Prometheus・Datadogなどの監視ツールがある環境でも、特定のパーティション(/var/log・/backup 等)のクリーンアップロジックはシェルスクリプトで実装したほうが、監視ツールとの依存を切り離せて保守しやすくなります。

dfコマンドから使用率を数値として取得する

監視スクリプトの出発点は、dfの出力から「使用率の数値だけ」を取り出すことです。

1. df -P でPOSIX形式の出力を得る

df -P は出力をPOSIX形式(長いパス名でも行が折り返されない)で固定します。-h(人間が読みやすいサイズ)はつけず、数値処理に適した出力にします。

# /var パーティションの使用率を確認する df -P /var

実際の出力例(本番サーバー確認結果、ホスト名マスク済み):

Filesystem 1024-blocks Used Available Capacity Mounted on /dev/sda2 52428800 45678934 6749866 88% /var

スクリプトで扱いたいのは「88」という数値だけです。

2. awkで使用率の数値だけを抽出する

df -P の6列目が「Capacity(Use%)」です。awkでパーセント記号を除去して整数値に変換します。

USAGE=$(df -P /var | awk 'NR==2 {gsub(/%/,""); print $5}') echo "${USAGE}" # → 88

NR==2 でヘッダー行(1行目)をスキップし、gsub(/%/,"") で % を除去してから5列目を出力します。変数 USAGE には整数だけが入り、[ "${USAGE}" -ge 80 ] のような比較演算がそのまま使えます。

閾値チェック関数の設計(段階アラート)

使用率の数値が取れたら、次は「いつ何をするか」の判断ロジックを関数化します。段階を設けることで、「警告だけ残す」「クリーンアップを実行する」「強制クリーンアップ」を区別できます。

1. スクリプト冒頭の設定セクションで閾値を変数管理する

閾値をハードコードしないために、設定セクションで変数定義します。後から変更するときも1か所を直すだけで済みます。

#!/bin/bash set -euo pipefail # ─── 設定セクション ─────────────────────────────────────── TARGET_DIR="/var" # 監視対象パーティション THRESHOLD_WARN=80 # 警告閾値(80%以上でログだけ残す) THRESHOLD_CRIT=90 # 危険閾値(90%以上でクリーンアップ実行) THRESHOLD_EMER=95 # 緊急閾値(95%以上で強化クリーンアップ) LOG_DIR="/var/log" # クリーンアップ対象ディレクトリ LOG_RETENTION_DAYS=30 # 通常の保持日数 SCRIPT_LOG="/var/log/disk_monitor.log" # ──────────────────────────────────────────────────────────

2. check_disk_usage()関数の実装

関数の戻り値(return code)で段階を表現することで、呼び出し側のcase文がアクションを判断しやすくなります。

check_disk_usage() { local target="${1:-${TARGET_DIR}}" local usage usage=$(df -P "${target}" | awk 'NR==2 {gsub(/%/,""); print $5}') log_msg "INFO" "${target} 使用率: ${usage}%" if [ "${usage}" -ge "${THRESHOLD_EMER}" ]; then log_msg "EMERGENCY" "${target} が ${usage}% に達しました(緊急クリーンアップ必要)" return 3 elif [ "${usage}" -ge "${THRESHOLD_CRIT}" ]; then log_msg "CRITICAL" "${target} が ${usage}% に達しました" return 2 elif [ "${usage}" -ge "${THRESHOLD_WARN}" ]; then log_msg "WARNING" "${target} が ${usage}% を超えました" return 1 else return 0 fi }

trapとset -eを使ったエラー処理設計

監視スクリプト自身がクラッシュした場合でも原因を追跡できるよう、ログ関数とtrapを必ず組み込みます。

1. タイムスタンプ付きログ関数を定義する

log_msg() { local level="${1}" local message="${2}" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[${timestamp}] [${level}] ${message}" | tee -a "${SCRIPT_LOG}" }

tee -a でターミナルとログファイルの両方に出力します。cronからの実行時はターミナル側の出力が捨てられますが、${SCRIPT_LOG} にはすべての記録が残ります。

2. trap でスクリプト異常終了を検知する

cleanup_on_exit() { local exit_code=$? if [ "${exit_code}" -ne 0 ]; then log_msg "ERROR" "スクリプトが異常終了しました(終了コード: ${exit_code})" logger -t disk_monitor "disk_monitor.sh が異常終了しました(exit: ${exit_code})" fi } trap cleanup_on_exit EXIT

trap cleanup_on_exit EXIT は正常終了・異常終了を問わずEXIT時に必ず呼ばれます。$? で終了コードを確認して非0の場合だけエラーとして記録します。

set -euo pipefail との組み合わせが効果的です。コマンドが1つでも失敗すれば即座にスクリプトが終了し、trapが起動して記録します。cronから「なぜかスクリプトが止まっていた」という問題が追いかけやすくなります。

findを使った古いログファイルの自動削除設計

クリーンアップ処理の核は find コマンドです。削除対象を必ずリストアップしてから削除するステップを分けることで、意図しないファイルを消す事故を防ぎます。

1. find -mtime で削除対象を事前確認する

-mtime +30 は「最終更新から30日を超えたファイル」を意味します。まず -print で対象ファイルを表示し、問題がないことを確認してから削除ステップに進みます。

# 削除対象の確認(ドライランとして先に実行する) find "${LOG_DIR}" -type f \( -name "*.log" -o -name "*.log.gz" -o -name "*.log.[0-9]*" \) -mtime +${LOG_RETENTION_DAYS} -print

実際の確認結果例(ホスト名・IPマスク済み):

/var/log/httpd/access_log.20250710 /var/log/httpd/access_log.20250717.gz /var/log/messages-20250710 /var/log/maillog-20250710 /var/log/audit/audit.log.3

2. cleanup_old_logs()関数の実装

cleanup_old_logs() { local target_dir="${1}" local retention_days="${2}" # 削除対象ファイルを配列に格納(件数確認のため) local -a targets mapfile -t targets < <(find "${target_dir}" -type f \( -name "*.log" -o -name "*.log.gz" -o -name "*.log.[0-9]*" \) -mtime +"${retention_days}" -print) if [ "${#targets[@]}" -eq 0 ]; then log_msg "INFO" "削除対象ファイルなし(${retention_days}日以上古いファイルは存在しない)" return 0 fi # 削除前に合計サイズをログに記録 local total_kb total_kb=$(find "${target_dir}" -type f \( -name "*.log" -o -name "*.log.gz" -o -name "*.log.[0-9]*" \) -mtime +"${retention_days}" -exec du -sk {} + | awk '{total += $1} END {printf "%.1f", total/1024}') log_msg "INFO" "${#targets[@]} 件のファイルを削除します(合計 ${total_kb} MB)" # 削除実行 find "${target_dir}" -type f \( -name "*.log" -o -name "*.log.gz" -o -name "*.log.[0-9]*" \) -mtime +"${retention_days}" -delete log_msg "INFO" "ログクリーンアップ完了" }

mapfile -t targets でファイルリストを配列に格納し、${#targets[@]} で件数を確認してから削除します。0件なら削除ステップをスキップするため、対象ファイルがない状態でも安全です。

古いアーカイブを削除する前に tarコマンドで圧縮アーカイブを作成してからリモートNASへ退避するフローも、長期保存が必要な環境では合わせて検討してください。
シェルスクリプトを体系的に学びたい方は、シェルスクリプト実践ガイド も合わせて活用してほしい。

スクリプト全体の組み立てとcronへの組み込み

ここまでの関数をmain()でまとめ、cronに登録します。

1. main()関数で各処理を結合する

main() { log_msg "INFO" "ディスク監視スクリプト開始" # check_disk_usage は set -e の影響を受けないよう || true で受ける local disk_status=0 check_disk_usage "${TARGET_DIR}" || disk_status=$? case "${disk_status}" in 0) log_msg "INFO" "使用率は正常範囲内です" ;; 1) log_msg "WARNING" "警告閾値を超えました。通常クリーンアップを実行します" cleanup_old_logs "${LOG_DIR}" "${LOG_RETENTION_DAYS}" ;; 2) log_msg "CRITICAL" "危険閾値を超えました。通常クリーンアップを実行します" cleanup_old_logs "${LOG_DIR}" "${LOG_RETENTION_DAYS}" ;; 3) log_msg "EMERGENCY" "緊急閾値を超えました。短期クリーンアップを実行します" cleanup_old_logs "${LOG_DIR}" 7 ;; *) log_msg "ERROR" "check_disk_usage が予期しない終了コードを返しました: ${disk_status}" ;; esac log_msg "INFO" "ディスク監視スクリプト終了" } main "$@"

check_disk_usage || disk_status=$? は、set -euo pipefailの環境で関数が非0を返しても即座にスクリプトが止まらないようにするための慣用表現です。disk_status に戻り値を受け取り、case文でアクションを振り分けます。

2. cronへの登録と環境変数の設定

# /usr/local/bin/disk_monitor.sh に配置して実行権限を付ける chmod 755 /usr/local/bin/disk_monitor.sh # crontab -e で追加する内容 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin MAILTO="" # 毎日 02:00 に実行し、cron自体のログも残す 0 2 * * * /usr/local/bin/disk_monitor.sh >> /var/log/disk_monitor_cron.log 2>&1

cronで実行する際の3点を確認します。

PATHを明示する:cronの実行環境はPATHが短く、/usr/local/bin に置いたツールが見つからないことがある。スクリプトや呼び出すコマンドのフルパスを確認しておく
MAILTO=""を設定する:デフォルトでは標準出力・エラーがrootへメール送信される。不要なメールを防ぐため MAILTO="" にして、ログはスクリプト内で明示的に管理する
>> logfile 2>&1でリダイレクトする:cronが実際に起動したかどうかの外部記録として残しておくと、スクリプト内ログと組み合わせて問題の切り分けがしやすくなる

トラブルシュート

「手動では動くがcronで動かない」典型パターン

最も多いのはPATHの問題です。スクリプト内で loggerawk を使っている場合、通常は /usr/bin にあるため問題ありませんが、追加インストールしたコマンドは見つからないことがあります。

cronのデバッグはスクリプト先頭に set -xenv > /tmp/cron_env.txt を追加し、実行後に /tmp/cron_env.txt の内容と手動実行時の環境を比較する方法が確実です。

「findが意図しないファイルを削除した」対処

-mtime +30 は「最終更新から30日を超えたファイル」を対象にします。アクセス時刻(-atime)や変更時刻(-ctime)ではなく修正時刻(-mtime)を使っていることを確認します。

また、削除対象のディレクトリを LOG_DIR="/var/log" のように明示し、/var などの広すぎる範囲を指定しないことが鉄則です。スクリプト化する前に必ず -print だけで1度実行して対象ファイルを目視確認してください。

「スクリプトが途中で止まっている」場合

trap cleanup_on_exit EXIT を組み込んでいれば、${SCRIPT_LOG} に終了コードが記録されます。tail -20 /var/log/disk_monitor.log で直近の実行記録を確認し、どのコマンドで止まったかを追跡します。set -x をデバッグ用に有効にすると、どのコマンドが実行された直後に止まったかをトレースできます。

本記事のまとめ

シェルスクリプトによるディスク監視・自動クリーンアップの設計要点を整理します。
やりたいこと 実装方法
使用率を数値で取得する df -P /var | awk 'NR==2 {gsub(/%/,""); print $5}'
段階アラートを実装する check_disk_usage()で戻り値0/1/2/3を返す設計にする
削除前に対象を確認する find ... -mtime +30 -printで事前確認してから-deleteする
削除件数・サイズを記録する mapfile -t targetsで配列に格納して${#targets[@]}を確認する
異常終了を検知する trap cleanup_on_exit EXITを冒頭に配置してsyslogに記録する
cronで安全に動かす PATH明示・MAILTO=""・>> logfile 2>&1の3点セットを設定する
定期的にディスク監視スクリプトを動かしておくと、突発的なディスクフルへの対処から「なぜ増えたのか」の原因調査まで、ログを元に素早く動けるようになります。本記事のパターンをベースに、自分のサーバー環境のパーティション構成・保持ポリシー・通知先に合わせてカスタマイズしてください。

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

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

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

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

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

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

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

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

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

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。