こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

()を使ったサブシェルの起動について

()を使ってサブシェル(?)を起動する下記スクリプトを実行してみました。


#!/bin/bash
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd)"
(
cd /tmp
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd)"
)
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd)"


[実行結果例(Linux Mint 17 Mate)]
/bin/bash:2: 31782, 31634: /home/taro/tmp
/bin/bash:2: 31782, 31634: /tmp
/bin/bash:2: 31782, 31634: /home/taro/tmp


てっきりサブシェルの出力結果は次のようになるとばかり思っていました。
* SHLVL環境変数の値が起動前の値よりも 1 大きくなる。
* 呼び出し元から新たに生成されたプロセスでサブシェルが実行される。

しかし、起動されたサブシェルのプロセスIDは呼び出し元と同じです。
() の中の処理はどのように実行されているのでしょうか?

投稿日時 - 2014-10-01 12:56:51

QNo.8774973

暇なときに回答ください

質問者が選んだベストアンサー

>新たに生成されたプロセスで () で囲まれた部分をどのように実行させているのかに興味を持ちました。

()によるサブシェルの場合、子プロセスが作られても「環境変数は親の物を引き継いでしまう」ことがあるようで、$$や$PID、$PPIDの値は、アテにならないようです。

また、$()や()の中が関数だったり複数のコマンドだと、きちんと子プロセスが生成されますが、単一のコマンドの場合は子プロセスを生成せずに自身のプロセスで処理しているようです。

詳しくは
http://rcmdnk.github.io/blog/2014/01/20/computer-bash/
を読んで下さい。

これを読んだら、更に謎が深まるような気もしますが…。

投稿日時 - 2014-10-02 09:56:50

お礼

chie65535さん
再びの回答ありがとうございます。

教えて頂いたリンク先を読んでみました。
確かに更に謎が深まりました :)

でも、最初の回答でchie65535さんが仰りたかったことが多少は理解できた気がしています。
ただ、bashがどのような状況で新たにプロセスを生成したり、bashを起動したりするのか正確に理解するのは、自分には難しいですし、それに掛けるコストに見合う見返りも現時点では大きくはないので、bashが良きに計らってくれていると思うようになりました :)
単純な話ではないということは分かりましたし、デバッグ手法やBASHPIDの存在も知ることができたので有益でした。

お付き合い頂きまして本当にありがとうございました。

投稿日時 - 2014-10-09 01:36:35

このQ&Aは役に立ちましたか?

3人が「このQ&Aが役に立った」と投票しています

回答(3)

ANo.3

別のコマンドを起動するとき、内部的には fork(2) (Linuxではcloneシステムコ
ール)してサブプロセスを生成した後に、execve(2)を実行しています。

()を使ってサブシェルを作った場合、fork(2)は実行されるものの、execve(2)は
実行されません。その結果、子プロセスは生成されるものの (psで存在を確認で
きます) 、親シェルで設定した情報がそのまま残ります。定義したシェル変数は
export していないものも含めて () の中に引き継がれます。

下記を実行してみてください。シェル変数TESTはexportしていない (環境変数で
はない) のに、 () の中のサブシェルまで引き継がれていることがわかります。
でも、bash -c とした場合は、execveが実行されるため、シェル変数の中身は引
き継がれていません。また、bash -c とした場合には、${SHLVL}がカウントア
ップされ、$$ も 更新されていることが確認できるかと思います。

==
#!/bin/bash
TEST=abc
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd): ${TEST}"
(
cd /tmp
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd): ${TEST}"
(
cd /var/tmp
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd): ${TEST}"
bash -c 'cd /var; echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd)": ${TEST}'
)
)
echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd): ${TEST}"

投稿日時 - 2014-10-03 11:06:22

お礼

entreeさん
回答ありがとうございます。

無条件でプロセスを生成し、()の中をexecするものとばかり思っていましたが、そんな単純な話ではないのですね。
bashの正確な処理内容を理解するのは難しいそうなので、現時点では「bashが良きに計らってくれる」と思うことにします :)

ありがとうございました。

投稿日時 - 2014-10-09 01:38:28

ANo.1

>echo "${SHELL}:${SHLVL}: $$, ${PPID}: $(pwd)"

()でサブシェルを呼ぶように書いても、環境変数の展開は「サブシェルを呼び出す前に終わってる」のではありませんか?

「$(pwd)」は、実行したコマンドの標準出力を結果として取り込むので、サブシェルの実行時まで実行されませんが、その他の部分は、bashがスクリプトファイルを読み込んだ時点で、展開が終わっちゃってます。

つまり、bashがスクリプトを読み込んだ瞬間に

#!/bin/bash
echo "/bin/bash:2: 31782, 31634: $(pwd)"
(
cd /tmp
echo "/bin/bash:2: 31782, 31634: $(pwd)"
)
echo "/bin/bash:2: 31782, 31634: $(pwd)"

という状態に書き換えてしまい、この状態でスクリプトが実行されるので

/bin/bash:2: 31782, 31634: /home/taro/tmp
/bin/bash:2: 31782, 31634: /tmp
/bin/bash:2: 31782, 31634: /home/taro/tmp

と表示されるのが当然だと思います。

テストするなら「サブシェルが起動される寸前まで、環境変数が展開されないように工夫」しなければなりません。

投稿日時 - 2014-10-01 14:04:04

お礼

chie65535さん
回答ありがとうございます。

> 環境変数の展開は「サブシェルを呼び出す前に終わってる」
なるほど、そういうことなんですね、、。

pid, ppid, 環境変数SHELL, SHLVLを取得するバイナリを作成し、サブシェルから実行してみたところ、呼び出し元が作成したプロセスでサブシェルの処理が実行されていることを確認できました。

ただ、SHELLの値は/bin/bash、SHLVLの値は呼び出し元と同じでした。サブシェルという名前からSHLVLの値が1つ多くなるのかと思っていたのですが、新たにbashが起動されているというわけではないようですね。
新たに生成されたプロセスで () で囲まれた部分をどのように実行させているのかに興味を持ちました。

投稿日時 - 2014-10-01 18:24:33

あなたにオススメの質問