web-dev-qa-db-ja.com

引用符なしのスペースを使用したパラメーター展開が二重大括弧「[[」内で機能し、単一大括弧「[」内では機能しないのはなぜですか?

シングルブラケットまたはダブルブラケットの使用と混同しています。このコードを見てください:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

文字列にスペースが含まれていますが、完全に機能します。しかし、それを単一のブラケットに変更すると:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

それは言う:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

それを次のように変更すると:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

正常に動作します。誰かが何が起こっているのか説明できますか?スペースによる問題を防ぐために、"${var}"などの変数を二重引用符で囲む必要があるのはいつですか。

90
Majid Azimi

単一のブラケット[は実際にはtestコマンドのエイリアスであり、それはnot構文です。

単一のブラケットの(多くの)欠点の1つは、評価しようとしている1つ以上のオペランドが空の文字列を返す場合、2つのオペランド(バイナリ)が必要であると文句を言うことです。これが、人々が[ x$foo = x$blah ]を実行する理由です。xは、オペランドが空の文字列に評価されないことを保証します。

一方、二重括弧[[ ]]is syntaxであり、[ ]よりもはるかに優れています。ご存知のように、これには単一オペランドの問題はなく、>, <, >=, <=, !=, ==, &&, ||演算子を使用してよりCに似た構文を使用することもできます。

私の推奨は次のとおりです。インタープリターが#!/bin/bashの場合、-always use [[ ]]

[[ ]]はすべてのPOSIXシェルでサポートされているわけではありませんが、zshに加えてkshbashなどの多くのシェルがサポートしています。

88
SiegeX

_[_コマンドは通常のコマンドです。ほとんどのシェルは効率化のために組み込みとして提供していますが、シェルの通常の構文規則に従います。 _[_はtestとまったく同じですが、_[_は最後の引数として_]_を必要とし、testは必要としません。

二重括弧_[[ … ]]_は特別な構文です。これらはksh(_[_の数年後)で導入されました。これは、_[_を正しく使用するのは面倒で、_[[_は、シェルの特殊文字を使用する新しいニースの追加を許可するためです。たとえば、次のように書くことができます

_[[ $x = foo && $y = bar ]]
_

条件式全体がシェルによって解析されるため、_[ $x = foo && $y = bar ]_は最初に、2つのコマンド_[ $x = foo_と_$y = bar ]_に_&&_演算子で区切られます。同様に、二重括弧は、パターンマッチング構文などを有効にします。 _[[ $x == a* ]]_ xの値がaで始まるかどうかをテストします。単一の括弧内では、これは_a*_を現在のディレクトリのaで始まるファイルのリストに展開します。二重括弧はkshで最初に導入され、ksh、bash、zshでのみ使用できます。

単一の括弧内では、他のほとんどの場所と同様に、変数の置換を二重引用符で囲む必要があります。これは、それらがコマンド(単なる_[_コマンド)の引数だからです。二重括弧内では、シェルはWordの分割やグロビングを行わないため、二重引用符は必要ありません。コマンドではなく条件式を解析します。

ただし、_[[ $var1 = "$var2" ]]_は例外ですが、バイトからバイトへの文字列比較を行う場合は引用符が必要です。それ以外の場合は、_$var2_は_$var1_を照合するパターンになります。

_[[ … ]]_で実行できないことの1つは、変数を演算子として使用することです。たとえば、これは完全に合法です(ただし、ほとんど役に立ちません)。

_if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi
…
if [ "$x" "$op" "$y" ]; then …
_

あなたの例では

_dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then …
_

if内のコマンドは、4つの引数_[_、_-d_、VMsおよび_/home/mazimi/VirtualBox_を使用した_]_です。シェルは_-d /home/mazimi/VirtualBox_を解析し、VMsの処理方法を知りません。整形式のコマンドを取得するには、_${dir}_でのWord分割を防ぐ必要があります。

一般的に言えば、結果に対してWordの分割とグロビングを実行したい場合を除いて、変数とコマンドの置換は常に二重引用符で囲みます。二重引用符を使用しないことが安全な主な場所は次のとおりです。

  • 割り当て:_foo=$bar_(ただし、_export "foo=$bar"_またはarray=("$a" "$b")などの配列割り当てでは二重引用符が必要です)。
  • caseステートメント内:_case $foo in …_;
  • _=_または_==_演算子の右側を除く二重括弧内(パターンマッチングが必要な場合を除く):_[[ $x = "$y" ]]_。

これらすべてにおいて、二重引用符を使用することは正しいので、高度なルールをスキップして、常に引用符を使用することもできます。

スペースによる問題を防ぐために、"${var}"のような変数を二重引用符で囲む必要があるのはいつですか。

この質問に含まれるのは

${variable_name}が十分ではないのはなぜですか?

${variable_name}は、あなたの考えていることを意味するわけではありません…

anythingがあると思われる場合は、(変数値の)スペースが原因の問題に対処します。 ${variable_name}はこれに適しています:

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

そして何もない!1${variable_name}は機能しませんany変数名の一部になる可能性のある文字をすぐに続けていない限り、文字(A-Zまたはa-z)、アンダースコア(_)、または数字(0-9)。そしてそれでも、あなたはそれを回避することができます:

$ echo "$bar"d
food

私はその使用を思いとどまらせようとはしていません— echo "${bar}d"がおそらくここでの最良の解決策です—しかし、中括弧に依存することを思いとどまらせる=の代わりに引用符を付けるか、または本能的に中括弧を適用してから、「さて、引用符が必要ですかalso?正当な理由がない限り、常に引用符を使用する必要があります。あなたが何をしているかを知っています。
_________________
1もちろん、より洗練された パラメータ展開 の形式(たとえば、${parameter:-[Word]}および${parameter%[Word]})は、${parameter}構文に基づいています。また、10、11などの位置パラメータを参照するには、${10}${11}などを使用する必要があります。引用符は役に立ちません。

変数のスペースと空白|特殊文字を処理するには、常に二重引用符で囲む必要があります。適切なIFSを設定することも良い方法です。

推奨: http://www.dwheeler.com/essays/filenames-in-Shell.html

2
ramonovski