exit
コマンドが同じシェルで実行されるため、exit
を実行すると現在のシェルが終了することを理解しています。また、exit &
を実行すると、元のシェルが終了しないことも理解しています。これは、&
によってコマンドがサブシェルで実行されるため、exit
がこのサブシェルを終了して戻るためです。元のシェルに戻ります。しかし、私が理解していないのは、&
があるコマンドとないコマンドが、pstree
、この場合はsleep 10
とsleep 10 &
でまったく同じに見える理由です。 4669は、最初にsleep 10
、次にsleep 10 &
が発行され、この間に別のシェルインスタンスから次の出力が取得されたbashのPIDです。
# version without &
$ pstree 4669
bash(4669)───sleep(6345)
# version with &
$ pstree 4669
bash(4669)───sleep(6364)
&
のバージョンには、このように、スポーンされたサブシェルがもう1つ含まれているべきではありませんか(この場合、PID 5555の場合など)?
bash(4669)───bash(5555)───sleep(6364)
PS:読みやすくするために、pstree
の出力から次のコードが省略されました:
systemd(1)───slim(1009)───ck-launch-sessi(1370)───openbox(1551)───/usr/bin/termin(4510)───bash(4518)───screen(4667)───screen(4668)───
この質問に答え始めるまで、_&
_制御演算子を使用してジョブを実行することに気づいていませんでしたバックグラウンドでサブシェルを開始します。サブシェルは、コマンドが括弧で囲まれている場合、またはパイプラインの一部を形成している場合に作成されます(パイプライン内の各コマンドは独自のサブシェルで実行されます)。
コマンドのリスト Bashマニュアルのセクション(thanks jimmij)は次のように述べています。
コマンドが制御演算子「&」によって終了した場合、シェルはサブシェルでコマンドを非同期的に実行します。これは、backgroundでコマンドを実行することとして知られています。シェルはコマンドが終了するのを待たず、戻りステータスは0(true)です。
私が理解しているように、_sleep 10 &
_を実行すると、シェル fork sで新しい子プロセス(それ自体のコピー)が作成され、すぐに exec sで置き換えられます。この子プロセスは、外部コマンド(sleep
)からのコードを使用します。これは、コマンドが通常どおり(フォアグラウンドで)実行されたときに発生することと似ています。このメカニズムの概要については、 Fork–exec Wikipediaの記事 を参照してください。
Bashがサブシェルでバックグラウンドコマンドを実行する理由を理解できませんでしたが、exit
やecho
などのシェルビルトインをバックグラウンドで実行できるようにしたい場合は理にかなっています(外部コマンドだけではありません)。
バックグラウンドで実行されているのがシェルビルトインの場合、外部コマンドに置き換えるためのfork
呼び出しなしで、exec
が発生します(サブシェルになります)。次のコマンドを実行すると、echo
コマンドが中括弧で囲まれ、バックグラウンドで(_&
_を使用して)実行されると、サブシェルが実際に作成されることがわかります。
_$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064
_
上記の例では、現在のシェルによって_BASH_SUBSHELL
_が展開されないように、echo
コマンドを中括弧で囲んでいます。中括弧は、サブシェルを使用せずにコマンドをグループ化するために使用されます。コマンドの2番目のバージョン(_&
_制御演算子で終わる)は、コマンドをアンパサンドで終了すると、echo
ビルトインを実行するためのサブシェル(新しいPID)が作成されたことを明確に示しています。 。 (ここではおそらくシェルの動作を単純化しています。mikeservのコメントを参照してください。)
_exit &
_を実行することを考えたことはなかったでしょうし、あなたの質問を読まなかったら、現在のシェルが終了することを期待していました。このようなコマンドがサブシェルで実行されることがわかったので、終了するのはサブシェルであるという説明は理にかなっています。
「バックグラウンドコントロールオペレーター(&)によって作成されたサブシェルがpstreeの下に表示されないのはなぜですか」
上記のように、_sleep 10 &
_を実行すると、Bashはそれ自体をフォークしてサブシェルを作成しますが、sleep
は外部コマンドであるため、exec()
システムコールを呼び出してすぐにBashを置き換えます。 sleep
プログラムの実行中のコピーを使用した子プロセスのコードとデータ。 pstree
を実行するまでに、exec
呼び出しはすでに完了しており、子プロセスの名前は「sleep」になります。
コンピューターから離れている間、サブシェルがpstree
で表示されるのに十分な時間サブシェルを実行し続ける方法を考えようとしました。 time
ビルトインを介してコマンドを実行できると思いました。
_$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
└─pstree(4504)
_
ここで、Bashシェル(26793)は、コマンドをバックグラウンドで実行するためにサブシェル(4502)を作成するためにフォークします。このサブシェルは、独自のtime
組み込みコマンドを実行します。このコマンドは、(PID 4503で新しいプロセスを作成するために)フォークし、外部のsleep
コマンドを実行するために実行します。
名前付きパイプ を使用して、jimmijは、exit
を実行するために作成されたサブシェルを存続させるための賢い方法を考え出しました。 pstree
で表示:
_$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
└─pstree(6414)
$ echo > file
$ jobs
[2]- Done exit < file
_
名前付きパイプからstdin
をリダイレクトすると、名前付きパイプから入力を受け取るまでサブシェルがブロックされるため、賢い方法です。後で、echo
の出力を(引数なしで)リダイレクトすると、名前付きパイプに改行文字が書き込まれ、サブシェルプロセスのブロックが解除され、サブシェルプロセスがexit
組み込みコマンドを実行します。
同様に、sleep
コマンドの場合:
_$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
└─pstree(6603)
_
ここでは、コマンドをバックグラウンドで実行するために作成されたサブシェルのPIDが_6600
_であることがわかります。次に、パイプに改行文字を書き込んでプロセスのブロックを解除します。
_$ echo > named_pipe
_
次に、サブシェルはexec
sを実行して、sleep
コマンドを実行します。
_$ pstree -p 26793
bash(26793)─┬─pstree(6607)
└─sleep(6600)
_
exec()
呼び出しの後、子プロセス(_6600
_)がsleep
プログラムを実行していることがわかります。