web-dev-qa-db-ja.com

Bashの$(コマンド置換)内の引用

私のBash環境では、スペースを含む変数を使用しており、コマンド置換内でこれらの変数を使用しています。残念ながら私はSEで答えを見つけることができません。

変数を引用する正しい方法は何ですか?そして、これらがネストされている場合はどうすればよいですか?

DIRNAME=$(dirname "$FILE")

または私は置換の外で引用しますか?

DIRNAME="$(dirname $FILE)"

または両方?

DIRNAME="$(dirname "$FILE")"

またはバックティックを使用しますか?

DIRNAME=`dirname "$FILE"`

これを行う正しい方法は何ですか?また、見積もりが正しく設定されているかどうかを簡単に確認するにはどうすればよいですか?

136
CousinCocaine

ワーストからベストの順に:

  • DIRNAME="$(dirname $FILE)"希望どおりに動作しません$FILEに空白文字またはグロビング文字\[?*が含まれている場合。
  • DIRNAME=`dirname "$FILE"`は技術的には正しいですが、ネストするときの複雑さが増すため、 バッククォートはコマンド展開には推奨されません です。
  • DIRNAME=$(dirname "$FILE")は正しいですが、 これは割り当てであるためのみ です。 export DIRNAME=$(dirname "$FILE")du $(dirname "$FILE")などの他のコンテキストでコマンド置換を使用する場合、展開の結果に空白文字またはグロビング文字が含まれていると、引用符がないと問題が発生します。
  • DIRNAME="$(dirname "$FILE")"が推奨される方法です。 DIRNAME=は、何も変更せずにコマンドとスペースに置き換えることができ、dirnameは正しい文字列を受け取ります。

さらに改善するには:

  • DIRNAME="$(dirname -- "$FILE")"は、$FILEがダッシュで始まる場合に機能します。
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"は、$FILEが改行で終わっていても機能します。これは、$()が出力の最後で改行を切り捨て、dirnameが結果の後に改行を出力するためです。 Sheesh dirname、どうして違うの?

コマンド拡張は好きなだけネストできます。 $()を使用すると、常に新しい引用コンテキストが作成されるため、次のようなことができます。

foo "$(bar "$(baz "$(ban "bla")")")"

あなたはしないでくださいバッククォートでそれを試したいです。

196
l0b0

printfを使用すると、変数の引用の効果をいつでも表示できます。

_var1_で単語分割が行われました:

_$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]
_

_var1_引用符で囲まれているため、Word分割はありません:

_$ printf '[%s]\n' "$var1"
[hello     world]
_

_var1_と同等の$()内の_echo "hello" "world"_での単語分割:

_$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]
_

_var1_での単語の分割はなく、$()を引用しないことで問題はありません。

_$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]
_

再び_var1_で単語分割:

_$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]
_

両方を引用すると、最も簡単な方法です。

_$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]
_

グラブ問題

変数を引用符で囲まないと、その内容がグロブ展開される可能性があります。

_$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]
_

これは、変数が展開された後にのみ発生することに注意してください。割り当て時にグロブを引用する必要はありません:

_$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]
_

_set -f_を使用して、この動作を無効にします。

_$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]
_

そして、再度有効にするには_set +f_を使用します。

_$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
_
19
Graeme

受け入れられた回答への追加:

私は一般的に @ l0b0の答え に同意しますが、 "最悪"のリストの裸のバックティックの配置は、少なくとも部分的に$(...)がどこでも利用できます。私は質問がBashを指定していることを理解していますが、Bashが_/bin/sh_を意味することが判明する場合が多くありますが、実際には必ずしもBourne Againシェル全体ではない場合があります。

特に、プレーンなBourne Shellは$(...)で何をすべきかを知らないので、それと互換性があると主張するスクリプト(例えば、_#!/bin/sh_ Shebang行を介して)は、それらが動作しない可能性があります。実際には「実際の」_/bin/sh_によって実行されます。これは、たとえばinitスクリプトを生成したり、前後のスクリプトをパッケージ化したりする場合に特に重要であり、ベースシステムのインストール中に驚くべき場所に配置されます。 。

この変数を使用して計画しているように聞こえる場合は、スクリプトを実際に予測どおりに実行するよりも、入れ子にすることの方が問題ではないでしょう。それが十分に単純なケースであり、移植性が懸念される場合、たとえ_/bin/sh_がBashであるシステムでスクリプトが通常実行されることを期待していても、このため、ネストの代わりに複数の割り当てを使用して、バックティックを使用する傾向があります。

そうは言っても、$(...)を実装するシェルの遍在性(Bash、Dashなど)は、より美しく、入れ子になりやすく、最近好まれている場所に私たちを置きます。 @ l0b0が言及するすべての理由により、ほとんどの場合POSIX構文。

余談ですが、これはStackOverflowでも時々発生します–

12
rsandwick3