シェルコマンド置換の予期しない動作

シェルコマンド置換の予期しない動作

シェルスクリプトが与えられるとtest.sh

for var in "$@"
do
    echo "$var"
done

そして電話してくださいsh test.sh $(echo '"hello " "hi and bye"')

出力を期待した

hello
hi and bye

しかし、以下を得る:

"hello
"
"hi
and
bye"

私が次に変更した場合sh test.sh $(echo "hello " "hi and bye")

それから私は得ます

hello
hi
and
bye

これらの動作のどれも望ましくない。目的の動作を取得するにはどうすればよいですか?

私が理解したいのはなぜ同じではありませんかsh test.sh $(echo "hello " "hi and bye")sh test.sh "hello " "hi and bye"

答え1

コマンド置換は文字列を生成します。

の場合

$(echo '"hello " "hi and bye"')

文字列は"hello " "hi and bye"

その後、文字列がトークン化されます(ファイル名のグロービングも同様ですが、この例には影響しません)。トークン化は、文字の1つと同じすべての文字$IFS(デフォルトでは空白、タブ、および改行)で発生します。

デフォルト値は、、、、および単語IFS"hello生成"します。 "hiandbye"

その後、スクリプトに別々のパラメーターとして提供します。

2番目のコマンドでは、コマンドの置き換えは次のようになります。

$(echo "hello " "hi and bye")

その後、文字列が生成され、トークン化は、、、およびhello hi and bye4つの単語を生成します。hellohiandbye

最後の例では二つパラメータを使用helloし、hi and byeスクリプトと共に直接使用されます。これらに慣れる単語の分割は引用されているために発生します。

答え2

長い話を短く:の結果はecho結果分割の犠牲者$()であり、sh test.sh "hello " "hi and bye" 他の規則を使用します。

set -xデバッグを追加するときに実際に何が起こるのか見てみましょう。

> set -x
> ./main.sh $(echo '"hello " "hi and bye"')
++ echo '"hello " "hi and bye"'
+ ./main.sh '"hello' '"' '"hi' and 'bye"'
"hello
"
"hi
and
bye"

echo"hello " "hi and bye"これは、コマンド自体の一重引用符のため、単一の引数(たとえば、すべてのスペースと一重引用符を含む)で受け取ります。ただし、コマンド置換はこれを"hello " "hi and bye".dereferenceに置き換えます。バッシュマニュアル:

二重引用符で置換が発生すると、単語の区切りとパス名の拡張は行われません。結果について

ただし、引用符なしでコマンドを置き換えると、$IFS空白文字(単語分割用の変数に設定されている3文字のうちの1つ)に基づいて単語分割が許可されます。したがって、結果の文字列をすべてのスペースで破棄すると何が得られますか?

  1. "hello、空白、その後に次のトークンがあります。
  2. "- 両側にスペースで区切られた文字自体です。
  3. "hi、再び左と右にスペースがあります。
  4. and
  5. bye"

全体的に空白のために破損した5つのトークンが得られます。この場合、噴射は正確に命令置換の結果であることを認識することが重要である。その後、それらはすべて解析された個々のトークンとして扱われます。set -x出力が示すように、シェルスクリプトはこれらのタグを使用して呼び出されます。

パフォーマンスが異なる理由は、適用されるルールが異なるsh test.sh "hello " "hi and bye"ためです。入力したコマンドラインは入力と同時に解析され、引用符付き文字列は単一の単位として扱われますhellohi and bye

答え3

スペース以外のものに変更することができますIFS( のスペースを保護するためhi and bye)。これを使用して2つの文字列を区切ります。

$ IFS=:
$ sh test.sh $(echo "hello ":"hi and bye")
hello 
hi and bye

(とにかくこの例に関連する)操作の順序は次のとおりです。

  1. 変える
  2. 噴射
  3. 見積もりの​​削除

代替結果として生成された引用符は、トークン化または引用符の削除に関して特別ではないため、(1)出力の引用符は他の文字と同様に(2)と(3)を通過します。だから:

  1. echo "hello " "hi and bye"シェルはこれらの引用符を削除してechosumhellohi and bye出力を取得し、文字列をスペースでhello hi and bye連結します。
  2. echo '"hello " "hi and bye"'シェルは外側を取り除き、echo'はそれを取り出して"hello " "hi and bye"出力します。
  3. コマンド置換では でsh test.sh $(echo '"hello " "hi and bye"')置き換えられますが"hello " "hi and bye"これら引用符は置換の結果であるため、単語の区切りや引用符の削除は不要です。

    したがって、シェルはこれを、、、、"helloに分割して出力します。""hiandbye"

  4. では、sh test.sh $(echo "hello " "hi and bye")コマンド置換がで置き換えられ、これは、およびに分割hello hi and byeされます。hellohibye

答え4

どうですか?

sh test.sh "hello " "hi and bye"

関連情報