Bashでサブシェルを呼び出すルールは何ですか?

Bashでサブシェルを呼び出すルールは何ですか?

サブシェルを生成するためのBashルールを誤解したようです。私は角括弧が常に独自のプロセスとして実行されるサブシェルを生成すると思いました。

しかし、これは本当ではないようです。コードスニペットA(下)では、2番目のsleepコマンドは別のシェル(pstree他の端末によって決定されます)では実行されません。しかし、コードフラグメントBでは、2番目のコマンドsleepする別のシェルで実行します。これらのスニペット間の唯一の違いは、2番目のスニペットには角かっこ内に2つのコマンドがあることです。

サブシェルの作成規則を説明できる人はいますか?

コードスニペットA:

sleep 5
(
sleep 5
)

コードピースB:

sleep 5
(
x=1
sleep 5
)

答え1

括弧は常にサブシェルを開始します。何が起こるかは、bashがこれがsleep 5そのサブシェルによって実行された最後のコマンドであることを検出してexec代わりに呼び出すことです。fork+exec。このsleepコマンドは、同じプロセス内のサブシェルを置き換えます。

つまり、基本的なケースは次のようになります。

  1. ( … )サブシェルを作成します。元のプロセスはとforkを呼び出しますwait。サブプロセスでは、これはサブシェルです。
    1. sleep子プロセスの子プロセスが必要な外部コマンドです。サブシェル呼び出しforkwait。子サブプロセスでは:
      1. 子サブプロセスは外部コマンド→を実行しますexec
      2. 最終コマンドが終了します→ exit
    2. waitサブシェルで実行されます。
  2. wait元のプロセスで完了しました。

最適化は次のとおりです。

  1. ( … )サブシェルを作成します。元のプロセスはとforkを呼び出しますwait。サブプロセス内では、次を呼び出すまでサブシェルですexec
    1. sleepこれは外部コマンドであり、プロセスが実行する必要がある最後の操作です。
    2. サブプロセスは外部コマンド→を実行しますexec
    3. 最終コマンドが終了します→ exit
  2. wait元のプロセスで完了しました。

呼び出し後に他のものを追加する場合は、これらのsleep最適化が発生しないようにサブシェルを保存する必要があります。

呼び出し前に他のものを追加すると最適化が行われますがsleep(kshはこれを実行します)、bashはそうではありません(この最適化は非常に保守的です)。

答え2

~から高度なBashプログラミングガイド:

「通常、スクリプトの外部コマンドは子プロセスを分岐しますが、Bashの組み込み機能はそうではありません。したがって、組み込み機能は外部コマンドに対応するコマンドよりも速く実行され、より少ないシステムリソースを使用します。」

もう少し下へ:

「括弧内に含まれるコマンドのリストはサブシェルとして実行されます。」

例:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

OPコードの使用例

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

出力:

[root@talara test]# bash sub_bash
6606
6608
6608

答え3

@Gillesの答えのさらなる説明です。

Gilesが言ったように:The parentheses always start a subshell.

ただし、これらのサブシェルには重複した番号がある可能性があります。

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

ご覧のとおり、$$ は繰り返されます。これは次の理由で予想されます(正しいman bash行を見つけるにはこのコマンドを実行します)。

$ LESS=+/'^ *BASHPID' man bash

BASHPIDは、
現在のbashプロセスのプロセスIDに展開されます。これは、bashを再初期化する必要がないサブシェルのような場合に応じて$ $とは異なります。

つまり、シェルが再初期化されない場合、$$ は同じです。

または以下を使用してください。

$ LESS=+/'^ *Special Parameters' man bash

特殊パラメータ
$はシェルのプロセスIDに展開されます。 ()サブシェルでは、サブシェルではなく現在のシェルのプロセスIDに展開されます。

$$現在のシェル(サブシェルではない)のIDです。

関連情報