Bashでサブシェルを呼び出すためのルールは?


24

サブシェルを作成するためのBashルールを誤解しているようです。かっこは常に独自のプロセスとして実行されるサブシェルを作成すると考えました。

しかし、これはそうではないようです。コードスニペットA(下記)では、2番目のsleepコマンドは別のシェルで実行されません(pstree別の端末で決定されます)。ただし、コードスニペットBでは、2番目のsleepコマンド別のシェルで実行されます。スニペットの唯一の違いは、2番目のスニペットが括弧内に2つのコマンドを持っていることです。

誰かがサブシェルを作成するときのルールを説明してもらえますか?

コードスニペットA:

sleep 5
(
sleep 5
)

コードスニペットB:

sleep 5
(
x=1
sleep 5
)

回答:


20

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

つまり、基本ケースは次のとおりです。

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

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

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

の呼び出し後に何か他のものを追加する場合sleep、サブシェルを保持する必要があるため、この最適化は発生しません。

の呼び出しの前に何かを追加するsleepと、最適化を行うことができます(そしてkshはそれを行います)が、bashはそれを行いません(この最適化では非常に保守的です)。


を呼び出すことでサブシェルが作成され、を呼び出すことforkで(外部コマンドを実行するために)子プロセスが作成されますfork + exec。しかし、あなたの最初のパラグラフは、それfork + execがサブシェルとも呼ばれることを示唆しています。ここで何が間違っていますか?
ハック

1
@haccks fork+ execはサブシェルではなく、外部コマンドで呼び出されます。最適化を行わない場合fork、サブシェルの呼び出しと外部コマンドの呼び出しがあります。回答に詳細なフローの説明を追加しました。
ジル 'SO-悪であるのをやめる'

更新してくれてありがとう。今ではより良く説明されています。(...)(基本的な場合)の場合exec、サブシェルに実行する外部コマンドがあるかどうかに依存する呼び出しがあるかどうかを推測できますが、外部コマンドを実行する場合はが必要ですfork + exec
ハック

もう1つの質問:この最適化はサブシェルに対してのみ機能dateしますか、それともシェルのようなコマンドに対して実行できますか?
ハック

@haccks質問がわかりません。この最適化は、シェルプロセスが最後に行うこととして外部コマンドを呼び出すことに関するものです。比較:それはサブシェルに限定されるものではないstrace -f -e clone,execve,write bash -c 'date'strace -f -e clone,execve,write bash -c 'date; true'
ジル「SO-停止されて悪」

4

高度なバッシュプログラミングガイド

「一般に、スクリプト内の外部コマンドはサブプロセスから分岐しますが、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

2
返信ありがとう。ティム。しかし、それが私の質問に完全に答えているかどうかはわかりません。「括弧の間に埋め込まれたコマンドリストはサブシェルとして実行される」ので、2番目sleepはサブシェルで実行されると予想されます(サブシェルのサブプロセスではなく、ビルトインであるため、おそらくサブシェルのプロセスで)。ただし、いずれにせよ、サブシェル、つまり親Bashプロセスの下のBashサブプロセスが存在すると予想されていました。上記のスニペットBの場合、これは当てはまらないようです。
内気な

修正:sleepビルトインではないように思われるためsleep、両方のスニペットの2番目の呼び出しがサブシェルプロセスのサブプロセスで実行されることを期待します。
内気な

@bashful私は自分の$BASHPID変数であなたのコードをハッキングする自由を取りました。悲しいことに、あなたがそれをしていた方法は、私が信じているすべての物語をあなたに与えていませんでした。答えに追加された出力を参照してください。
ティム

4

@Gillesの回答に対する追加のメモ。

ジルが言ったように: 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です。


1
特定のセクションでbashのマンページを開くための素敵なトリック
ダニエルセロディオ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.