web-dev-qa-db-ja.com

中括弧を使用したbashサブシェルの作成

this によると、中括弧の間にコマンドのリストを配置すると、現在のシェルコンテキストでリストが実行されます。サブシェルは作成されません

psを使用してこの動作を確認する

これは、コマンドラインで直接実行されるプロセスパイプラインのプロセス階層です。 4398はログインシェルのPIDです。

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

コマンドラインで直接実行される中括弧間のプロセスパイプラインのプロセス階層に従います。 4398はログインシェルのPIDです。 これは、すべてが現在のシェルコンテキストで実行されることを証明する上記の階層に似ています

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

さて、これはパイプラインのsleep自体が中括弧の中に配置されている場合のプロセス階層です(したがって、全体で2レベルの中括弧)。

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

なぜ、中括弧内のコマンドが現在のシェルコンテキストで実行されるとドキュメントに記載されている場合、bashが3番目のケースでsleepを実行するサブシェルを作成する必要があるのですか?

33
iruvar

パイプラインでは、すべてのコマンドが同時に実行され(stdout/stdinがパイプで接続されているため)、異なるプロセスで実行されます。

cmd1 | cmd2 | cmd3

3つのコマンドはすべて異なるプロセスで実行されるため、少なくとも2つは子プロセスで実行する必要があります。一部のシェルは現在のシェルプロセスでそれらの1つを実行します(readのように組み込みの場合、またはパイプラインがスクリプトの最後のコマンドである場合)が、bashはそれらをすべて個別のプロセスで実行します(ただし、最近のlastpipeバージョンのbashオプションを使用する場合と、特定の条件下で)。

{...}グループコマンド。そのグループがパイプラインの一部である場合、単純なコマンドと同様に、別のプロセスで実行する必要があります。

に:

{ a; b "$?"; } | c

それを評価するためにシェルが必要ですa; b "$?"は別のプロセスなので、サブシェルが必要です。シェルは、そのグループで実行される最後のコマンドであるため、bをフォークしないことで最適化できます。一部のシェルはそれを行いますが、明らかにbashではありません。

28

中かっこを入れ子にすると、新しいサブシェルを呼び出す必要があるスコープレベルをさらに作成していることになります。この効果は、ps -H出力のBashの2番目のコピーで確認できます。

中括弧の最初のレベルで規定されているプロセスのみが、元のBashシェルのスコープ内で実行されます。ネストされた中括弧は、独自のスコープ付きBashシェルで実行されます。

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

ネストされた中括弧が見えるように| ps -Hをミックスから除外して、別のシェルでps auxf | lessを実行できます。

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

しかし、まだまだあります!

ただし、パイプを取り出してこの形式のコマンドを使用すると、実際に期待することがわかります。

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

結果のウォッチウィンドウで、2秒ごとに更新が行われます。

これが最初のsleep 10です:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

これが2番目のsleep 10です。

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

これが3番目のsleep 10です。

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

中括弧の異なるネストレベルで呼び出された3つすべてのスリープは、実際にはBashのPID 5676内に留まっていることに注意してください。ですから、あなたの問題は| ps -Hの使用によって自己に負わされていると思います。

結論

| ps -H(パイプなど)を使用すると、追加のサブシェルが発生するので、何が起こっているのかを調べるときにこのメソッドを使用しないでください。

19
slm

テストの結果を投稿します。これにより、bashがグループコマンドのサブシェルを作成し、パイプラインの一部である場合はonlyであると結論付けます。これは、サブシェルでも呼び出されるいくつかの関数。

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
7