Bash(シェルスクリプト)のif文は、条件に応じて処理を柔軟に分岐させるための最重要構文です。しかし、「[ ] と [[ ]] の使い分けが曖昧」「変数が空のときにエラーで落ちる」「-eq と == を間違えて予期せぬ挙動になる」といったトラブルに遭遇するエンジニアは少なくありません。
本記事では、システム運用やインフラ自動化の実務現場で即座に役立つよう、if文の基本文法(elif / else / fi)から条件式演算子(数値・文字列・ファイル)の完全早見表、[ ](test)と [[ ]](拡張構文)の決定的な違い、実務テンプレ集、頻出エラーの逆引き対策まで徹底解説します。
まず結論(3つの最重要ポイント)
- 基本書式:
if 条件; then 処理; fi。thenの前のセミコロン;と[]の前後の半角スペースは必須。 - 終了ステータス判定: if文が評価するのは真偽値ではなくコマンドの終了ステータス
0(成功=真)。 - 構文の使い分け: POSIX互換(
/bin/sh)なら[ ](変数は"$var"とクォート必須)、Bash専用(/bin/bash)なら正規表現やワイルドカードが使えて安全な[[ ]]を推奨。
※ 本記事のコードはすべて GNU bash 5.2(Ubuntu 24.04 LTS)にて実機検証済みです。POSIX sh互換の例は dash(Debian/Ubuntuの標準 /bin/sh)での動作も確認しています。
Bash if文の基本構文(if・else・elif・1行書き)
【大原則】Bashのif文は、条件式やコマンドを実行した直後の「終了ステータス(Exit Status)」が 0 であれば then ブロックを実行します。一般的なプログラミング言語のように true / false という真偽値型を直接見ているわけではない点に注意してください。
1. 最小構文(if … then … fi)
最もシンプルな条件分岐です。条件が成立したときだけ処理を実行します。
#!/bin/bash
# 数値が5より大きいかチェック
x=10
if [ "$x" -gt 5 ]; then
echo "xは5より大きいです(x=$x)"
fi
実行結果
$ bash sample.sh
xは5より大きいです(x=10)
※ [ の直後と ] の直前には必ず半角スペースが必要です([ は独立したコマンドとして解釈されるため)。
2. else 構文(2分岐)
条件を満たさなかった(終了ステータスが 0 以外)場合の処理を else で指定します。
#!/bin/bash
# 合格点判定
score=75
if [ "$score" -ge 60 ]; then
echo "合格です(スコア: $score)"
else
echo "不合格です(スコア: $score)"
fi
実行結果
$ bash sample.sh
合格です(スコア: 75)
3. elif 構文(多重分岐)
3つ以上の分岐を扱う場合は elif(else if の短縮形)を使います。
#!/bin/bash
# 多段階判定
num=10
if [ "$num" -gt 10 ]; then
echo "10より大きいです"
elif [ "$num" -eq 10 ]; then
echo "ぴったり10です"
else
echo "10未満です"
fi
実行結果
$ bash sample.sh
ぴったり10です
※ elif はいくつでも連結できます。他言語のように else if とスペースを空けて記述すると、新しい if のネストと解釈されて fi が余分に必要になるため、必ず elif を使用してください。
4. 1行で書くワンライナー(セミコロン && / ||)
CLIターミナル上で素早く動作確認したい場合や、簡単なガード処理では1行(ワンライナー)で記述できます。
# if文のワンライナー(セミコロンで区切る)
$ x=10; if [ "$x" -gt 5 ]; then echo "5より大きい"; fi
5より大きい
# &&(短絡評価)を使った簡易分岐(条件一致で実行)
$ [ -f /etc/hosts ] && echo "hostsファイルが存在します"
hostsファイルが存在します
# ||(短絡評価)を使った簡易分岐(不一致で実行)
$ [ -d /tmp/workdir ] || mkdir -p /tmp/workdir
※ 短絡評価 && は左辺が成功(終了ステータス0)のときのみ右辺を実行し、|| は左辺が失敗したときのみ右辺を実行します。実務スクリプトでは、処理が2行以上になる場合は可読性・保守性の観点から通常の if 文を使うのがベストプラクティスです。
5. ネスト(入れ子)の基本と注意点
#!/bin/bash
user="admin"
is_active="yes"
if [ "$user" = "admin" ]; then
if [ "$is_active" = "yes" ]; then
echo "アクティブな管理者ユーザーです"
else
echo "管理者は無効化されています"
fi
else
echo "一般ユーザーです"
fi
※ 内側の if に対しても必ず対応する fi を閉じる必要があります。ネストが3段階以上深くなる場合は、論理演算子(&&)で条件をまとめるか、case文 への置き換えを検討してください。
【早見表】条件式・演算子一覧(数値・文字列・ファイル・論理演算)
Bashのif文で利用できる条件分岐演算子をカテゴリ別にまとめました。実務でのコーディング時にいつでも逆引きしてコピペ利用できます。
1. 数値比較演算子(整数比較)
[ ](testコマンド)で数値を比較する際は、記号(< や >)ではなくアルファベットの演算子を使用します。
| 演算子 | 意味 | 記述例 | 備考 |
|---|---|---|---|
-eq | 等しい (equal) | [ "$a" -eq "$b" ] | 整数同士の一致判定 |
-ne | 等しくない (not equal) | [ "$a" -ne "$b" ] | 整数の不一致判定 |
-gt | より大きい (greater than) | [ "$a" -gt "$b" ] | a > b |
-ge | 以上 (greater than or equal) | [ "$a" -ge "$b" ] | a >= b |
-lt | より小さい (less than) | [ "$a" -lt "$b" ] | a < b |
-le | 以下 (less than or equal) | [ "$a" -le "$b" ] | a <= b |
※ [ ] の中で < や > を書くとリダイレクト記号と解釈されてファイルが作成・上書きされる危険があります。記号で直感的に数値比較したい場合は、後述する (( )) 算術構文(例: if (( a > b )); then)を使用します。
2. 文字列比較演算子
文字列の一致判定、辞書順比較、空文字判定を行う演算子です。
| 演算子 | 意味 | 記述例 | 対応環境 |
|---|---|---|---|
=(または ==) | 文字列が完全に一致 | [ "$str" = "target" ][[ "$str" == "target" ]] | = はPOSIX sh互換== はBash / Zsh推奨 |
!= | 文字列が一致しない | [ "$str" != "target" ] | POSIX sh互換 |
-z | 文字列が空(長さ0) | [ -z "$str" ] | 変数未定義または空文字のチェック |
-n | 文字列が空でない(長さ1以上) | [ -n "$str" ] | 値が存在することのチェック |
< / > | 辞書順で小さい / 大きい | [[ "$a" < "$b" ]][ "$a" \< "$b" ] | [[ ]] ではエスケープ不要[ ] では \< エスケープ必須 |
=~ | 正規表現マッチ | [[ "$str" =~ ^[0-9]+$ ]] | Bash / Zsh専用([[ ]] のみ) |
より高度な文字列操作・パターン判定については「Bashでの文字列比較方法:条件分岐で使える便利なテクニック12選」で詳しく解説しています。
3. ファイルテスト演算子(存在・種類・権限)
ファイルやディレクトリの存在、パーミッション、ファイルサイズなどを判定する演算子です。
| 演算子 | 判定内容 | 実用例 | ワンポイント解説 |
|---|---|---|---|
-e | パスが存在する(ファイル・ディレクトリ不問) | [ -e "$path" ] | 何らかのファイル/ディレクトリが存在するか |
-f | 通常ファイルが存在する | [ -f "/etc/nginx/nginx.conf" ] | ディレクトリやデバイスファイルは偽 |
-d | ディレクトリが存在する | [ -d "/var/log/app" ] | ディレクトリ判定の定番 |
-s | ファイルが存在し、サイズが0より大きい(非空) | [ -s "$logfile" ] | 空ファイルを除外して処理したいときに最適 |
-r | 読み取り権限(Read)がある | [ -r "$config" ] | 現実行ユーザー基準で判定 |
-w | 書き込み権限(Write)がある | [ -w "/tmp/cache" ] | 現実行ユーザー基準で判定 |
-x | 実行権限(Execute)がある | [ -x "/usr/local/bin/deploy" ] | コマンド実行可否の判定 |
-L / -h | シンボリックリンクである | [ -L "/usr/bin/python" ] | 実体ではなくリンク自体をチェック |
-nt | 左のファイルが右より新しい (newer than) | [ "$src" -nt "$dst" ] | ビルド・キャッシュ更新判定 |
-ot | 左のファイルが右より古い (older than) | [ "$cache" -ot "$origin" ] | 古いキャッシュの検知 |
ファイル存在確認(-f)の詳しい実践手順は「Bashスクリプトで-fオプションを使ったファイル存在確認の方法と実例解説」をご覧ください。
4. 論理演算子(AND / OR / NOT)
| 論理条件 | 推奨構文(POSIX sh互換) | 推奨構文(Bash専用 [[ ]]) | 非推奨(バグの原因) |
|---|---|---|---|
| AND(かつ) | [ 条件1 ] && [ 条件2 ] | [[ 条件1 && 条件2 ]] | [ 条件1 -a 条件2 ] |
| OR(または) | [ 条件1 ] || [ 条件2 ] | [[ 条件1 || 条件2 ]] | [ 条件1 -o 条件2 ] |
| NOT(否定) | ! [ 条件 ] または [ ! 条件 ] | ! [[ 条件 ]] または [[ ! 条件 ]] | — |
※ 古いシェルスクリプトで見かける -a(AND)や -o(OR)は、変数値に空白や特殊記号(括弧など)が含まれると構文解析エラーや誤判定を引き起こすため、POSIX規格でも非推奨(Obsolescent)となっています。複数条件は必ず && または || を使用してください。
`[`(testコマンド)と `[[`(拡張構文)の決定的な違い
Bashのスクリプトを書く上で最も重要なのが、従来の [ ](testコマンド)とBash拡張構文 [[ ]] の違いを正しく理解して使い分けることです。
1. `[` と `[[` の機能比較表
| 機能・項目 | [ ](testコマンド) | [[ ]](Bash拡張構文) |
|---|---|---|
| 構文種別 | 外部コマンド / シェル組み込みコマンド | シェルのキーワード(予約語) |
| ポータビリティ | ◎ 完璧(POSIX sh / dash / bash / zsh) | △ Bash / Zsh / ksh 専用(shでは不可) |
| 変数のクォート省略 | ✕ 必須(省略すると空白割れでエラー) | ◯ 安全(未クォートでも単語分割されない) |
正規表現マッチ(=~) | ✕ 利用不可 | ◯ 利用可能(=~) |
ワイルドカード(*, ?) | ✕ パス名展開(Globbing)されてしまう | ◯ パターンマッチとして安全に比較可能 |
| 論理演算子(AND / OR) | [ A ] && [ B ](個別コマンド結合) | [[ A && B ]](内部で直接記述可) |
不等号(<, >) | ✕ \<, \> とエスケープ必須 | ◯ そのまま記述可能 |
2. ① ワイルドカード(パターン)と正規表現マッチング(=~)
[[ ]] を使う最大のメリットは、パターンマッチ(ワイルドカード)や正規表現(=~)が直感的に書ける点です。
#!/bin/bash
# 1. ワイルドカード比較(拡張子チェック)
filename="app-error-2026.log"
if [[ "$filename" == *.log ]]; then
echo "ログファイルです: $filename"
fi
# 2. 正規表現マッチ(IPアドレス形式チェック)
ip="192.168.1.10"
if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "正しいIPv4形式です: $ip"
else
echo "無効なIP形式です: $ip"
fi
実行結果
$ bash sample.sh
ログファイルです: app-error-2026.log
正しいIPv4形式です: 192.168.1.10
※ =~ の右辺にある正規表現や、== の右辺のパターンはダブルクォートで囲まないのが鉄則です。[[ "$filename" == "*.log" ]] のように引用符で囲むと、ワイルドカードではなく文字通りの *.log というファイル名との完全一致比較になってしまいます。
3. ② 単語分割(Word Splitting)と変数クォートの安全性
[ ] はコマンド引数として評価されるため、変数が空文字列だったりスペースを含んでいると引数の数が変わり、構文エラーが発生します。一方、[[ ]] はシェルの予約語として特別にパースされるため、単語分割が起きません。
#!/bin/bash
empty_var=""
space_var="hello world"
# [ ] でクォートを忘れるとエラーになる
# [ $empty_var = "foo" ] -> [ = "foo" ] となり unary operator expected
# [ $space_var = "hello" ] -> [ hello world = "hello" ] となり too many arguments
# [[ ]] ならクォートなしでも安全に動作
if [[ $empty_var == "foo" ]]; then
echo "一致"
else
echo "empty_varは一致しません(安全に判定完了)"
fi
if [[ $space_var == "hello world" ]]; then
echo "space_varは正しく一致しました"
fi
実行結果
$ bash sample.sh
empty_varは一致しません(安全に判定完了)
space_varは正しく一致しました
4. ③ 論理演算子(&& / ||)の安全性
[ ] の内部に && や || を直接書くことはできませんが、[[ ]] の内部であれば自然に記述できます。
#!/bin/bash
env="production"
role="admin"
# [[ ]] 内で直接 && / || を組み合わせる
if [[ "$env" == "production" && "$role" == "admin" ]]; then
echo "本番環境の管理者として実行します"
fi
5. ④ 数値計算に特化した `(( ))`(算術評価構文)
Bashでは、純粋な数値計算や比較を行う場合に (( )) 構文を使うと、C言語やJava、JavaScriptなどと同様に直感的な比較演算子(>, <, ==, >=, <=, !=)が使えます。
#!/bin/bash
count=15
# (( )) の中では $ を省略でき、直感的な記号が使える
if (( count >= 10 && count <= 20 )); then
echo "countは10以上20以下です(count=$count)"
fi
# 算術計算を含めた判定も可能
if (( (count % 3) == 0 )); then
echo "3の倍数です"
fi
実行結果
$ bash sample.sh
countは10以上20以下です(count=15)
3の倍数です
※ (( )) もBash / Zsh専用の拡張機能です。#!/bin/sh で動作させる汎用スクリプトでは [ "$count" -ge 10 ] && [ "$count" -le 20 ] を使用してください。
実務で即使えるif文テンプレート集(コピペOK)
シェルスクリプトの実務開発や運用監視で頻出する、安全性の高い実践テンプレート集です。コピペして変数を書き換えるだけでそのまま利用できます。
1. コマンドライン引数の個数チェック(ガード節)
スクリプトに必要な引数が渡されているかを確認し、不足していれば使い方(Usage)を表示して終了ステータス 1 で即座に終了(イグジット)します。
#!/bin/bash
# 引数が1個未満(未指定)の場合は終了
if [ "$#" -lt 1 ]; then
echo "エラー: 引数が指定されていません。" >&2
echo "使用方法: $0 " >&2
exit 1
fi
target_file="$1"
echo "処理を開始します: $target_file"
実行結果
$ bash check_args.sh
エラー: 引数が指定されていません。
使用方法: check_args.sh
$ bash check_args.sh data.csv
処理を開始します: data.csv
2. コマンド・サービスの終了ステータス判定(systemctl / 外部コマンド)
サービスの稼働状態や外部コマンドの成否を直接判定します。! を前置することで「コマンドが失敗したとき」の処理を簡潔に書けます。
#!/bin/bash
# Nginxの稼働確認(停止していれば再起動)
service_name="nginx"
if ! systemctl is-active --quiet "$service_name"; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 警告: $service_name が停止しています。再起動を試みます。" >&2
sudo systemctl restart "$service_name"
else
echo "$service_name は正常稼働中です。"
fi
※ --quiet(または -q)オプションを付与すると標準出力が抑制され、終了ステータスのみが返るため、if文の条件式に直接渡すのに最適です。
3. 必須コマンドのインストール確認(command -v)
#!/bin/bash
# 必要なCLIツール(jqやcurl)が存在するかチェック
required_commands=("curl" "jq" "tar")
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "エラー: 必要なコマンド '$cmd' がインストールされていません。" >&2
exit 1
fi
done
echo "すべての前提コマンドが確認できました。"
4. ログファイル監視とエラー検知(grep -q)
ログファイル内に特定の重大エラー(FATALやERRORなど)が含まれているかを高速に検知します。
#!/bin/bash
logfile="/var/log/app/production.log"
# ファイルが存在し、中身があり、かつエラー文字列を含むか判定
if [ -s "$logfile" ] && grep -q "FATAL ERROR" "$logfile"; then
echo "緊急アラート: $logfile に致命的エラーを検知しました!" >&2
# ここにアラート通知(Slack通知やメール送信)を記述
fi
5. 環境変数のバリデーション(必須設定のチェック)
#!/bin/bash
# 必須のAPIキーや環境変数が空でないかチェック
if [ -z "${API_KEY:-}" ]; then
echo "エラー: 環境変数 'API_KEY' が設定されていません。" >&2
exit 1
fi
echo "API_KEY の設定を確認しました。"
※ ${API_KEY:-} のように :- を添えて展開することで、set -u(未定義変数で即終了するオプション)が有効な環境でもスクリプトがクラッシュすることなく安全にif文で判定できます。
よくあるエラーと対処法(トラブルシューティングFAQ)
Bashのif文で頻出するエラーメッセージとその根本原因、正しい解決手順をまとめました。
| エラーメッセージ | 根本原因 | 解決策 |
|---|---|---|
syntax error near unexpected token `fi' | then の直前の ; 忘れ、または fi の数が不一致 | if [ ... ]; then とセミコロンを記述 |
[: =: unary operator expected | 変数を引用符 "$var" で囲まず、変数が未定義または空 | 変数を必ず二重引用符 "$var" で囲む(または [[ ]] を使用) |
[: : integer expression expected | 数値比較(-gt 等)に変数が空文字列または非数値 | ${var:-0} でデフォルト数値を設定する |
[: missing `]' | ] の前の半角スペース不足、または [ ] 内で && を使用 | スペースを空ける。複数条件は [ A ] && [ B ] に修正 |
syntax error: unexpected end of file | fi の閉じ忘れ、または else if で fi が不足 | elif を使用するか、対応する fi を追加 |
Q1. syntax error near unexpected token `fi`
【原因】 then の前にセミコロン(;)がないか、改行されていない場合に発生します。シェルが then をコマンドの引数と認識し、then がない状態で fi に到達したため構文エラーになります。
# ✕ 誤り(; が抜けている)
if [ "$x" -eq 1 ] then
echo "OK"
fi
# ◯ 正しい書き方1(セミコロンで区切る)
if [ "$x" -eq 1 ]; then
echo "OK"
fi
# ◯ 正しい書き方2(then を改行する)
if [ "$x" -eq 1 ]
then
echo "OK"
fi
Q2. unary operator expected(単項演算子エラー)
【原因】 [ $var = "value" ] のように変数をクォートせずに記述し、$var が空だった場合、シェルは [ = "value" ] と展開します。これにより = の左辺の引数が消滅し、演算子エラーが発生します。
# ✕ 誤り(変数が空のとき構文崩壊)
name=""
if [ $name = "alice" ]; then ...
# ◯ 正しい書き方(必ず二重引用符で囲む)
if [ "$name" = "alice" ]; then
echo "一致しました"
fi
# ◯ または [[ ]] を使用(クォートなしでも安全)
if [[ $name == "alice" ]]; then
echo "一致しました"
fi
Q3. integer expression expected(整数式エラー)
【原因】 -eq や -gt などの数値比較演算子に対して、空文字列や文字列(例: "abc")が渡された場合に発生します。数値をクォートしても中身が空であればこのエラーは防げません。
#!/bin/bash
count=""
# ◯ 対策: パラメータ展開で未定義・空文字時にデフォルト値 0 を代入
if [ "${count:-0}" -gt 5 ]; then
echo "5より大きいです"
else
echo "5以下(または空・0)です"
fi
Q4. else if と elif の違いによる構文崩壊
他言語の感覚で else if と記述すると、シェルは else のブロック内に新たな if をネストしたと解釈します。そのため、全体の末尾に fi が2つ必要になります。
# ✕ else if と書くと fi が2つ必要になりミスを誘発
if [ "$x" -gt 0 ]; then
echo "正"
else if [ "$x" -eq 0 ]; then
echo "ゼロ"
fi # <- エラー!外側の if に対応する fi が足りない
# ◯ 推奨: elif を使えば fi は末尾に1つだけで完結
if [ "$x" -gt 0 ]; then
echo "正"
elif [ "$x" -eq 0 ]; then
echo "ゼロ"
else
echo "負"
fi
まとめ
Bashのif文を実務で安全に使いこなすための重要ポイントは以下の3点です。
- 終了ステータスで分岐する: if文は真偽値ではなく「コマンドの終了ステータス
0(成功)」を真として判定する。 - 構文の特性を理解して選ぶ: 移植性重視(
/bin/sh)なら[ "$var" ... ]とダブルクォート徹底。Bash環境(/bin/bash)ならパターン一致や正規表現が使えて単語分割事故のない[[ ]]を優先する。 - 構文ルールを遵守する:
thenの前のセミコロン;、[ ]前後の半角スペース、複数条件の&&/||を守ることで、大半の構文エラーは未然に防げる。
関連記事
シェルスクリプトの条件分岐や実務スキルをさらに高めたい方は、以下の関連解説記事もあわせて参考にしてください。
- シェルスクリプト入門 - 現場で使える実践付きの完全版 — シェルスクリプト全体の基礎から応用までを網羅したピラー記事
- Bashでの文字列比較方法:条件分岐で使える便利なテクニック12選 — 文字列比較演算子やパターンマッチの実践集
- Bashスクリプトで-fオプションを使ったファイル存在確認の方法と実例解説 — ファイルテスト演算子の詳細解説
- test コマンドの使い方|bashで条件式を評価する方法(if文・[ ]・[[ ]]比較付き) — testコマンドと [ ] の内部構造
- case/esac徹底解説|ifとの使い分け・パターン設計・落とし穴回避 — 多数の分岐を美しく処理する構文
- パターン・正規表現の使い分け|[[ ]] / extglob / grep -E — [[ ]] で使える正規表現の応用
- Bashのfor文は3種類|最適な書き方と安全テンプレ14選 — ループ処理との組み合わせ
- while / until / for:Bashループの入門からwhile readでの実務活用 — 条件に応じた繰り返し処理
