シェルスクリプトのDRY RUN設計|--dry-runフラグと実行関数で危険な操作を安全にテストする方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトのDRY RUN設計|--dry-runフラグと実行関数で危険な操作を安全にテストする方法
「rsyncでバックアップスクリプトを書いたが、パスの指定を間違えて本番ディレクトリのファイルを大量に上書きしてしまった」
「デプロイスクリプトを実行したら、想定外のサーバーに対してサービス停止コマンドが走った」

こうした事故は「実行前に何が起きるか確認できていれば防げた」ケースがほとんどです。外部ツールには rsync --dry-run や Ansible の --check モードがありますが、自作スクリプトにも同じ仕組みを組み込めます。
この記事では、DRY RUNフラグと実行ラッパー関数を使って、危険な操作(削除・上書き・ネットワーク送信)を安全にテストできるシェルスクリプトの設計パターンを解説します。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みです。

この記事のポイント

・DRY_RUN変数とexecute()関数で危険なコマンドの実行を一元制御できる
・--dry-runフラグで「実行しない」モードに切り替え、何が起きるかを事前確認できる
・DRY RUN時は[DRY-RUN]プレフィックスをログに出力して実行内容を可視化する
・バックアップ・デプロイスクリプトへそのまま転用できる実践パターンを紹介


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

DRY RUNモードとは何か

DRY RUNモード(試し実行・模擬実行)とは、実際の変更をシステムに加えることなく、「このスクリプトを実行したら何が起きるか」をログに出力するだけで終わらせる実行モードです。

代表的な例として、rsync --dry-run は実際のファイル転送を行わずに転送リストだけを表示し、Ansible の --check モードは実際の変更を加えずに適用内容だけを報告します。

自作スクリプトで DRY RUN モードを実装すると、次の3つのメリットがあります。

本番適用前の確認:削除・上書き・サービス停止といった取り返しのつかない操作を、まずログだけで確認できる
CI/CDでの事前検証:パイプラインの中でドライラン結果を diff 出力として保存し、レビューに組み込める
新人オペレーターの安全教育:「まず --dry-run で確認してから実行する」という習慣を強制できる

現場ではバックアップスクリプト・デプロイスクリプト・データ移行スクリプトなど、実行ミスが許されないスクリプトほど DRY RUN モードを持つべきです。

DRY RUN実装の基本構造

DRY RUN を実装する基本構造は「DRY_RUN変数」と「execute()関数」の2点セットです。危険なコマンドを execute() 経由で呼び出すだけで、DRY_RUN フラグの有無に応じて実行・スキップを自動制御できます。

1. DRY_RUN変数とexecute()関数を定義する

まず、スクリプト冒頭に DRY_RUN 変数を宣言し、execute() 関数を定義します。

#!/bin/bash set -euo pipefail # DRY RUN フラグ(デフォルトは false) DRY_RUN=false # 実行ラッパー関数 # DRY_RUN=true の時はコマンドを実行せずログだけ出力する execute() { if "${DRY_RUN}"; then echo "[DRY-RUN] $*" else "$@" fi }

execute() { ... } というシンプルな構造で、引数に渡したコマンドをそのまま実行できます。DRY_RUN が true の場合は echo "[DRY-RUN] $*" でコマンド内容を出力するだけです。

使い方は次のとおりです。

# 通常実行: 実際にファイルを削除する execute rm -f /tmp/old_backup.tar.gz # DRY_RUN=true の場合の出力例: # [DRY-RUN] rm -f /tmp/old_backup.tar.gz

execute() を経由させるだけで、DRY_RUN フラグ1つですべての危険なコマンドの挙動を切り替えられます。

2. --dry-runオプションを引数で受け取る

次に、コマンドライン引数 --dry-run を受け取って DRY_RUN 変数を切り替える処理を実装します。

#!/bin/bash set -euo pipefail DRY_RUN=false # 引数パース for arg in "$@"; do case "${arg}" in --dry-run|-n) DRY_RUN=true ;; --help|-h) echo "Usage: $0 [--dry-run] [--help]" echo " --dry-run, -n : 実際の変更を行わず内容を確認する" exit 0 ;; *) echo "[ERROR] 不明なオプション: ${arg}" >&2 exit 1 ;; esac done execute() { if "${DRY_RUN}"; then echo "[DRY-RUN] $*" else "$@" fi }

--dry-run-n(rsync と同じ短縮オプション)の両方に対応することで、rsync に慣れたエンジニアが直感的に使えます。

3. DRY RUNログで実行内容を可視化する

DRY RUN時のログには、[DRY-RUN] プレフィックスを必ず付けます。このプレフィックスがあると、あとでログを grep したときに DRY RUN の実行記録だけを抜き出せます。

# DRY RUN ログの確認例 ./backup.sh --dry-run 2>&1 | grep "DRY-RUN" # 出力例: # [DRY-RUN] rsync -avz /data/www/ user@backup-server:/backup/www/ # [DRY-RUN] rm -f /backup/www/20260601.tar.gz # [DRY-RUN] gzip -c /tmp/db_dump.sql > /backup/db/20260808.sql.gz

grep で抽出した DRY RUN ログをレビューし、問題がなければ --dry-run を外して本番実行するという確認フローが定着します。

実践例:rsyncバックアップスクリプトをDRY RUN対応にする

実際のバックアップスクリプトに DRY RUN を組み込んだ例を示します。RHEL 9.4 の検証機で動作確認したものです。

#!/bin/bash set -euo pipefail # ---- 設定 ---- SRC_DIR="/data/www" DEST_USER="backup" DEST_HOST="192.168.1.50" DEST_DIR="/backup/www" RETENTION_DAYS=30 LOG_FILE="/var/log/backup.log" # ---- DRY RUN 制御 ---- DRY_RUN=false for arg in "$@"; do case "${arg}" in --dry-run|-n) DRY_RUN=true ;; --help|-h) echo "Usage: $0 [--dry-run]" exit 0 ;; esac done execute() { if "${DRY_RUN}"; then echo "[DRY-RUN] $*" else "$@" fi } log() { local level="$1"; shift local prefix="" "${DRY_RUN}" && prefix="[DRY-RUN] " || true echo "$(date '+%Y-%m-%d %H:%M:%S') [${level}] ${prefix}$*" | tee -a "${LOG_FILE}" } # ---- メイン処理 ---- log INFO "バックアップ開始: ${SRC_DIR} -> ${DEST_USER}@${DEST_HOST}:${DEST_DIR}" # rsync 転送(DRY_RUN=true なら rsync にも --dry-run を追加) RSYNC_OPTS="-avz --delete" "${DRY_RUN}" && RSYNC_OPTS="${RSYNC_OPTS} --dry-run" || true # shellcheck disable=SC2086 execute rsync ${RSYNC_OPTS} "${SRC_DIR}/" "${DEST_USER}@${DEST_HOST}:${DEST_DIR}/" # 古いバックアップの削除(RETENTION_DAYS日以上前のtar.gz) execute find "${DEST_DIR}" -name "*.tar.gz" -mtime "+${RETENTION_DAYS}" -delete log INFO "バックアップ完了"

このスクリプトのポイントは2点あります。

rsync 自体にも --dry-run を渡す:スクリプト側の DRY RUN だけでは rsync の「実際の転送リスト」が見えません。rsync 本体にも --dry-run を渡すことで、どのファイルが転送されるかを正確に確認できます
log() 関数も DRY RUN を認識する"${DRY_RUN}" && prefix="[DRY-RUN] " || true という書き方で、DRY_RUN=true の時だけログに [DRY-RUN] プレフィックスを付加しています

実行時の確認フローは次のとおりです。

# ステップ1: DRY RUNで内容確認 ./backup.sh --dry-run # 出力例(実際の変更はなし): # 2026-08-08 12:00:00 [INFO] [DRY-RUN] バックアップ開始 ... # [DRY-RUN] rsync -avz --delete --dry-run /data/www/ backup@192.168.1.50:/backup/www/ # (rsync の転送予定ファイル一覧がここに表示される) # [DRY-RUN] find /backup/www -name "*.tar.gz" -mtime +30 -delete # ステップ2: 内容に問題がなければ本番実行 ./backup.sh

DRY RUN設計の応用パターン

4. 確認系コマンドはDRY RUNでも実行する(safe_execute)

大規模スクリプトでは、副作用のない「確認系」コマンド(stat、ls、curl GET など)は DRY RUN でも実際に実行して情報を得たい場合があります。そのような場合は safe_execute()execute() を使い分けます。

# DRY RUN でも実際に実行する(読み取り専用操作に使う) safe_execute() { "$@" } # DRY RUN 時はスキップする(書き込み・削除等に使う) execute() { if "${DRY_RUN}"; then echo "[DRY-RUN] $*" else "$@" fi } # 使い分け例: safe_execute stat /data/www # DRY RUN でも実行(確認のため) execute rm -rf /backup/old/ # DRY RUN 時はスキップ

5. trapと組み合わせてDRY RUNでも後片付けを確実に行う

DRY RUN モードでも一時ファイルを作成するケースがあります。trap を使って終了時に一時ファイルを確実に削除する設計は、DRY RUN と関係なく有効です。

TMP_DIR="$(mktemp -d)" # trap は DRY RUN に関係なく常に実行(一時ファイルを必ず削除する) trap 'rm -rf "${TMP_DIR}"' EXIT INT TERM # DRY RUN でも一時ファイルは作成して内容を確認する curl -sf "https://example.com/api/data" > "${TMP_DIR}/data.json" safe_execute cat "${TMP_DIR}/data.json" # 内容確認(DRY RUN でも表示) # 本番への書き込みだけ DRY RUN でスキップ execute cp "${TMP_DIR}/data.json" /etc/app/config.json

よくあるミスと対策

【ミス1】execute()の外でdestructiveな操作を直接呼ぶ

DRY RUN を導入しても、execute() を経由せず直接 rm や cp を呼んでしまうと、DRY RUN モードでも実際に実行されてしまいます。

実装後に次のコマンドで DRY RUN バイパス箇所を確認できます。

# execute() を通さない破壊的操作の呼び出しを検出する grep -En "^[[:space:]]*(rm|cp|mv|rsync|find) " backup.sh | grep -v "execute" # 出力があれば要修正(execute() でラッピングする)

【ミス2】DRY RUNでもAPIリクエストや通知が送信される

「通知・API呼び出し・メール送信」などの外部への副作用を execute() で制御し忘れると、DRY RUN 中にアラートメールが本当に飛んでしまいます。

外部へのリクエストは必ず execute() 経由にするか、DRY RUN 時は専用のテスト用エンドポイントに向けるように設計します。

【ミス3】DRY RUN変数をサブシェル内で変更しても反映されない

パイプの途中や ( ) のサブシェル内で DRY_RUN を変更しても、親シェルには反映されません。DRY_RUN はスクリプト最上部で宣言し、変更は引数パース時に1か所だけで行います。

本記事のまとめ

シェルスクリプトへの DRY RUN 設計の要点を整理します。
やりたいこと 実装方法 注意点
DRY RUNフラグを宣言する DRY_RUN=false をスクリプト冒頭に配置する サブシェル内で変更しないこと
危険なコマンドをラップする execute rm -f /path/to/file の形式で呼び出す rm/cp/mv/rsync等すべてを経由させる
引数で切り替える ./script.sh --dry-run または -n で起動する -n は rsync と同じ慣習で使いやすい
rsync にも dry-run を伝える "${DRY_RUN}" && RSYNC_OPTS="${RSYNC_OPTS} --dry-run" || true 転送予定リストを確認できて情報量が増える
確認系操作は常に実行する safe_execute stat /data/www の形式で呼び出す stat/ls/curl GET など副作用なし操作向け
DRY RUNバイパスを検出する grep -En "^[[:space:]]*(rm|cp)" script.sh | grep -v execute 実装後の確認フローとして組み込む
DRY RUN を持つスクリプトは、新人への手順書として「まず --dry-run で確認してから実行する」というワークフローを自然に強制できます。バックアップ・デプロイ・データ移行スクリプトなど、実行ミスが重大な影響を持つ場面でぜひ導入してください。

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

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

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

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

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

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

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

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

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

この記事を書いた人

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

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

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