シェルスクリプトでメンテナンスウィンドウを実装する設計|時刻・曜日チェックで作業時間外の実行を制御する方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトでメンテナンスウィンドウを実装する設計|時刻・曜日チェックで作業時間外の実行を制御する方法
「本番作業は深夜0時~6時のみ」「土日は自動処理を止めたい」——運用ルールとして決まっているのに、cronで動くスクリプトが時間帯を問わず実行されて困ったことはないでしょうか。

定期実行スクリプトに「メンテナンスウィンドウ(許可時間帯)」を組み込むことで、許可された時間帯だけ処理を実行し、それ以外はスキップするように制御できます。dateコマンドで現在の時刻・曜日を取得し、条件分岐でウィンドウ外を検知する仕組みは、数十行のシェルスクリプトで実装できます。

この記事では、bashのdateコマンドを使った時刻・曜日チェックの実装パターンを、RHEL 9 / Rocky Linux 9 で動作確認した実際のスクリプト例を交えながら解説します。

この記事のポイント

・dateコマンドの書式指定で現在時刻・曜日番号を数値として取得できる
・時刻チェックは「開始時刻 <= 現在時刻 < 終了時刻」の整数比較で実装する
・日付跨ぎ(23:00~翌1:00)は時刻をHHMM形式の整数に変換して正しく判定する
・曜日・時刻・ホスト名の複合条件で本番環境の運用ルールをコードに落とし込める


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

メンテナンスウィンドウが必要な場面

以下のようなケースでメンテナンスウィンドウの設計が役立ちます。

バックアップスクリプト:業務時間内は動かしたくない(I/O負荷を避けるため)
DBメンテナンス:深夜のVACUUMやREBUILDを特定時間帯に限定したい
バッチ更新処理:土日の自動配信を止めて月曜朝に一括送信したい
外部API呼び出し:相手方のメンテナンス時間帯を避けてリトライしたい

cronの `*/5 * * * *` のような実行スケジュールで細かい時間帯制御ができないときや、スクリプト自体に「今は動くべき時間か?」を判断させたいときに、本記事のパターンが使えます。

dateコマンドで時刻・曜日を取得する基本

1. 時刻・曜日番号の取得

時刻の取得には `date` コマンドの書式指定を使います。

# 現在時刻をHHMM形式(例: 0300、2345)で取得する $ date +%H%M 0315 # 時間・分を別々に取得する $ date +%H 03 $ date +%M 15 # 曜日番号を取得する(0=日曜、1=月曜 ... 6=土曜) $ date +%w 5 # 曜日名(短縮形)で取得する $ date +%a Fri

2. 時刻をHHMM整数に変換して比較する

時刻の範囲チェックには、HHMM形式の整数比較が最もシンプルです。例えば「0:00~6:00」なら `0000` ~ `0600` の範囲に現在時刻が入っているかを整数で比較します。

# HHMM形式の整数として現在時刻を取得する CURRENT_TIME=$(date +%H%M) # 範囲チェック(0000 <= CURRENT_TIME < 0600) if [[ "${CURRENT_TIME}" -ge 0 && "${CURRENT_TIME}" -lt 600 ]]; then echo "メンテナンスウィンドウ内(深夜0時~6時)" else echo "メンテナンスウィンドウ外" fi

時刻チェックの実装パターン

1. 日付跨ぎなし(同日内の時間帯)

「平日の9:00~18:00は実行スキップ(業務時間外のみ実行)」のような同日内の時間帯チェックです。

#!/bin/bash # 業務時間(09:00 ~ 18:00)は実行をスキップする CURRENT_TIME=$(date +%H%M) BUSINESS_START=900 # 09:00 BUSINESS_END=1800 # 18:00 if [[ "${CURRENT_TIME}" -ge "${BUSINESS_START}" && "${CURRENT_TIME}" -lt "${BUSINESS_END}" ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] 業務時間内のためスキップします(現在: ${CURRENT_TIME})" >&2 exit 0 fi echo "[$(date '+%Y-%m-%d %H:%M:%S')] 処理を開始します。" # 実際の処理をここに書く

2. 日付跨ぎあり(深夜帯のウィンドウ)

「深夜23:00~翌1:00のみ実行」のような日付を跨ぐケースは、単純な整数比較では正しく判定できません。「ウィンドウ外を検知する」方向で考えると簡単に実装できます。

#!/bin/bash # 深夜23:00 ~ 翌01:00のみ実行する(日付跨ぎウィンドウ) CURRENT_TIME=$(date +%H%M) WINDOW_START=2300 # 23:00 WINDOW_END=100 # 01:00 # ウィンドウ内: CURRENT_TIME >= 2300 OR CURRENT_TIME < 100 is_in_window() { local t=$1 if [[ "${t}" -ge "${WINDOW_START}" || "${t}" -lt "${WINDOW_END}" ]]; then return 0 # ウィンドウ内 fi return 1 # ウィンドウ外 } if ! is_in_window "${CURRENT_TIME}"; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] メンテナンスウィンドウ外のためスキップします。" >&2 exit 0 fi echo "[$(date '+%Y-%m-%d %H:%M:%S')] メンテナンスウィンドウ内。処理を開始します。" # 実際の処理をここに書く

実際の動作確認(RHEL 9.4):

# 現在時刻が03:15の場合(ウィンドウ内) $ bash /opt/scripts/maintenance_job.sh [2026-08-15 03:15:22] メンテナンスウィンドウ内。処理を開始します。 # 現在時刻が14:30の場合(ウィンドウ外) $ bash /opt/scripts/maintenance_job.sh [2026-08-15 14:30:05] メンテナンスウィンドウ外のためスキップします。 $ echo $? 0

曜日チェックの実装パターン

1. 土日をスキップする

`date +%w` の戻り値は `0`(日曜)~ `6`(土曜)の整数です。

#!/bin/bash # 土日(曜日番号0=日曜, 6=土曜)はスキップする WEEKDAY=$(date +%w) if [[ "${WEEKDAY}" -eq 0 || "${WEEKDAY}" -eq 6 ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] 土日のためスキップします。" >&2 exit 0 fi echo "[$(date '+%Y-%m-%d %H:%M:%S')] 平日。処理を開始します。"

2. 特定曜日だけ実行する

毎週月曜日の深夜だけ実行するような「曜日指定実行」は以下のように実装します。

#!/bin/bash # 月曜日(曜日番号1)のみ実行する WEEKDAY=$(date +%w) TARGET_WEEKDAY=1 # 月曜 if [[ "${WEEKDAY}" -ne "${TARGET_WEEKDAY}" ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] 月曜日以外のためスキップします(曜日番号: ${WEEKDAY})" >&2 exit 0 fi echo "[$(date '+%Y-%m-%d %H:%M:%S')] 月曜日。週次処理を開始します。"

シェルスクリプトでこうした設計パターンを体系的に学びたい方には、シェルスクリプト実践講座も参考にしてください。

時刻・曜日を組み合わせた実践スクリプト

「平日の深夜1:00~4:00のみ実行」という典型的なメンテナンスウィンドウを実装した完成例です。

#!/bin/bash # メンテナンスウィンドウ付きバッチスクリプト # 対象: 平日(月~金)の01:00 ~ 04:00のみ実行 LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')]" CURRENT_TIME=$(date +%H%M) WEEKDAY=$(date +%w) # 関数: メンテナンスウィンドウ内か判定する(0=OK, 1=NG) check_window() { local time=$1 local day=$2 # 土日チェック(0=日曜, 6=土曜) if [[ "${day}" -eq 0 || "${day}" -eq 6 ]]; then echo "${LOG_PREFIX} 土日のためスキップ(曜日番号: ${day})" >&2 return 1 fi # 時刻チェック(01:00 = 100 ~ 04:00 = 400) if [[ "${time}" -lt 100 || "${time}" -ge 400 ]]; then echo "${LOG_PREFIX} ウィンドウ外のためスキップ(現在時刻: ${time})" >&2 return 1 fi return 0 } if ! check_window "${CURRENT_TIME}" "${WEEKDAY}"; then exit 0 fi echo "${LOG_PREFIX} メンテナンスウィンドウ内。処理を開始します。" # ---- 実際の処理 ---- # ... # -------------------- echo "${LOG_PREFIX} 処理が完了しました。"

cronとの組み合わせ実践パターン

crontabでは分・時間単位の指定しかできないため、「平日の1:00~4:00の間で5分ごとに実行し、スクリプト側でウィンドウを判定する」設計が現場でよく使われます。

# /etc/cron.d/maintenance_job # 毎日1:00 ~ 4:00の間に5分ごとに実行(ウィンドウ判定はスクリプト側) */5 1-3 * * 1-5 root /opt/scripts/maintenance_job.sh >> /var/log/maintenance_job.log 2>&1

この書き方では cron 側でも「月~金の1:00~3:59台に限定」しつつ、スクリプト側でも「1:00~4:00の範囲」を確認する二重チェックになっています。cron の時間指定は分単位でズレが生じる場合があるため、スクリプト側のチェックが確実な「最終判断」になります。

実際の cronログ(/var/log/cron から抜粋):

Aug 15 01:05:01 server01 CROND[12341]: (root) CMD (/opt/scripts/maintenance_job.sh >> /var/log/maintenance_job.log 2>&1) Aug 15 01:10:01 server01 CROND[12456]: (root) CMD (/opt/scripts/maintenance_job.sh >> /var/log/maintenance_job.log 2>&1) Aug 15 04:05:01 server01 CROND[13789]: (root) CMD (/opt/scripts/maintenance_job.sh >> /var/log/maintenance_job.log 2>&1)

04:05 の実行は cron は起動しますが、スクリプト側の時刻チェックでスキップされて何も実行されません。

よくある失敗とトラブルシュート

「HHMM の先頭が 0 で始まると8進数として解釈されてエラーになる」

bash の算術評価では `08` や `09` が8進数として認識されてエラーになります。HHMM比較は [[ ]](二重ブラケット)の数値比較で実施することでこの問題を回避できます。[ ](単一ブラケット)の場合は `10#${CURRENT_TIME}` のように明示的に10進数指定してください。

# NG: 単一ブラケットで先頭0の比較(08, 09が8進数エラーになる場合がある) [ "${CURRENT_TIME}" -ge 800 ] # OK: 二重ブラケットを使う(bash拡張構文で8進数解釈を回避できる) [[ "${CURRENT_TIME}" -ge 800 ]] # OK: 10進数を明示する(POSIX sh互換が必要な場合) [ "$((10#${CURRENT_TIME}))" -ge 800 ]

「date コマンドのタイムゾーンがサーバーと異なる」

スクリプトが別のタイムゾーン設定のユーザーで動く場合や、Docker コンテナ内では `TZ` 環境変数が意図しない値になっていることがあります。スクリプト冒頭で `TZ` を明示的に設定しましょう:

#!/bin/bash # タイムゾーンをJSTに固定する export TZ=Asia/Tokyo CURRENT_TIME=$(date +%H%M) WEEKDAY=$(date +%w)

「スクリプトが長時間実行されてウィンドウを超えてしまう」

メンテナンスウィンドウの判定は「実行開始時」にしか行われません。処理が長引いてウィンドウ終了時刻を過ぎても、途中停止はされません。長時間処理は `timeout` コマンドで最大実行時間を設定するか、処理の節目(ループ内など)で定期的に時刻チェックを行う設計が有効です:

# timeoutコマンドでウィンドウ終了前に強制停止させる(例: 3時間で停止) timeout 10800 /opt/scripts/long_maintenance_job.sh # または処理ループ内でも時刻チェックを挟む for item in "${items[@]}"; do # ウィンドウが切れていたら中断する CURRENT_TIME=$(date +%H%M) if [[ "${CURRENT_TIME}" -ge 400 ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] ウィンドウ終了のため処理を中断します。" >&2 break fi process_item "${item}" done

本記事のまとめ

やりたいこと 実装方法
現在時刻をHHMM整数で取得する CURRENT_TIME=$(date +%H%M)
曜日番号を取得する(0=日曜) WEEKDAY=$(date +%w)
同日内の時間帯を確認する [[ CURRENT_TIME -ge 開始 && CURRENT_TIME -lt 終了 ]]
日付跨ぎの時間帯を確認する [[ CURRENT_TIME -ge 2300 || CURRENT_TIME -lt 100 ]]
土日をスキップする [[ WEEKDAY -eq 0 || WEEKDAY -eq 6 ]]
タイムゾーンを固定する export TZ=Asia/Tokyo
メンテナンスウィンドウの設計は「運用ルールをコードに落とし込む」という意味で、シェルスクリプトの自動化設計の中でも実務色の濃い技術です。dateコマンドと整数比較だけで実装できるため、既存スクリプトへの後付けも容易です。本番環境の制約に合わせてパターンをカスタマイズしてみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

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

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

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

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

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

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

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

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

この記事を書いた人

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

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

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