昨日、bashでコマンド置換を使用すると不要なサブシェルが生成されるというアドバイスがありました。そのアドバイスはこのユースケース:
# Extra subshell spawned
foo=$(command; echo $?)
# No extra subshell
command
foo=$?
私の理解では、この使用例ではこれが正しいようです。しかし、これを確認するために簡単に検索してみると、混乱を招き矛盾したアドバイスが山ほど見つかります。一般的な見解では、コマンド置換のすべての使用でサブシェルが生成されるようです。たとえば、次のようになります。
コマンド置換はコマンドの出力に展開されます。これらのコマンドはサブシェルで実行されます、そしてそれらの stdout データは、置換構文が展開されるものです。(ソース)
これは、さらに詳しく調べない限りは、十分単純なことのように思えます。さらに詳しく調べると、そうではないことを示唆する参照が見つかり始めます。
コマンド置換必ずしもサブシェルを呼び出すわけではない、ほとんどの場合はそうはなりません。唯一保証されるのは、順序どおりに評価されないことです。つまり、まず置換内の式を評価し、次に置換の結果を使用して周囲のステートメントを評価するだけです。(ソース)
これは合理的に思えますが、本当でしょうか?この答えサブシェルに関連する質問で、man bash
次の点に注意する必要があることがわかりました。
パイプライン内の各コマンドは、個別のプロセスとして(つまり、サブシェル内で)実行されます。
これで主な質問に移ります。正確には、コマンド置換によって、同じコマンドを単独で実行するために生成されることのないサブシェルが生成されるのはなぜでしょうか?
以下のケースを検討し、どのケースで追加のサブシェルのオーバーヘッドが発生するかを説明してください。
# Case #1
command1
var=$(command1)
# Case #2
command1 | command2
var=$(command1 | command2)
# Case #3
command1 | command 2 ; var=$?
var=$(command1 | command2 ; echo $?)
これらの各ペアでは、実行するサブシェルの数は同じですか? POSIX と bash の実装に違いはありますか?コマンド置換を使用するとサブシェルが生成されるが、同じコマンドセットを単独で実行するとサブシェルが生成しないケースは他にもありますか?
ベストアンサー1
更新と注意事項:
この答えは、私が自信を持って主張したことが真実ではないことが判明したという問題のある過去を持っています。私はそれが価値があると信じています現在フォームですが、その他の不正確な点をなくすのにご協力ください(または、完全に削除する必要があることを納得させてください)。
私はこの回答は大幅に修正され、ほとんど削除された。@kojiro が私のテスト方法に欠陥があると指摘した後 (最初はps
子プロセスを探していましたが、それでは子プロセスを常に検出するには遅すぎます)、新しいテスト方法を以下に説明します。
当初、すべての bash サブシェルが独自の子プロセスで実行されるわけではないと主張しましたが、それは真実ではないことが判明しました。
@kojiro が回答で述べているように、いくつかのbash以外のシェルは、サブシェルの子プロセスの作成を避けることがあるので、一般的にシェルの世界では、サブシェルが子プロセスを意味するとは想定しないでください。
OPのケースに関してはバッシュ(command{n}
インスタンスが簡単なコマンド):
# Case #1
command1 # NO subshell
var=$(command1) # 1 subshell (command substitution)
# Case #2
command1 | command2 # 2 subshells (1 for each pipeline segment)
var=$(command1 | command2) # 3 subshells: + 1 for command subst.
# Case #3
command1 | command2 ; var=$? # 2 subshells (due to the pipeline)
var=$(command1 | command2 ; echo $?) # 3 subshells: + 1 for command subst.;
# note that the extra command doesn't add
# one
$(...)
コマンド置換( )を使用しているようですbashには常に追加のサブシェルを追加します- 任意のコマンドを で囲む場合も同様です(...)
。
これらの結果が正しいと信じていますが、確信はありません; 私がテストした方法は次のとおりです(OS X 10.9.1上のbash 3.2.51) -このアプローチに欠陥があるかどうか教えてください:
- 対話型 bash シェルが 2 つだけ実行されていることを確認しました。1 つはコマンドを実行するためのもので、もう 1 つは監視するためのものです。
- 2 番目のシェルでは、
fork()
1 番目のシェルの呼び出しを監視しましたsudo dtruss -t fork -f -p {pidOfShell1}
(呼び出しを「推移的に」-f
トレースするために、つまりサブシェル自体によって作成された呼び出しを含めるために、 が必要ですfork()
)。 テスト コマンドでは組み込み
:
(no-op) のみを使用しました (外部実行可能ファイルの追加呼び出しによる混乱を避けるためfork()
)。具体的には、次のとおりです。:
$(:)
: | :
$(: | :)
: | :; :
$(: | :; :)
ゼロ以外の PID を含む出力行のみをカウントしました
dtruss
(各子プロセスもfork()
それを作成した呼び出しを報告しますが、PID は 0 です)。- 結果の数値から 1 を減算しました。対話型シェルから組み込みを実行するだけでも、少なくとも 1 が必要になるようです
fork()
。 - 最後に、結果のカウントが作成されたサブシェルの数を表すと仮定します。
以下は、私の最初の投稿から今でも正しいと信じている内容です: bash がサブシェルを作成する場合。
bash は次の状況でサブシェルを作成します。
(...)
括弧( ) で囲まれた式の場合- を除外するの内側に直接挿入します
[[ ... ]]
。括弧は論理的なグループ化にのみ使用されます。
- を除外するの内側に直接挿入します
- のために毎パイプラインのセグメント(
|
)を含む初め1つ- ご了承ください毎関与するサブシェルは、オリジナルシェルに関してコンテンツ(プロセス的には、サブシェルは他のサブシェルからフォークすることができます(前に
したがって、パイプラインの前のセグメントのサブシェルの変更は、後のセグメントには影響しません。
(設計上、パイプラインのコマンドは、同時に- シーケンスは接続された stdin/stdout パイプを通じてのみ行われます。 bash 4.2+
シェルオプションlastpipe
(デフォルトではOFF)があり、最後パイプライン セグメントはサブシェルで実行されません。
- ご了承ください毎関与するサブシェルは、オリジナルシェルに関してコンテンツ(プロセス的には、サブシェルは他のサブシェルからフォークすることができます(前に
コマンド置換(
$(...)
)プロセス置換(
<(...)
)- 通常は2サブシェルの場合、簡単なコマンド、@konsoleboxがテクニックを考案した作成するだけ1: 単純なコマンドの前に
exec
(<(exec ...)
) を追加します。
- 通常は2サブシェルの場合、簡単なコマンド、@konsoleboxがテクニックを考案した作成するだけ1: 単純なコマンドの前に
- バックグラウンド実行 (
&
)
これらの構造を組み合わせると、複数のサブシェルが生成されます。