エコーではなくifで変数を引用する必要があるのはなぜですか?


26

変数を展開するには二重引用符が必要であることを読みました。例えば

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

期待どおりに動作しますが、

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

がnullで$test okあっても常に言い$testます。

しかし、なぜ引用符が必要ないのecho $testですか?


2
の引数として使用する変数を引用符で囲まない場合echo、余分なスペースと改行が削除されます。
ヨルダン

回答:


36

すべてのリストコンテキストの変数は常に引用符で囲む必要があります。つまり、変数を引用符で囲まずに残す3つの副作用が必要な場合を除き、変数はどこでも複数の値に展開できます。

リストコンテキストでは、単純なようなコマンドに引数を含める[echofor i in <here>変数も引用符で囲む必要がある他のコンテキストがあります...アレイに、代入。最善の理由は、そうしない理由がない限り、常に変数を引用することです。

(リストコンテキストで)引用符がないことはsplit + glob演算子と考えてください。

かのようecho $testでしたecho glob(split("$test"))

ほとんどの人にとって、シェルの動作は混乱を招きます。他のほとんどの言語でputs("foo")は、変数(などputs(var))ではなくのような固定文字列を引用符で囲みますが、シェルでは逆です:すべてがシェル内の文字列なので、すべてを引用符で囲みます面倒です、あなたecho test、する必要はありません"echo" "test"。シェルでは、引用符は他の何かに使用されます。一部の文字の特別な意味を回避したり、一部の展開の動作に影響を与えたりします。

では[ -n $test ]またはecho $test、シェルが分割されます$test(デフォルトでは空白に)した後、ファイル名の生成を行う(すべての拡大*、「?」...パターンを一致するファイルのリストに)、その後に引数のリストを渡し[たりechoするコマンド。

ここでも、と考えます"[" "-n" glob(split("$test")) "]"$testが空の場合、または空白(spc、tab、nl)のみを含む場合、split + glob演算子は空のリストを返します。したがって、[ -n $test ]は-が"[" "-n" "]"空の文字列であるかどうかを確認するテストです。しかし、$test「*」または「= foo」だったらどうなるか想像してみてください...

では[ -n "$test" ][四つの引数を渡され"[""-n"""そして"]"私たちが望むものである、(引用符なし)。

それはだかどうechoか、[違いはありません、それはそれだけだecho、それが空の引数または全く引数を渡されたかどうか、同じことを出力します。

コマンドと構成の詳細については、同様の質問に対するこの回答も参照してください。[[[...]]


7

@ h3rrmillerの答えはのためにあなたが引用符を必要とする理由を説明するための良いですif(というか、[/ test)が、私は実際にあなたの質問が間違っていることを断定う。

次のコマンドを試してみてください、そうすれば私の意味がわかります。

export testvar="123    456"
echo $testvar
echo "$testvar"

引用符がないと、変数置換により2番目のコマンドが次のように展開されます。

echo 123    456

また、複数のスペースは単一のスペースに縮小されます。

echo 123 456

引用符を使用すると、スペースが保持されます。

ので、これは起こりますが(そのパラメータが渡されているかどうかのパラメータを引用する場合echotestまたは他のいくつかのコマンド)、そのパラメータの値は以下のように送信される1つのコマンドに値。引用符で囲まない場合、シェルは空白を検索するという通常のマジックを実行して、各パラメーターの開始位置と終了位置を決定します。

これは、次の(非常に単純な)Cプログラムでも説明できます。コマンドラインで次のことを試してください(何かを上書きしないように、空のディレクトリで実行することもできます)。

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

その後...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

を実行するとparamtest$?渡されたパラメーターの数が保持されます(その数が出力されます)。


2

これは、プログラムが実行される前にシェルが行をどのように解釈するかに関するすべてです。

行が読み取るとecho I am $USER、シェルはそれを拡大echo I am blrflし、echoテキストの起源は、リテラルまたは変数展開であるかどうかを手掛かりを持っていません。同様に、行がの場合echo I am $UNDEFINED、シェルは$UNDEFINED何にも展開せず、エコーの引数はになりI am、これで終わりです。echo引数なしで正常に動作するため、echo $UNDEFINED完全に有効です。

あなたの問題はif本当にではないifので、ifどのようなプログラムと引数だけで実行は、それに従うと、実行thenプログラムが終了する場合には一部の0(またはelse部分が存在する場合、プログラムは非終了します0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

if [ ... ]比較に使用する場合、シェルに組み込まれたプリミティブを使用していません。実際には、最後の引数がを必要[とする非常にわずかなスーパーセットであるプログラムを実行するようにシェルに指示していtest(1)ます]0テスト条件が真である1場合とそうでない場合、両方のプログラムは終了します。

変数が未定義のときに一部のテストが失敗する理由testは、変数を使用していることがわからないためです。エルゴ、[ $UNDEFINED -eq 2 ]壊れたのは、シェルがそれで終了するまでに、すべてのtest引数が-eq 2 ]であるため、有効なテストではないからです。[ $DEFINED -ne 0 ]シェルなどの有効なテスト(など0 -ne 0)に展開するため、のように定義された何かでそれを行った場合は機能します。

にはセマンティックな違いがありますfoo $UNDEFINED bar。これは、名前に対応しているためfoo、2つの引数(およびbar)に展開$UNDEFINEDされます。これfoo "$UNDEFINED" bar3つの引数(foo、空の文字列と `bar)に展開されると比較してください。引用符は、それらの間に何かがあるかどうかに関係なく、シェルがそれらを引数として解釈することを強制します。


0

引用符$testがないと複数の単語に展開される可能性があるため、[コマンド内の各スイッチは引用符が何をするかを1つの引数を期待しているため、構文を壊さないために引用符で囲む必要があります($test展開されたものはすべて1つの引数になります)

変数を展開するのに引用符が必要ないのechoは、1つの引数を期待していないためです。それは単にあなたがそれを伝えるものを印刷します。その$testため、100ワードに拡張しても、エコーはそれを出力します。

見てみましょうBashの落とし穴


はい、でもなぜ必要ないのechoですか?
CharlesB

@CharlesBの引用符は必要ですecho。そうでないと思う理由は何ですか?
ジル「SO-悪であるのをやめる」

私はそれらを必要としない、私はできるecho $test、そしてそれは働く(それは$ testの値を出力する)
-CharlesB

1
@CharlesB複数のスペースがどこにも含まれていない場合にのみ、$ testの値を出力します。その理由を説明するために、私の答えのプログラムを試してください。
CVn

0

引用符で囲まれていない場合、空のパラメーターは削除されます。

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

呼び出されたコマンドは、シェルコマンドラインに空のパラメーターがあったことを認識しません。[は、-nに対して0を返すように定義されているようです。とにかく。

引用もいくつかの場合にエコーに違いをもたらします:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
echoではなく、シェルです。同じ動作が表示されlsます。touch '*'冒険心があると感じたら、しばらく試してください。:)
CVn

「if [...]」の場合と違いがないので、それは単なる言い回しです。[は、特別なシェルコマンドではありません。これは、引用が不要な[[(bashで)とは異なります。
ハウケレイジング
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.