test / [[ ]] 入門 - シェルスクリプトの条件判定
この記事で解決できること
test/[ ]/[[ ]]の 違いと使い分け が分かる- 文字列・数値・ファイルの 比較演算子を正しく選べる ようになる
[: too many argumentsのような クォート事故を構造的に回避 できる
結論(実務の型)
- 迷ったら
[[ ]]を使う(bash 前提なら安全側に倒れる) - POSIX
shへの移植性が必要なときだけ[ ](=test) を使う - 事故の原因はほぼ ①クォート漏れ ②演算子の取り違え(
-eqと=) の 2 つ
前提(対象環境)
- シェル: bash(
[[ ]]は bash / ksh / zsh の拡張で、POSIXshには無い) - スクリプト先頭が
#!/bin/bashであることを想定
test・[ ]・[[]] は何が違うのか?
結論:
[ ]はtestコマンドそのもの。[[ ]]はシェルの予約語で、クォート・演算子・パターンマッチが強化されている。
3 つの関係を一言で整理すると次のとおり。
| 記法 | 正体 | 移植性 | 主な強み |
|---|---|---|---|
test EXPR |
外部にも実体がある組み込みコマンド | POSIX(高い) | 最も基本的 |
[ EXPR ] |
test の別名(] は引数) |
POSIX(高い) | if と相性が良い見た目 |
[[ EXPR ]] |
シェルの予約語(keyword) | bash 等(限定) | クォート安全・=~・&&/|| 可 |
[ ] は コマンド なので、[ と ] の前後、各演算子の前後には必ずスペースが要る。test と [ は同じ動作をする。
$ test 1 -lt 2; echo $? 0 $ [ 1 -lt 2 ]; echo $? 0
一方 [[ ]] はシェルが構文として解釈するため、後述のクォート事故やパターンマッチで有利になる。
[ の正体を確認する
$ type [ [ is a shell builtin $ ls -l /usr/bin/[ -rwxr-xr-x 1 root root ... /usr/bin/[
組み込み版が優先されるが、test/[ は外部コマンドとしても存在する歴史的経緯がある。
文字列はどう比較するのか?
結論: 文字列一致は
=([[ ]]では==も可)、空判定は-z/非空は-n。変数は必ずダブルクォートで囲む。
基本の演算子
=… 等しい([[ ]]内では==も同義)!=… 等しくない-z "$s"… 文字列が空(zero length)-n "$s"… 文字列が空でない
name="penguin"
if [[ "$name" = "penguin" ]]; then
echo "match"
fi
if [[ -z "$name" ]]; then
echo "empty"
else
echo "not empty"
fi= と == の使い分け
== は bash の拡張。POSIX sh への移植を考えるなら [ ] では = を使うのが安全。[[ ]] 内ではどちらでも動く。
[ "$a" = "$b" ] # POSIX 安全 [[ "$a" == "$b" ]] # bash([[ ]] 内では == も =も可)
数値はどう比較するのか?
結論: 数値比較は
-eq -ne -lt -le -gt -geを使う。=や>は文字列比較になり別物。
| 演算子 | 意味 | 数学記号 |
|---|---|---|
-eq |
等しい | == |
-ne |
等しくない | != |
-lt |
より小さい | < |
-le |
以下 | <= |
-gt |
より大きい | > |
-ge |
以上 | >= |
count=42
if [[ "$count" -ge 10 ]]; then
echo "10 以上"
fi= と -eq を取り違えない
[[ "08" = "8" ]] # false(文字列として違う) [[ "08" -eq "8" ]] # true(数値として等しい)
数値のつもりで = を使うと、ゼロ埋めや空白で意図しない結果になる。
算術評価が必要なら (( )) を使う手もある。(( count >= 10 )) のように数学記号がそのまま書ける。
ファイルの存在や種類はどう調べるのか?
結論: ファイルテスト演算子で存在・種類・権限を判定する。よく使うのは
-e -f -d -r -w -x -s。
| 演算子 | 意味 |
|---|---|
-e file |
存在する(種類を問わない) |
-f file |
通常ファイルである |
-d file |
ディレクトリである |
-r file |
読み取り可能 |
-w file |
書き込み可能 |
-x file |
実行可能 |
-s file |
サイズが 0 より大きい |
-L file |
シンボリックリンクである |
a -nt b |
a が b より新しい |
config="/etc/myapp.conf"
if [[ -f "$config" && -r "$config" ]]; then
echo "読み込める設定ファイルがある"
else
echo "設定ファイルが無いか読めない"
fi「存在しない」を判定する
! で否定する。スクリプトの早期リターンで頻出する型。
if [[ ! -d "$dir" ]]; then
echo "ディレクトリがありません: $dir" >&2
exit 1
fiなぜ [[]] のほうが事故りにくいのか?
結論:
[[ ]]は変数を展開しても単語分割・グロブ展開をしないため、クォート漏れによる構文崩壊が起きない。
[ ] はコマンドなので、変数が空や空白を含むと 引数の数が変わって 構文が壊れる。
v="" [ $v = "x" ] # → [ = "x" ] と解釈され: too many arguments / 構文エラー [ "$v" = "x" ] # → [ "" = "x" ](正しく false)クォートで回避 v="a b" [ $v = "x" ] # → [ a b = "x" ](引数 4 個でエラー)
[[ ]] 内では、変数を展開しても単語分割・グロブが起きない。クォートを忘れても壊れにくい。
v="a b" [[ $v = "x" ]] # 壊れない(false)
それでも クォートする習慣 は残すこと。[ ] でも [[ ]] でも "$var" と書いておけば、どちらに移植しても安全。
[[]] だけのパターンマッチと正規表現とは?
結論:
[[ ]]の==右辺はグロブパターン、=~は正規表現として評価される。[ ]には無い機能。
グロブによるパターンマッチ(==)
右辺をクォートしないとパターン、クォートすると文字列として扱う。
file="report.txt"
if [[ "$file" == *.txt ]]; then
echo "テキストファイル"
fi
if [[ "$file" == "*.txt" ]]; then
echo "これはリテラルの *.txt と一致したときだけ"
fi正規表現マッチ(=~)
右辺は クォートしない。マッチ部分は BASH_REMATCH 配列に入る。
input="port=8080"
if [[ "$input" =~ ^port=([0-9]+)$ ]]; then
echo "ポート番号: ${BASH_REMATCH[1]}"
fi=~ の右辺をダブルクォートで囲むと、正規表現ではなく リテラル文字列 として扱われる(bash 3.2 以降)。パターンは変数に入れて [[ "$s" =~ $re ]] とするのが安全。
複数条件はどうつなぐのか?
結論:
[[ ]]内なら&&||で連結できる。[ ]では[ ... ] && [ ... ]とコマンド単位でつなぐ。
# [[ ]] の中で連結(読みやすい)
if [[ "$age" -ge 18 && "$age" -lt 65 ]]; then
echo "現役世代"
fi
# [ ] はコマンドを && でつなぐ
if [ "$age" -ge 18 ] && [ "$age" -lt 65 ]; then
echo "現役世代"
fi[ ] 内の -a(AND)・-o(OR)は 非推奨。引数の解釈が曖昧になり事故の温床になる。条件をつなぐときは [ ] を分けて && / || でつなぐこと。
まとめ:test と [[]] の使い分け
結論: bash スクリプトなら
[[ ]]を基本にし、POSIXsh互換が要る箇所だけ[ ]を使う。クォートと演算子選択を外さなければ事故はほぼ防げる。
[[ ]]… bash 前提。クォート安全・=~・&&/||が使えて読みやすい[ ]/test…#!/bin/shや POSIX 互換が必要な場面- 文字列は
=/-z/-n、数値は-eq系、ファイルは-f/-d系 - 変数は常に
"$var"でクォートする
コピペ用テンプレ
# 引数チェック
if [[ $# -lt 1 ]]; then
echo "usage: $0 <file>" >&2
exit 1
fi
target="$1"
# ファイル存在と種類
if [[ ! -f "$target" ]]; then
echo "通常ファイルではありません: $target" >&2
exit 1
fi
# 拡張子で分岐
if [[ "$target" == *.log ]]; then
echo "ログファイルを処理します"
fi