test と [ と [[ コマンドの違い - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog

2013-12-15(Sun) [sh][shell] [更新履歴]

拡張 POSIX シェルスクリプト Advent Calendar 2013、15日目の記事です。 書くのが遅れ、ネタは尽きかけ、 マニアックさやニッチさが足りなくなってきているような気がします。 ふつうの内容ですみません。

今日は test, [, [[ コマンドの違いについてざっくり紹介します。

[[ がある bash, ksh, zsh ならば test[ の使用は避け、 [[ を使用すべきです。 [ は慎重に使わないと様々な罠にかかるため危険です。 (翌日のネタも併わせてどうぞ)

test[ の違い

どちらもシェルの組込みコマンドです。名前が違うのと [ は最後の引数を ] にしなければいけない縛りがある以外、 動作まったく同じです。

不定期に「test は外部コマンド (/usr/bin/test) で [ は組込みコマンドだ」 などという話が涌きますが、私の不確かな記憶が確かならば(どっちだよ)、 そのようなシェルは見たことがありません。(csh 系は知らん)

現代のシェルの中では busybox の組込み sh がコンパイル時に test を組込みにしない実装にすることが可能です。 メモリやストレージの容量が厳しい組込み機器内の実装であれば、 test が組込まれていない sh も珍しくないかもしれません。

[[ の違い 1: ワード分割とパス名展開がされない

[[ 〜 ]] 内では変数展開後にワード分割されません。 クォートの有無には依りません。 変数にワード区切り文字 ($IFS) が含まれていても安全です。 zsh 使いであればデフォルトの set +o SH_WORD_SPLIT 状態と等価であると言うと理解し易いかもしれません。

また、[[ 〜 ]] 内ではパス名展開もされません。 クォートやパスのパターンマッチ文字 ?, * などの有無には依りません。 zsh のデフォルトの set +o GLOBSUBST 状態と等価です。

次の例のように [ 〜 ] 内ではクォートなしの変数展開は変数値に依って式が変化してしまう可能性があります。 何かしら意図がない限り、変数展開は必ずダブルクォートすべきです

$ line='foobar'
$ [ $line == foobar ]; echo $?
0
$ line='foo bar'
$ [ $line == foobar ]; echo $?
bash: [: 引数が多すぎます
2
$ line='/*'
$ [ $line == foobar ]; echo $?
bash: [: 引数が多すぎます
2
$ line='1 -eq 1 -o xxx'
$ [ $line == foobar ]; echo $?
0

[[ では変数展開をダブルクォートする必要がありません。

$ line='foobar'
$ [[ $line == foobar ]]; echo $?
0
$ line='foo bar'
$ [[ $line == foobar ]]; echo $?
1
$ line='/*'
$ [[ $line == foobar ]]; echo $?
1
$ line='1 -eq 1 -o dummy'
$ [[ $line == foobar ]]; echo $?
1

[[ の違い 2: 数値の比較演算子では左右の値が算術式展開される

-eq, -ne などの数値の比較演算子を用いたとき、 比較対象の値を算術式として評価した結果を比較します。

つまり、次の例の [ 〜 ][[ 〜 ]] は等価です。

$ var=123
$ [ "$((var))" -eq 123 ]; echo $?
0
$ [[ var -eq 123 ]]; echo $?
0
$ varname=var
$ [ "$(($varname))" -eq 123 ]; echo $?
0
$ [[ $varname -eq 123 ]]; echo $?
0

let コマンドや算術式展開と同様に、単純な値だけでなく式も書けます。 式に空白を含めたい場合はクォートします。

$ let 'x = 2 ** 10'
$ echo $x
1024
$ echo $((2**10))
1024
$ [[ 2**10 -eq 512+512 ]]; echo $?
0
$ [[ '2 ** 10' -eq '512 + 512' ]]; echo $?
0

[[ の違い 3: 文字列の比較演算子 == の動作が異なる

[[ 文字列1 == 文字列2 ]] のように文字列の比較演算子 == の右辺がクォートされていない場合、完全一致ではなくパターンマッチとなります。

$ [ /foobar == /fooba[rz] ]; echo $?
1
$ [ /foobar == '/fooba[rz]' ]; echo $?
1
$ [[ /foobar == /fooba[rz] ]]; echo $?
0
$ [[ /foobar == '/fooba[rz]' ]]; echo $?
1
$ [[ /foobar == /foo* ]]; echo $?
0
$ [[ /foobar == '/foo*' ]]; echo $?
1

[[ の違い 4: 文字列の比較演算子の種類が多い

[ にはない 3 つの文字列用比較演算子が用意されています。

<> はリダイレクト用のメタ文字と被っていますが、 クォートしたりエスケープする必要はありません。

[[ の違い 5: 論理演算子が異なる

[[ の論理演算子の論理積 (AND) は &&、論理和 (OR) は || です。 [-a-o は使用できません。

次の例の [ 式 -a 式 ][[ 式 && 式 ]] は等価です。

$ [ -n "$foo" -a -n "$bar" ]; echo $?
$ [[ -n $foo && -n $bar ]]; echo $?

次の例の [ 式 -o 式 ][[ 式 || 式 ]] は等価です。

$ [ -n "$foo" -o -n "$bar" ]; echo $?
$ [[ -n $foo || -n $bar ]]; echo $?

[ のほうは [ -a ファイル ][ -o オプション名 ] と重複している上に見難いですが、 [[ のほうは [ 式 ] && [ 式 ][ 式 ] || [ 式 ] 記法と同じくらい見易いですね。


ところで、12月25日はクリスマスな上に、 OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority

逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!


私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/


よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。