シェルスクリプトの事前チェック設計|コマンド存在・実行権限・ディスク容量を冒頭でまとめて検証する方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > シェルスクリプト > シェルスクリプトの事前チェック設計|コマンド存在・実行権限・ディスク容量を冒頭でまとめて検証する方法
「スクリプトを実行したら途中で『command not found』が出て、サーバーのファイルが壊れた」
「rootで動かすつもりだったのに一般ユーザーで実行してしまい、権限エラーが連発した」

シェルスクリプトに慣れてくると、こういう凡ミスが意外と痛い失敗につながります。対処は単純で、スクリプトの冒頭で「必要なコマンドがあるか」「適切な権限で動いているか」「ディスクに空きがあるか」をまとめて確認してから本処理に進む——いわゆるpreflight(事前チェック)設計です。

この記事では、RHEL 9.4 / Rocky Linux 9.3で動作確認した事前チェック関数の実装パターンを解説します。trap によるエラー終了処理との組み合わせ、よくあるつまずきポイントも合わせて紹介します。

この記事のポイント

・command -v でコマンド存在を確認し、なければ即時エラー終了できる
・id コマンドと [ -w ] で root権限・書き込み権限をチェックできる
・df -k でディスク空き容量を数値比較し、不足時に処理を止められる
・全チェックを preflight 関数に集約すると本処理との責務が明確に分かれる


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

事前チェックを省略すると何が起きるか

シェルスクリプトは「上から順に実行する」という性質上、途中で失敗したときに「ここまでは成功した」状態が残ります。バックアップスクリプトなら古いバックアップが消えた後で新しいバックアップが取れない、デプロイスクリプトなら半分だけ更新されたファイルが本番に残る——どちらも手動で復元するのが厄介な状態です。

よくある失敗パターンを3つ挙げます。

・コマンド未インストール: mysqldump や rsync が入っていない環境でスクリプトを流し、バックアップが取れていなかった
・権限の確認漏れ: 一般ユーザーで実行したために /etc 配下の設定ファイルが書けず、中途半端な状態のまま終了した
・ディスクフル: バックアップ先の空きが足りず、途中でディスクがいっぱいになって圧縮ファイルが壊れた

これらは「本処理の前に1行チェックを入れる」だけで防げます。それをまとめて担うのがpreflight関数です。

3つの事前チェック設計パターン

1. command -v でコマンドの存在を確認する

コマンドの存在確認には command -v を使うのが標準的です。which はPATHの通り方によって結果が変わることがあり、type はシェル組み込みコマンドも返すため、外部コマンドの確認には command -v が最も信頼できます。

# コマンド存在確認の基本パターン require_command() { local cmd="$1" if ! command -v "$cmd" > /dev/null 2>&1; then echo "[ERROR] 必要なコマンドが見つかりません: ${cmd}" >&2 echo "[ERROR] インストールしてから再実行してください" >&2 exit 1 fi } # 使い方 require_command rsync require_command mysqldump require_command gzip

実行してコマンドがない場合は次のように終了します。

$ bash backup.sh [ERROR] 必要なコマンドが見つかりません: mysqldump [ERROR] インストールしてから再実行してください

command -v はコマンドのフルパスを標準出力に返します。> /dev/null 2>&1 でその出力を捨てて終了コードだけを判定に使います。! command -v "$cmd" の形で「見つからなかったら」という分岐を書くのがシンプルです。

2. id と [ -w ] で実行権限を確認する

「rootで動かす前提のスクリプト」と「特定ディレクトリへの書き込みが必要なスクリプト」では、確認すべき権限が異なります。それぞれの関数を分けておくと使いまわしやすくなります。

# root権限チェック(id -u が 0 なら root) require_root() { if [ "$(id -u)" -ne 0 ]; then echo "[ERROR] このスクリプトはroot権限で実行してください" >&2 echo "[ERROR] sudo bash $(basename "$0") で再実行してください" >&2 exit 1 fi } # 書き込み権限チェック require_writable() { local dir="$1" if [ ! -d "$dir" ]; then echo "[ERROR] ディレクトリが存在しません: ${dir}" >&2 exit 1 fi if [ ! -w "$dir" ]; then echo "[ERROR] ディレクトリへの書き込み権限がありません: ${dir}" >&2 exit 1 fi } # 使い方 require_root require_writable /var/backup

RHEL 9.4の実機で一般ユーザー(ec2-user)から実行した場合の出力例です。

[ec2-user@sv01 ~]$ bash deploy.sh [ERROR] このスクリプトはroot権限で実行してください [ERROR] sudo bash deploy.sh で再実行してください

id -u はUIDを数値で返します。rootのUIDは0なので -ne 0(0でなければ)でroot以外を弾けます。[ ! -w "$dir" ] は書き込み権限がない場合にtrueになるテスト式です。

3. df で空きディスク容量を確認する

バックアップや大きなファイルを生成するスクリプトでは、ディスクの空き容量を事前に確認しておくと安全です。

# ディスク空き容量チェック(単位: MB) require_disk_space() { local dir="$1" local required_mb="$2" # df -Pk: POSIXフォーマット・1024バイトブロック単位で出力 local available_kb available_kb=$(df -Pk "$dir" | awk 'NR==2 {print $4}') local available_mb=$(( available_kb / 1024 )) if [ "$available_mb" -lt "$required_mb" ]; then echo "[ERROR] ディスク容量が不足しています: ${dir}" >&2 echo "[ERROR] 必要: ${required_mb}MB / 利用可能: ${available_mb}MB" >&2 exit 1 fi } # 使い方(/var/backup に 2000MB 以上の空きが必要) require_disk_space /var/backup 2000

df -Pk の -P はPOSIX出力形式(ヘッダ行1行+データ行1行の2行出力を保証)、-k は1024バイト単位です。awk 'NR==2 {print $4}' で2行目の第4フィールド(Available列)を取り出します。この書き方はLinux・macOS・AIXいずれでも動作します。

容量不足時の出力例:

[ERROR] ディスク容量が不足しています: /var/backup [ERROR] 必要: 2000MB / 利用可能: 342MB

preflight 関数にまとめる設計パターン

3つのチェック関数ができたら、スクリプト冒頭の1か所から呼び出すpreflight関数にまとめます。本処理を書く前にすべての前提条件を満たしているかを確認するのが目的です。

#!/bin/bash # backup-db.sh — MySQLバックアップスクリプト set -euo pipefail BACKUP_DIR="/var/backup/mysql" REQUIRE_MB=3000 # --- チェック関数 --- require_command() { local cmd="$1" if ! command -v "$cmd" > /dev/null 2>&1; then echo "[ERROR] コマンドが見つかりません: ${cmd}" >&2 exit 1 fi } require_root() { if [ "$(id -u)" -ne 0 ]; then echo "[ERROR] root権限が必要です" >&2 exit 1 fi } require_writable() { local dir="$1" if [ ! -d "$dir" ] || [ ! -w "$dir" ]; then echo "[ERROR] 書き込み不可: ${dir}" >&2 exit 1 fi } require_disk_space() { local dir="$1" required_mb="$2" local avail_mb avail_mb=$(( $(df -Pk "$dir" | awk 'NR==2 {print $4}') / 1024 )) if [ "$avail_mb" -lt "$required_mb" ]; then echo "[ERROR] ディスク不足: 必要${required_mb}MB / 利用可能${avail_mb}MB" >&2 exit 1 fi } # --- preflight: ここで全条件を一括検証 --- preflight() { echo "[INFO] 事前チェックを開始します..." require_root require_command mysqldump require_command gzip require_writable "$BACKUP_DIR" require_disk_space "$BACKUP_DIR" "$REQUIRE_MB" echo "[INFO] 事前チェック完了。本処理を開始します" } # --- 本処理 --- main() { preflight local timestamp timestamp=$(date '+%Y%m%d_%H%M%S') local outfile="${BACKUP_DIR}/db_${timestamp}.sql.gz" mysqldump --all-databases | gzip > "$outfile" echo "[INFO] バックアップ完了: ${outfile}" } main "$@"

正常実行時の出力例(RHEL 9.4実機):

[root@sv01 ~]# bash backup-db.sh [INFO] 事前チェックを開始します... [INFO] 事前チェック完了。本処理を開始します [INFO] バックアップ完了: /var/backup/mysql/db_20260924_034501.sql.gz

preflight関数は「本処理を開始してよいか」だけを担当し、本処理はmain関数が担当します。この責務の分離があると、チェックを増やしたい時にpreflight内だけを修正すれば済みます。

シェルスクリプトによる安全なバックアップ設計のより詳しい解説は、シェルスクリプト実践講座 でもまとめています。

trap と組み合わせた安全な終了処理

preflight設計と相性がよいのが、trap によるエラー終了時の後片付けです。事前チェックをパスしても本処理の途中で失敗することはあります。一時ファイルを使う処理では、trap で確実に後片付けをしておきましょう。

#!/bin/bash set -euo pipefail BACKUP_DIR="/var/backup/mysql" TMPFILE="" cleanup() { if [ -n "$TMPFILE" ] && [ -f "$TMPFILE" ]; then rm -f "$TMPFILE" echo "[INFO] 一時ファイルを削除しました: ${TMPFILE}" >&2 fi } # EXIT シグナルで cleanup を必ず呼ぶ(正常終了・異常終了どちらでも) trap cleanup EXIT preflight() { require_root require_command mysqldump require_command gzip require_writable "$BACKUP_DIR" require_disk_space "$BACKUP_DIR" 3000 } main() { preflight TMPFILE=$(mktemp /tmp/db_dump.XXXXXX) mysqldump --all-databases > "$TMPFILE" gzip -c "$TMPFILE" > "${BACKUP_DIR}/db_$(date '+%Y%m%d_%H%M%S').sql.gz" echo "[INFO] バックアップ完了" } main "$@"

trap cleanup EXIT を冒頭に置いておくと、スクリプトが正常終了・エラー終了・Ctrl+C のいずれで終わっても必ず cleanup が実行されます。TMPFILE変数をグローバルに置いておき、mktemp で値を代入した後に cleanup が参照できるようにする点がポイントです。

トラブルシュート

「事前チェックをパスしたのに本処理で失敗する」ケース

・rootチェックはパスしたが対象ファイルの所有者が別ユーザー: rootでもSELinux/ACLが有効な環境では書き込みを拒否されることがあります。require_writable でテストファイルを作成してすぐ消す方法(touch "$dir/.preflight_test" && rm -f "$dir/.preflight_test")を使うとより確実です。
・コマンドはあるが古いバージョンで動かない: command -v はバージョンを確認しません。バージョン依存の機能を使う場合は mysqldump --version | awk '{print $3}' 等で確認します。
・df -Pk が古いNFSマウントで詰まる: NFSがオフラインの時に df がタイムアウトまでブロックします。NFSマウント先を確認先にする場合は timeout 5 df -Pk "$dir" としてタイムアウトを設けましょう。

「set -euo pipefail と require_command が競合する」ケース

set -e(エラーで即終了)が有効な環境でも、if ! command -v "$cmd" > /dev/null 2>&1 の形にしておけばサブシェルの終了コードを if が受け取るためスクリプトが止まりません。command -v "$cmd" > /dev/null 2>&1 || exit 1 という書き方でも同様です。

まとめ

シェルスクリプトのpreflight設計の要点をまとめます。

確認したいこと 使う手法
コマンドの存在確認 command -v コマンド名 > /dev/null 2>&1
root権限の確認 [ "$(id -u)" -ne 0 ]
ディレクトリ書き込み権限の確認 [ ! -w ディレクトリ ]
ディスク空き容量の確認 df -Pk ディレクトリ | awk 'NR==2 {print $4}'
チェックをまとめる場所 preflight関数に集約してmain冒頭で呼ぶ
後片付けの保証 trap cleanup EXIT と組み合わせる
preflight関数はスクリプトの「安全装置」です。処理が複雑になるほど「途中で止まっても安全な状態」を保つのが難しくなるため、最初から事前チェックを入れておく習慣が重要です。設計のポイントを体系的に身につけたい場合は、実機を使ったハンズオンで手を動かして覚えていくのが一番の近道です。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、シェルスクリプト実践講座(無料)では、今回紹介したpreflight設計を含む実務で使えるシェルスクリプトのパターンを体系的に解説しています。ぜひご覧ください。

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

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

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

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

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

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

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

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

この記事を書いた人

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

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

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