web-dev-qa-db-ja.com

なぜbashは単純なコマンドのサブシェルを生成しないのですか?

次の2つのbashコマンドについて考えてみます。 1つはサブシェルを作成し、もう1つは作成しません。

サブシェルなし

$ bash -c "sleep 10000 "

pstree出力:

bash,100648
  └─sleep,103509 10000

サブシェル付き

$ bash -c "sleep 10000; sleep 99999 "


bash,100648
  └─bash,103577 -c sleep 10000; sleep 99999
      └─sleep,103578 10000

これはある種の最適化ですか? Bashはコマンドを確認し、単純なコマンドのプロセス作成をスキップします。単純なコマンドが親端末のbashによって生成されたように見えます。


私が使用しているBashバージョンは

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

ただし、bash3.Xでも同じことが観察できると思います。


さらにいくつかの例。ビルトインを使用すると、サブシェルが生成されます。ビルトインは親bashでは実行されません。

$ bash -c "read"

bash,100648
  └─bash,104336 -c read

sleepでの同じ動作は、topと出力リダイレクトで再現できます。

$ bash -c "top -b "
bash,100648
  └─top,104392 -b

そして

$ bash -c "top -b > /dev/null "
bash,100648
  └─bash,104420 -c top -b > /dev/null
      └─top,104421 -b
5
Hakan Baba

pstreeの出力は誤解を招く恐れがあります。

_bash -c "sleep 10000 "
_

bashの子プロセスを作成します。ただし、このプロセスは別の子プロセスを作成しません。 sleepを実行した後は何もすることがないため、シェルは最初にフォークせずにexecve()sleepに直接送信します。

これは非常に高速であるため、execvepstreeの後に結果が表示されます。

しかし、

_bash -c "sleep 10000; sleep 99999 "
_

新しいbashフォークをコマンドごとに1回ずつ、2回ケースに入れます。最初にフォークする代わりに、最後のコマンドにexecveを作成することもできます。なぜそうならないのかわかりません。

これとリダイレクトの場合は、おそらくフォークが必要な場合の検出に関する問題にすぎません。

3
Hauke Laging

最初の例では、PID 100648のシェルがインタラクティブなシェルですが、sleepは実際には_bash -c ''_プロセスを置き換えたプロセスです。 execve() syscall は、それを呼び出した元のプロセスのPIDを保持する新しいプログラムを生成します。したがって、元の_bash -c ''_が表示されないのはなぜですか。単純なコマンドがシェルプロセスに置き換わる単純な理由は、 ステファンの答え で説明されているようにリソースを節約するためです。

これは、1つの端末での簡単なテストからも明らかです。

_$ echo $$
10250
$ bash -c 'sleep 60'
_

そして別の:

_$ pstree -p 10250
bash(10250)───sleep(21031)
_

このように発生する理由は、パイプのない単純なコマンドがあり、bash単純なコマンドに対して直接実行を実行する であるためです。

まさにその理由で、bashは、2番目の例に複数のコマンドステートメントがあることを認識します。したがって、_"sleep 10000; sleep 99999 "_にあるsleepコマンドごとに。最初のケースでは、execve()は親プロセスを置き換えるだけで、新しいプロセスが終了したときに問題ありません。ただし、ここでは、最初のコマンドが完了した後、シェルを終了できません。したがって、_sleep 10000_のforkとexecveがあります。これは、pstreeの出力に表示され、後で_sleep 99999_の新しいforkとexecveが表示されます。


_bash -c ''_の単純なコマンドがシェルプロセスを置き換えると信じるために実行できる別のテストは次のとおりです。

_$ bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  23946
/proc/23946/status:Pid: 23946
_

このような動作はbash固有であることに注意してください。 Debianベースのディストリビューションの_/bin/dash_の場合(およびstraceでも明らかです):

_$ sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  24188
/proc/24187/status:Pid: 24187
_
1