コマンド置換はなぜこのように機能するのですか?

コマンド置換はなぜこのように機能するのですか?
$ echo $(echo x; echo y)
x y
$ a='echo x; echo y'
$ echo $($a)  # expect 'x y'
x; echo y
  1. コマンド置換はなぜこのように機能するのですか?
  2. とを使用せずにeval変数に保存されているコマンドのリストからコマンドの置き換えをどのように実行できますかbash -c

echo $(eval $a)実際にecho $(bash -c "$a")は私が望むようになるのにevalを使うのは問題を解決するために一般的に間違った方法だと聞いたので、これらのコマンドなしでどのように管理できるか疑問に思います(usingはbash -c実際に同じものです)。

答え1

参加は、指導評価の非常に遅く発生します。あなたにとって最も重要なことは、そのようなことが起こったということです。後ろに変数の拡張とコマンドの置換。

これは2行目を意味します。

s="echo a; echo b"
echo $($s)

まず$s展開しecho a; echo bてからコマンドを実行します。いいえ2つのsで構成された複合コマンドに分割しますecho

(詳細:は、、、および$s 4つの単語に分かれています。パッケージは、、、および3つのパラメータ、つまりデフォルトで持っているコマンドを使用してこれを実行します。)echoa;echob$(...)echoa;echobecho $('echo' 'a;' 'echo' 'b')

したがって、与えられた最も外側echoは文字列a; echo b(実際に$(...)は引用符なしで3つの単語)です。これはこれが最も内側の出力であるためですecho

比較する

s1="echo a"
s2="echo b"
echo $($s1;$s2)

すると、期待した結果が出るでしょう。

はい、「eval悪い」ということは、ほとんどの場合、sh -c自分が何をしているのかわからない場合は、おそらく厄介で脆弱だと感じます。しかし、信頼できるシェルコードがあると仮定すると文字列としてシェルスクリプトから。この場合、これらのツールはこのコードを正しく実行する唯一の(?)方法です。これは通常、文字列のテキストをシェルコード(最初から最後までのすべての評価ステップ)で明示的に評価する必要があるためです。複合コマンド


私はこれがUnixシェルとUnixシェルのためだと思います。テキストecho $($s)何かできることは幸運です別の言葉

文字列として与えられたCコードを実行するためにCプログラムを取得するために取る必要があるステップについて考えてみましょう。

答え2

少し読んだ後、私は自分で答えようとしました。

コマンド置換はなぜこのように機能するのですか?

$ a='echo x; echo y'
$ echo $($a)  # expect 'x y'

コマンドの置き換え

$a変数置換が実行されることに注意してください。サブシェルから現在のシェル以外のコマンド置換中に作成されます(12サム)。したがって、サブシェルは$a代わりにコマンドを実行しますecho x; echo y(変数の値はa継承されます)。

だから今調べるだけです。シェルはなぜこのように動作しますか?:

$ a='echo x; echo y'
$ $a
x; echo y

噴射

~によるとバッシュリファレンスマニュアル:

拡張には7つのタイプがあります...拡張順序は、チルダ拡張、パラメータと変数の拡張、算術拡張、およびコマンドの置き換え(左から右に完了)です。噴射;およびファイル名拡張子

——本項目から抜粋3.5 シェル拡張


シェルスキャンパラメータ拡張、コマンド置換、および算術拡張の結果単語の分離に使用される二重引用符内では、これは発生しません。

シェルはそれぞれを処理します。$IFSの文字区切り文字として使用し、他の拡張結果を単語に分割します。これらフィールド終端としての文字

——本項目から抜粋3.5.7 噴射

(デフォルトはIFS<space><tab><newline>


IFS変数は、すべての単語ではなく拡張結果を分割するためにのみ使用されます(単語の分割を参照)。

- から付録B Bourne Shellとの主な違い


メタ文字

引用しない文字は次のとおりです。別の単語。メタ文字は、スペース、タブ、改行、または、、、、、、|または文字のいずれ&かです。;()<>

——本項目から抜粋2. 定義


コマンドの読み取りと実行時にシェルが実行する操作の簡単な説明です。デフォルトでは、シェルは次のことを行います。

  1. 入力を読んでください。
  2. 引用規則に従い、入力を単語と演算子に分割します。これらのタグはメタ文字で区切られます。
  3. 解析トークン単純命令と複合命令に分けられます。
  4. 様々な実行シェル拡張
  5. 必要なリダイレクトを実行します。
  6. 注文の実行

——本項目から抜粋3.1.1 シェル動作

今私たちは実際に2つの異なる「スプレー」- 初期トークン化(ステップ2)とシェル拡張に属するトークン化(ステップ4)。

初期単語分割(ステップ2)では、、;同じ(メタ)文字を結果として処理します。解析済み(ステップ3)メタ文字とキーワードを識別できます。

その後、Bashは「単語の分離」を含むさまざまなシェル拡張(手順4)を実行します。その一つです。このタイプの単語分割は、文字を区切り文字としてのみ処理しますIFS。メタ文字は気にしません。その結果未解決繰り返しますが、他の拡張結果も同じです。したがって、メタ文字とキーワードは認識されません。

これが;、値内の変数がaコマンド区切り文字と見なされない理由です。の値があっても、その単語はメタ文字と見なされないため、コマンドはになります$a'echo' 'x;' 'echo' 'y'a'echo x ; echo y'';'$a'echo' 'x' ';' 'echo' 'y'

要約:

メタ文字とキーワード(ifthenなどwhile)は拡張の結果にはできませんが、プログラム名、組み込みコマンド、関数、エイリアスは可能です。単純なコマンドを変数に格納できますが、複合コマンドを使用して同じ操作を実行することは許可されていないため、少し混乱する可能性があります。

$ 'echo' 'a' ';' 'echo' 'b'  # ';' is a literal
a ; echo b

$ cmd=echo
$ "$cmd" a ; $(printf echo) b  # ';' is a metacharacter
a
b

$ cmd='echo a'  # simple command is executed properly
$ $cmd
a

$ cmd='echo a;'  # but metacharacters are not recognized
$ $cmd echo b
a; echo b

$ echo a $(echo ';') echo b  # ';' is a literal
a ; echo b

$ $(printf if) true; then echo a; fi  # parsing error
bash: syntax error near unexpected token 'then'

$ $(printf if) true; $(printf then) echo a; $(printf fi)  # looking for command "if"
bash: if: command not found
bash: then: command not found
bash: fi: command not found

evalとbash -cを使わずに変数に格納されているコマンドのリストからコマンドの置き換えをどのように実行できますか? (evalは邪悪だからです)

おそらく正解は「それも邪悪です」であるので、evalを避ける必要はありません:)

変数はデータを保持します。関数はコードを保存します。変数にコードを入れないでください! ..

——記事から抜粋コマンドを変数に入れようとしましたが、複雑な場合は常に失敗します!

リンク

Unixスタック交換:

Bash シェルコマンドの置換

サブシェルに変数が表示されるのはなぜですか?

角かっこは実際にコマンドをサブシェルに入れますか?

その他:

バッシュリファレンスマニュアル

コマンドを変数に入れようとしましたが、複雑な場合は常に失敗します!

Debian バグ追跡システムでの会話

関連情報