現場で使えるシェルスクリプト入門7 test コマンドと [ ] / [[ ]] の違いを徹底解説

シェルスクリプト IT

Bashシェルスクリプトで条件分岐を書くときによく使われるのが test 系の条件式です。

testコマンドの書き方

代表的な書き方として次の3種類があります。

  • test 条件
  • [ 条件 ]
  • [[ 条件 ]]

このうち、testと [ ] はほぼ同じ意味を持ち、[ ] はtestコマンドの別表記です。[ はコマンドとして存在しますが、 ] は終端記号となり、コマンドとして存在しません。

# [はコマンドとして存在するのに対して、
# which [
/usr/bin/[

# ]はコマンドとして存在しない
# which ]
/usr/bin/which: no ]

なお、次のようにtestコマンドを使うより、

s=""
if test -z "$s" ; then
    echo "文字列の長さは0です"
else
    echo "文字列の長さは1以上です"
fi

実務では[ ]、もしくは[[ ]]を使う方がコードが読みやすく一般的です。

s=""
if [[ -z "$s" ]]; then
    echo "文字列の長さは0です"
else
    echo "文字列の長さは1以上です"
fi

[ ]と[[ ]]の違い

[ ]と[[ ]]では以下のような違いがあります。

特性[ ][[ ]]
単語分割起きる起きない
ワイルドカードパス名展開(glob展開)起きる起きない
未定義変数エラーになりやすい安全
論理演算-a / -o&& / ||
パターンマッチ正規表現強力(==、!=、=~)
POSIX(UNIX系OSの標準規格)対応非対応(bash等)

一つづつ違いを見ていきます。

単語分割

スペースなどで単語が分割されているケースです。
変数sに単語を分割代入して動作を確認します。

[ ]の場合:

s="apple orange"
if [ $s = "apple orange" ]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# エラーとなり、正しく判定できない
./test1.sh: line 36: [: too many arguments
違います

この場合、[ ]内の変数sをダブルクォーテーションで囲み、単語分割されないようにします。

s="apple orange"
if [ "$s" = "apple orange" ]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
同じです

次に[[ ]]を確認します。

[[ ]]の場合:

s="apple orange"
if [[ $s = "apple orange" ]]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
同じです

ダブルクォーテーションで囲まなくても単語分割が起きないのが分かります。

ワイルドカードパス名展開(glob展開)

[ ]でワイルドカード(*)を使うとパス名展開されます。
変数sに存在しないファイル「test1.txt」を指定します。

s="test1.txt"
if [ $s = test1* ]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
違います

次に存在するファイル「test1.sh」を変数sに代入します。

s="test1.sh"
if [ $s = test1* ]; then
    echo "同じです"
else
    echo "違います"
fi

「test1*」でパス名展開され、「test1.sh」と一致します。
実行結果:

# ./test1.sh 
同じです

次に[[ ]]を確認します。
[[ ]]の場合*はパス名展開ではなく、パターンマッチ(globパターン)として使用されます。
存在しないファイル「test1.txt」を変数sに代入します。

s="test1.txt"
if [[ $s = test1* ]]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
同じです

[[ ]]の場合はパス名として展開されるのではなく、「test1*」が文字列としてパターンマッチされるため「test1.txt」と一致します。

globパターンの詳細については「パターンマッチ正規表現」の章に記載します。

未定義変数

[ ]で未定義変数を扱うとエラーになります。

unset s
if [ $s = "test1*" ]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
./test1.sh: line 35: [: =: unary operator expected
違います

このエラーは左辺の未定義変数sが存在しない([ = “test1*” ])と判定されるからです。

[[ ]]では未定義変数はエラーになりません。

unset s
if [[ $s = "test1*" ]]; then
    echo "同じです"
else
    echo "違います"
fi

実行結果:

# ./test1.sh 
違います

これは[[ ]]が未定義変数を空文字([[ “” = “test1*” ]])として判定するためです。

論理演算

論理演算子とは、複数の条件を組み合わせるための演算子のことです。
次の 3 つを使います。

  • AND(かつ)
  • OR(または)
  • NOT(否定)

以下は[ ]の論理演算子です。

  • AND演算子:-a
  • OR演算子:-o
  • NOT演算子:!

AND演算子の使用例です。OR演算子も使い方は同じです。

s="test1"
t="test2"
if [ $s = "test1" -a $t = "test2" ]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
trueです

以下は[[ ]]の論理演算子です。

  • AND演算子:&&
  • OR演算子:||
  • NOT演算子:!

AND演算子の使用例です。OR演算子も使い方は同じです。

s="test1"
t="test2"
if [[ $s = "test1" && $t = "test2" ]]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
trueです

否定(NOT演算子)する場合は!になります。

s="test1"
t="test2"
if [ ! $s = "XXX" -a $t = "test2" ]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
trueです

「$s = “XXX”」が否定されたことが分かります。

次に[[ ]]で試します。

s="test1"
t="test2"
if [[ ! $s = "XXX" && $t = "test2" ]]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
trueです

同じく「$s = “XXX”」が否定されたことが分かります。

それでは式全体($s = “XXX” && $t = “test2″)を否定するにはどうすればよいでしょうか?
[[ ]]の場合は()で囲みます。

s="test1"
t="test2"
if [[ ! ( $s = "test1" && $t = "test2" ) ]]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
falseです

[ ]の場合、()はバックスラッシュでエスケープする必要があります。

s="test1"
t="test2"
if [ ! \( $s = "test1" -a $t = "test2" \) ]; then
    echo "trueです"
else
    echo "falseです"
fi

実行結果:

# ./test1.sh 
falseです

論理演算子を使用する場合は、可読性が高くシンプルに記載できる[[ ]]がお勧めです

パターンマッチ

パターンマッチを使いたいなら[[ ]]一択です。

種類特徴
[ ]glob 展開(パス名展開)が起きるためパターンマッチ不可
[[ ]]glob パターンによるパターンマッチが可能。正規表現も使える

先ずは、glob展開とglobパターンについて説明します。

glob 展開

glob パターンを、条件に一致する “実在するファイル名の一覧に置き換える処理” のことです(パス名展開とも呼ばれます)。

例:ディレクトリに a.txtb.txt がある場合

*.txt → a.txt b.txt

glob展開については前述していますので、詳細はそちらをご参照ください。

glob パターン

globパターンは、ワイルドカード(*? など)を使って探したいファイルの特徴を表現したものです。

  • *.txt → 「.txt で終わるファイル」
  • a?c → 「a + 任意の1文字 + c」
  • [0-9]* → 「数字で始まる任意のファイル」

glob パターンは通常ファイル名の指定に使われますが、[[ ]] の中ではglob展開しないので、文字列のパターンマッチに使えます。つまり、ワイルドカードを使ったパターン記法です。

[[ ]] 構文の = / == は、右辺をクォートしなければパターンに一致するかどうかを判定します。

  • [[ $str == "*.txt" ]] ➔ 完全一致(「*.txt」という文字列そのものか判定)
  • [[ $str == *.txt ]] ➔ パターンマッチ(.txtで終わるか判定)

以下は拡張子を判定する例です。
パターンマッチ(右辺をクォートしない場合):


filename="backup_20260630.tar.gz"
if [[ $filename == *.tar.gz ]]; then
    echo "これは圧縮アーカイブファイルです。"
else
    echo "圧縮アーカイブファイルではありません。"
fi

実行結果:

# ./test1.sh 
これは圧縮アーカイブファイルです。

「backup_20260630.tar.gz」は存在しないので、glob展開しないことが分かります。

右辺をクォートした場合:

filename="backup_20260630.tar.gz"
if [[ $filename == "*.tar.gz" ]]; then
    echo "これは圧縮アーカイブファイルです。"
else
    echo "圧縮アーカイブファイルではありません。"
fi

実行結果:

# ./test1.sh 
圧縮アーカイブファイルではありません。
[[ ]] の正規表現マッチ(=~)

[[ ]]=~正規表現によるマッチングを行います。 globパターンよりもはるかに強力です。
現場でよく使う「入力値が数字だけか」「IPアドレスの形式か」といったチェックが行えます。

num="1234567890"
if [[ $num =~ ^[0-9]+$ ]]; then
    echo "入力されたのは正しい数値です: $num"
else
    echo "エラー: 数値以外の文字が含まれています。"
fi

実行結果:

# ./test1.sh 
入力されたのは正しい数値です: 1234567890

こちらもglobパターンと同様に、右側の正規表現パターンはクォートで囲みません

拡張正規表現

[[ ]]=~拡張正規表現を使えます。

特徴:

  • +? をそのまま使える(基本正規表現では”\+ “や “\? “のようにバックスラッシュが必要)
  • | で OR が書ける
  • () でグループ化できる
  • [0-9][A-Za-z] などの文字クラスが使える

例:

s="user100access.log"
if [[ $s =~ ^user?[0-9]+(access|error)\.log$ ]]; then
    echo "正しいログ名です。: $s"
else
    echo "エラー: ログ名に誤りがあります。"
fi

実行結果:

# ./test1.sh 
正しいログ名です。: user100access.log
キャプチャも可能

キャプチャとは正規表現の () で囲んだ部分を「抜き出す」仕組みのことです。

たとえば:

item42

という文字列から 数字の部分だけ を取り出したいとします。

正規表現ではこう書きます:

item([0-9]+)

この ( ) の部分が キャプチャ(グループ)です。
[[ ]]=~ を使うと、 マッチした内容が自動的に BASH_REMATCH という配列に入ります。

配列内容
BASH_REMATCH[0]正規表現にマッチした全体
BASH_REMATCH[1]最初の ( ) の中身
BASH_REMATCH[2]2番目の ( ) の中身
以下同様

実行例です。

s="item-fruits-42"

if [[ $s =~ item-([a-z]+)-([0-9]+) ]]; then
    echo "0: ${BASH_REMATCH[0]}"
    echo "1: ${BASH_REMATCH[1]}"
    echo "2: ${BASH_REMATCH[2]}"
fi

実行結果:

# ./test1.sh 
0: item-fruits-42
1: fruits
2: 42
glob パターンと正規表現の違い(まとめ)
種類演算子説明
glob パターン= / ==*.txtファイル名マッチと同じ簡易パターン
拡張正規表現(ERE)=~^a.*b$より強力で複雑な判定が可能

testコマンドオプション

testコマンドには多くのオプションがあり、大きく次の4種類に分類できます。

  1. 文字列
  2. 数値比較
  3. ファイル判定
  4. 論理演算

よく使うものについて記載します。

1.文字列オプション

オプション意味
-z “$s”文字列の長さが 0(空文字)
-n “$s”文字列の長さが 0 でない

例:文字列長の確認

s=""
if [ -z "$s" ]; then
    echo "文字列の長さは0です"
else
    echo "文字列の長さは1以上です"
fi

結果:

文字列の長さは0です

2. 数値比較オプション

数値の比較には、文字列比較とは別の専用オプションを使います。=!= を数値に対して使わないよう注意してください。

オプション意味
"$a" -eq "$b"数値が等しい (equal)
"$a" -ne "$b"数値が等しくない (not equal)
"$a" -gt "$b"数値がより大きい (greater than)
"$a" -ge "$b"数値が以上 (greater or equal)
"$a" -lt "$b"数値がより小さい (less than)
"$a" -le "$b"数値が以下 (less or equal)

例:数値の判定

num=10
if [ "$num" -gt 5 ]; then
    echo "5より大きいです"
fi

実行結果:

# ./test1.sh 
5より大きいです

3. ファイル判定オプション

シェルスクリプトで頻繁に使用される、ファイルやディレクトリの状態を確認するためのオプションです。

オプション意味
-e "$file"ファイルが存在する (exist)
-f "$file"通常のファイルである
-d "$file"ディレクトリである
-r "$file"読み取り権限がある
-w "$file"書き込み権限がある
-x "$file"実行権限がある

例:ディレクトリの存在チェック

dir="./logs"
if [ -d "$dir" ]; then
    echo "$dir はディレクトリです"
else
    echo "$dir は存在しないか、ディレクトリではありません"
fi

実行結果:

# ./test1.sh 
./logs はディレクトリです

4. 論理演算

複数の条件を組み合わせたい場合に使用します。

オプション意味
! expr条件の否定 (NOT)
expr1 -a expr2条件の論理積 (AND)
expr1 -o expr2条件の論理和 (OR)

例:AND条件

file="data.txt"
if [ -f "$file" -a -r "$file" ]; then
    echo "ファイルが存在し、かつ読み取り可能です"
fi

実行結果:

# ./test1.sh 
ファイルが存在し、かつ読み取り可能です

コメント

タイトルとURLをコピーしました