シェルスクリプトでif文を書くとき、必ず登場するのがtestコマンドと[ ]です。「ファイルが存在するか」「変数が空か」「数値が閾値を超えたか」といった条件の判定を担い、スクリプトの分岐処理の土台になります。[ 条件 ]という角括弧の書き方が有名ですが、これは実はtestというコマンドの別名です。
test・[ ]は、シェルスクリプトの中でも特に罠が多いことで知られます。[の前後にスペースが必要、数値比較と文字列比較で演算子が違う、変数をクォートしないと誤動作する——これらを知らないと、意図しない分岐やエラーに悩まされます。この記事では、実機のLinux(WSLのDebian)でこれらの罠を実際に再現しながら、条件判定の正しい書き方を整理します。
[ 条件 ]はtest 条件と同じもの([はコマンド名です)。[の前後・中にはスペースが必須。[5 -gt 3]はエラーになります。- 数値比較は
-eq/-lt/-gt、文字列比較は=/!=。混同に注意。 - ファイル判定は
-f(ファイル)・-d(ディレクトリ)・-e(存在)。 - 空文字列の判定は
-z(空)・-n(非空)。変数は必ずクォートします。 - bashなら
[[ 条件 ]]が使え、&&や<が書けてより安全です。
スクリプトを動かす環境は環境変数、定期実行はcron、コマンドの成否判定はリダイレクトとパイプもあわせて参考になります。
[ ]とtestは同じもの
まず基本です。[ 条件 ]という書き方は、test 条件というコマンドとまったく同じです。[は「testの別名のコマンド」であり、最後の]は「引数の終わり」を示す約束事です。
# この2つは完全に同じ意味 if [ 5 -gt 3 ]; then echo "5は3より大きい"; fi if test 5 -gt 3; then echo "5は3より大きい"; fi # [ はコマンド、] は引数の終わりの目印 # つまり [ 5 -gt 3 ] は「[ というコマンドに 5 -gt 3 ] を渡す」
実機でも、[ 5 -gt 3 ]とtest 5 -gt 3がまったく同じ結果になることを確認しました。-gtは「greater than(より大きい)」の意味です。[が「コマンド」だと理解することが、次に説明する「スペース必須」の罠を理解する鍵になります。コマンドである以上、コマンド名の後にはスペースが必要なのです。
【最重要】スペースが必須
test・[ ]で最も多いエラーがこれです。[の後、]の前、そして条件の各要素の間には、すべてスペースが必要です。[はコマンドなので、スペースで区切らないと別のコマンド名として扱われてしまいます。
# NG: [ の後にスペースが無い if [5 -gt 3]; then echo ok; fi # bash: [5: command not found # ↑ 「[5」という名前のコマンドを探してしまう # NG: ] の前にスペースが無い if [ 5 -gt 3]; then ... # これもエラー # OK: すべてスペースで区切る if [ 5 -gt 3 ]; then echo ok; fi
実機で、[5 -gt 3]のように[の直後にスペースを入れずに書くと、bash: [5: command not foundというエラーになりました。これは、[がコマンドであるため、[5を「[5という名前のコマンド」だと解釈してしまうからです。同様に]の前にスペースが無い場合もエラーになります。[を「特別な記号」ではなく「[という名前のコマンド」だと理解すれば、コマンド名・引数・引数の終わりの]を、すべてスペースで区切る必要があることが腑に落ちます。if文を書いてエラーになったら、まずこのスペースを疑ってください。これはtest・[ ]で最も頻繁に起こるミスです。
数値比較と文字列比較(混同の罠)
次の重要な罠が、数値比較と文字列比較で演算子が異なることです。数値は-eq・-lt・-gtなど、文字列は=・!=を使います。これを混同すると、エラーは出ないのに意図しない結果になります。
# 数値比較(-eq=等しい, -ne=異なる, -lt=未満, -le=以下, -gt=超, -ge=以上) if [ 10 -eq 10 ]; then echo "数値として等しい"; fi if [ 3 -lt 5 ]; then echo "3は5未満"; fi # 文字列比較(= と != を使う) if [ "abc" = "abc" ]; then echo "文字列として等しい"; fi if [ "$name" != "" ]; then echo "name は空でない"; fi # 【罠】数値を = で比較すると「文字列比較」になる if [ 10 = 10.0 ]; then echo A; else echo "B: 10 と 10.0 は文字列として別物"; fi # B が出力される("10" と "10.0" は文字列として違う) if [ 10 -eq 10 ]; then echo "-eq なら数値比較で正しい"; fi
実機で確認したところ、[ 10 = 10.0 ]は偽になりました(else側が実行)。これは=が文字列比較で、"10"と"10.0"を文字列として比べるため「違う」と判定されるからです。一方[ 10 -eq 10 ]は数値比較なので正しく真になりました。数値を比べたいのに=を使うと、エラーは出ないまま間違った判定をしてしまう——これが厄介なところです。覚え方は「数値はアルファベットの演算子(-eqなど equal の略)、文字列は記号の演算子(=)」です。逆に見えて紛らわしいのですが、ハイフン付きのアルファベット2文字(-eq/-lt/-gt)が数値用と覚えておけば間違えません。-eq=equal、-lt=less than、-gt=greater than、-le=less or equal、-ge=greater or equal、-ne=not equal です。
ファイルの判定(-f / -d / -e)
test・[ ]は、ファイルやディレクトリの存在・種類も判定できます。スクリプトで「ファイルがあれば処理する」といった分岐に不可欠です。
# -f: 通常ファイルが存在するか if [ -f config.txt ]; then echo "config.txt がある"; fi # -d: ディレクトリが存在するか if [ -d /var/log ]; then echo "ディレクトリがある"; fi # -e: ファイルでもディレクトリでも、存在するか if [ -e somepath ]; then echo "存在する"; fi # && や || と組み合わせる定番の書き方 [ -f data.txt ] && echo "ファイルあり" [ -e backup ] || mkdir backup # 無ければ作る
実機でも、[ -f afile.txt ]が通常ファイル、[ -d adir ]がディレクトリ、[ -e nonexistent ]が存在しないファイルで偽になることを確認しました。-f(file)・-d(directory)・-e(exists)のほか、-r(読み取り可)・-w(書き込み可)・-x(実行可)といった権限の判定もできます。[ -e backup ] || mkdir backupのように||と組み合わせると、「無ければ作る」といった処理が1行で書けます(ただしディレクトリ作成ならmkdir -pのほうが簡潔です)。
【罠】空文字列の判定とクォート忘れ
変数が空かどうかは-z(空なら真)・-n(非空なら真)で判定します。ここで絶対に守るべきルールが、変数を必ずダブルクォートで囲むことです。クォートを忘れると、意図しない判定になります。
# -z: 変数が空か / -n: 空でないか EMPTY="" if [ -z "$EMPTY" ]; then echo "空です"; fi # 【罠】クォートを忘れると誤動作する V="" if [ -n $V ]; then echo "誤ってtrueになる"; fi # ↑ $V が空だと [ -n ] となり、引数が消えて意図せず真になる # OK: 必ずダブルクォートで囲む if [ -n "$V" ]; then echo "本当に空でないときだけ"; fi
実機で、空の変数V=""に対してクォート無しで[ -n $V ]を実行すると、誤って真になってしまいました。これは、$Vが空に展開されると[ -n ]という形になり、testが「引数が1つ(-nという文字列)だけ渡された=空でない文字列」とみなして真を返すためです。クォートで囲めば([ -n "$V" ])、$Vが空でも[ -n "" ]となり、正しく偽になります。この「変数は必ずダブルクォートで囲む」というルールは、-z/-nに限らずあらゆる条件判定で徹底すべき鉄則です。スペースを含む値でも壊れなくなり、空文字列の事故も防げます。「[ ]の中の変数は"$変数"」と体で覚えてください。次に紹介する[[ ]]を使うと、この問題の一部は自動的に回避されます。
bash拡張の[[ ]]
bashには、[ ]を強化した[[ ]]があります。変数のクォート忘れに強く、&&や||、</>が使えるため、bashスクリプトでは[[ ]]が推奨されます。
# && や || をそのまま書ける if [[ 5 -gt 3 && "a" < "b" ]]; then echo "両方成立"; fi # 変数のクォートを多少忘れても壊れにくい(それでも囲むのが安全) if [[ -n $V ]]; then echo "空でない"; fi # パターンマッチもできる(* が使える) if [[ "report.txt" == *.txt ]]; then echo ".txt ファイル"; fi # 注意: [[ ]] は bash 専用。#!/bin/sh のスクリプトでは使えない
実機でも、[[ 5 -gt 3 && "a" < "b" ]]のように&&と文字列の<比較が[[ ]]で使えることを確認しました。[ ]では&&の代わりに-aを使ったり、<をエスケープしたりと面倒でしたが、[[ ]]なら直感的に書けます。さらに[[ "report.txt" == *.txt ]]のようなパターンマッチも可能です。ただし[[ ]]はbash専用で、#!/bin/sh(dashなど)のスクリプトでは使えません。bashスクリプトなら[[ ]]、移植性が必要なら[ ]と使い分けます。
主な演算子一覧
test・[ ]でよく使う演算子をまとめます。
| 演算子 | 意味 |
|---|---|
-eq / -ne |
数値: 等しい / 異なる |
-lt / -le / -gt / -ge |
数値: 未満 / 以下 / 超 / 以上 |
= / != |
文字列: 等しい / 異なる |
-z / -n |
文字列: 空 / 空でない |
-f / -d / -e |
ファイル / ディレクトリ / 存在 |
-r / -w / -x |
読み取り / 書き込み / 実行が可能 |
よくある失敗
[の前後にスペースが無くcommand not found
[はコマンドです。[ 条件 ]のように前後・中をスペースで区切ります。
数値を=で比較して誤判定する
数値は-eqなど、文字列は=です。混同すると誤った結果になります。
変数のクォートを忘れて誤動作する
[ -n "$V" ]のように、変数は必ずダブルクォートで囲みます。
[[ ]]をshスクリプトで使ってエラーになる
[[ ]]はbash専用です。#!/bin/shでは[ ]を使います。
ファイル判定で-fと-eを混同する
-fは通常ファイルのみ、-eはディレクトリを含む存在確認です。
よくある質問
[はtestコマンドの別名で、[ 条件 ]とtest 条件はまったく同じ動作をします。[がコマンドであるため、後ろにスペースが必要で、]は引数の終わりを示す目印です。実機でも両者が同じ結果になることを確認しています。[の後にスペースが無いためです。[はコマンドなので、[5だと「[5という名前のコマンド」を探してしまいます。[ 5 -gt 3 ]のように、[の後・]の前・各要素の間をすべてスペースで区切ってください。testで最も多いエラーです。-eq(等しい)・-lt(未満)・-gt(超)などハイフン付きのアルファベット、文字列比較は=・!=という記号を使います。数値を=で比較すると文字列比較になり、10と10.0が「違う」と判定されるなど誤動作します(実機で確認済み)。数値には必ず-eq系を使ってください。[ -f ファイル ]で通常ファイルの存在、[ -d ディレクトリ ]でディレクトリの存在、[ -e パス ]でファイル・ディレクトリを問わず存在を判定できます。[ -f config.txt ] && echo ありのように&&と組み合わせるのが定番です。[ -n "$V" ]のように書きます。クォートを忘れると、変数が空のときに引数が消えて意図しない判定になります(実機でも、クォート無しの[ -n $V ]が空変数で誤って真になることを確認)。スペースを含む値でも壊れなくなるため、クォートは必須の習慣です。まとめ
[ 条件 ]はtest 条件と同じ。[はコマンドです。[の前後・中にはスペースが必須。忘れるとcommand not found。- 数値は
-eq系、文字列は=。混同すると誤判定します。 - ファイルは
-f/-d/-e、空文字列は-z/-n。 - 変数は必ず
"$V"とクォート。bashなら[[ ]]がより安全です。
test・[ ]は、シェルスクリプトの分岐を支える基本でありながら、スペース・演算子・クォートという3つの罠を持つコマンドです。「[はコマンドだからスペースで区切る」「数値は-eq」「変数はクォート」——この3点を押さえれば、if文で悩むことはなくなります。cronや環境変数と組み合わせて、堅牢なスクリプトを書いてみましょう。
- grep(文字列検索)
- find(ファイル検索)
- sed(置換・行編集)
- awk(列抽出・集計)
- cut(列の切り出し)
- chmod(パーミッション)
- tar(圧縮・解凍)
- リダイレクトとパイプ
- echo・printf(文字列出力)
- ps・kill(プロセス管理)
- 環境変数とPATH
- which・type(コマンドの正体)
- ln(シンボリックリンク)
- cp・mv・rm(コピー/移動/削除)
- touch・mkdir(ファイル/フォルダ作成)
- basename・dirname(パス分解)
- history(コマンド履歴)
- curl(HTTP通信・API)
- ping(疎通確認)
- dig・nslookup(DNS調査)
- jq(JSON処理)
- vim(基本操作・終了方法)
- cron・crontab(定期実行)
- test・[ ](条件判定)
- read(入力の読み取り)
- date(日付・時刻)
- sort・uniq・wc(並べ替え/集計)
- xargs(一括処理)
- chown・chgrp(所有者/グループ変更)
- diff(ファイル比較)
- du・df(ディスク使用量)
- head・tail(先頭/末尾・ログ監視)
- cat・less(ファイル表示)
- alias(コマンドの別名)
- nohup(SSH切断対策)
- tmux(セッション管理)
- よく使うコマンドまとめ
- cd(ディレクトリ移動)

