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

このQ&Aのポイント
  • サブシェルの起動について疑問を抱いています。
  • サブシェルのプロセスIDが呼び出し元と同じである理由が分かりません。
  • サブシェルの()の中の処理がどのように実行されているのか知りたいです。
回答を見る
  • ベストアンサー

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

()を使ってサブシェル(?)を起動する下記スクリプトを実行してみました。 #!/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は呼び出し元と同じです。 () の中の処理はどのように実行されているのでしょうか?

noname#241088
noname#241088

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

  • ベストアンサー
  • chie65535
  • ベストアンサー率43% (8525/19382)
回答No.2

>新たに生成されたプロセスで () で囲まれた部分をどのように実行させているのかに興味を持ちました。 ()によるサブシェルの場合、子プロセスが作られても「環境変数は親の物を引き継いでしまう」ことがあるようで、$$や$PID、$PPIDの値は、アテにならないようです。 また、$()や()の中が関数だったり複数のコマンドだと、きちんと子プロセスが生成されますが、単一のコマンドの場合は子プロセスを生成せずに自身のプロセスで処理しているようです。 詳しくは http://rcmdnk.github.io/blog/2014/01/20/computer-bash/ を読んで下さい。 これを読んだら、更に謎が深まるような気もしますが…。

noname#241088
質問者

お礼

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

その他の回答 (2)

  • entree
  • ベストアンサー率55% (405/735)
回答No.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}"

noname#241088
質問者

お礼

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

  • chie65535
  • ベストアンサー率43% (8525/19382)
回答No.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 と表示されるのが当然だと思います。 テストするなら「サブシェルが起動される寸前まで、環境変数が展開されないように工夫」しなければなりません。

noname#241088
質問者

お礼

chie65535さん 回答ありがとうございます。 > 環境変数の展開は「サブシェルを呼び出す前に終わってる」 なるほど、そういうことなんですね、、。 pid, ppid, 環境変数SHELL, SHLVLを取得するバイナリを作成し、サブシェルから実行してみたところ、呼び出し元が作成したプロセスでサブシェルの処理が実行されていることを確認できました。 ただ、SHELLの値は/bin/bash、SHLVLの値は呼び出し元と同じでした。サブシェルという名前からSHLVLの値が1つ多くなるのかと思っていたのですが、新たにbashが起動されているというわけではないようですね。 新たに生成されたプロセスで () で囲まれた部分をどのように実行させているのかに興味を持ちました。

関連するQ&A

  • Linuxでの質問。(echoコマンドなど)

    Linuxでの質問。 1.echoコマンドで* $home=' home ' (ただし home はシェル変数homeの値)と表示したい。echoコマンドにどのようは引数を渡せばよいのでしょうか。 実行例 % echo 適切な引数 * $home='/home/gbu/myname' 答えは echo \* "\home="\'$home\' でよろしいですか。 echo \* '$home='\'$home\' と入力しても同じ結果が出るのですが、どっちでいいでしょうか。 2.次の2つのコマンドラインは実行結果が異なる。その違いが生じる理由は何でしょうか。 % cd ; (cd / ; pwd) ; pwd % cd ; cd / ; pwd ; pwd まったく初心者です。manコマンドを使って調べたのですが。。 man () って入力しても、何にも出なくて、ぜひ教えてください。お願いします。 3.演習課題にならって、 エイリアスが子プロセスのシェル(サブシェル)に引き継がれるかどうかを確かめる手順の例を示しなさい。っていう課題が出ました。どういうふうにやればいいでしょうか。教えてください。 演習課題 シェル変数と環境変数の違いの確認 % set foo=1 シェル変数fooに値を設定する。 % setenv FOO 1 環境変数FOOに値を設定する。 % echo $foo シェル変数fooの値を表示する。 % echo $FOO 環境変数FOOの値を表示する。 % tcsh 子プロセスとしてシェルを起動する。 (ここから、子プロセスのシェル上での操作) % echo $foo シェル変数fooの値の表示を試みる。(引き継がれていない。) % echo $FOO 環境変数FOOの値を表示する。(引き継がれている。) % set foo=2 シェル変数fooに値を設定する。 % setenv FOO 2 環境変数FOOに値を設定する。 % exit 子プロセスとしてシェルを起動したシェルを終了する。 (ここまで、子プロセスのシェル上での操作) % echo $foo シェル変数fooの値を表示する。(子プロセスでの変更は影響しない。) % echo $FOO 環境変数FOOの値を表示する。(子プロセスでの変更は影響しない。) エイリアスの意味を調べたのですが、「コマンドを別名で登録したもの」と解釈されていて、これはどういうことなんでしょうか。 この問題の意味は、課題にならって set alias=1 と設定して、次は setenv ALIAS 1 そして、課題のように、fooのとこをalias、FOOのとこをALIASと変えて、最後echo $ALIAS を調べればいいのですか。 学校の宿題ですが、やってもぜんぜん意味不明なので、助けてください。

  • 一行でdoループ

    debian wheezy で 一行でdoループをやろうとしたのですが、失敗します。 $ for i in {1..2} ; do ; pwd ; done bash: 予期しないトークン `;' 周辺に構文エラーがあります $ echo $SHELL /bin/bash $ echo $BASH_VERSION 4.2.37(1)-release なぜうまくいかないのでしょうか? 教えてください。 よろしくお願いします。

  • 変数$importfileに第1引数($1)のファイルPATHを表示したい

    下記のスクリプトを書きましたが ./aa.sh: line 6: =/tmp: command not found と言われるのはなぜですか? 変数$importfileに第1引数($1)のファイルPATHを表示したいです。 1 #/bin/bash 2 3 echo $# 4 if [ $# -eq 1 ] 5 then 6 $importfile=$1 7 echo "$importfile" 8 else 9 echo "argv file notfound" 10 exit; 11 fi 12 実行結果 ./aa.sh /tmp 1 ./aa.sh: line 6: =/tmp: command not found

  • Linux の shell プログラミングについて

    すみません、どなたがご存じでしたら教えてください。 Linux の shell プログラミングですが、 実行するコマンドをそのまま表示する良い方法が無いでしょうか? 現在は実行するコマンドをechoで書いています。 たとえば次のような感じです。 #!/bin/bash var1=/etc echo 'ls -l $var1 | wc -l' ls -l $var1 | wc -l 結果 ls -l $var1 | wc -l 205 でも(echoで)同じ内容を2行書きたくないので いろいろ調べて次のようにしてみました。 #!/bin/bash var1=/etc set -x ls -l $var1 | wc -l set +x 結果は + ls -l /etc + wc -l 205 + set +x 思ったようになりませんでした。 他に良い方法が何かありますでしょうか? よろしくお願いいたします。

  • 死なない(不思議な?)プロセスの振舞いについて

    プロセスの振舞いでよく分からない点がありますので、 この点に詳しい方、ぜひご教授下さい。 実験として、次のような無限ループをもつシェルスクリプトを作りました。 #!/bin/sh while : do echo "hogehoge" > tmp echo "Please stop me!" sleep 1 done このfile名を loop.sh として、実行属性を付けてから $ ./loop.sh & とバックグラウンドで実行してみます。 すると、1秒毎に、tmpにhogehogeと書き込まれ 標準出力にPlease stop me!と標準出力に表示されますが、 ここで、 exitコマンドでterminalを急に閉じてみます。 こうした場合、どうやら、このプロセスは何処かで生き続けるようなのです。 というのも、新たにterminalを起動し、上で出来た tmp をrmで消そうとしても、 すぐ新たなtmpが作られるからです。 しかも、単にlogoutしただけではこのプロセスは死なないようです。 (再起動するとさすがに止まります。) そこで質問ですが、 1) 上のプロセスが生き続けていることを確認する方法はありますか? 2) この生き続けたプロセスを再起動せずに殺す方法はありますか? 3) このようにシェルスクリプト実行中に急にexitをされた場合でも、 スクリプトが正常終了するように予めスクリプトを組むことは出来ますか? 以上3点についてご教授いただけたら幸です。 よろしくお願いします。

  • bashでの変数の引渡し方法

    bashを使用するのが初めてなので教えてください。 shellscriptの中で変数を設定するのに共通変数設定用の子Shellを実行して変数を設定したいと思っていますが、うまく動きません。どのように設定したらいいのでしょうか。cshのときはsetenvでよかったのですが。。 親shell(hoge.sh) -------------- #!/bin/sh ./setenv.sh #変数設定用子Shell echo $USER exit 0 -------------- 子Shell(setenv.sh) -------------- #!/bin/sh USER="hoge" exit 0 -------------- 実行すると以下のようになります。 $./hoge.sh USER: not found

  • シェルスクリプトからPHP実行

    シェルスクリプト(bash)内で、以下のようにPHPを呼んだ場合、 その次の処理がPHPの終了を待たずに実行されるようなのですが、 どうしてでしょうか? #!\bin\bash /usr/local/php4/bin/php /home/test.php echo "test!" 以上 なぜか test! という表示がPHPの処理中に出るように思えます。

  • シェルスクリプトのコマンド グループ化における変数の有効範囲について教

    シェルスクリプトのコマンド グループ化における変数の有効範囲について教えてください。 中括弧"{ }"を使うと親プロセス(=その実行中のプロセス)にて実行、 小括弧"( )"を使うとサブシェル(=子プロセス)にて実行されると認識しています。 また環境変数は子プロセスに引き継がれ、シェル変数は引き継がれないと認識しています。 以下のシェルスクリプト(Bシェル)でテストしたところ、続くような結果となりました。 ~スクリプト内容~ #!/bin/sh ##TEST1 VAR01=AAA { echo 中括弧内でVAR01は${VAR01}; } ( echo 小括弧内でVAR01は${VAR01}; ) ##TEST2 { VAR02=BBB; echo 中括弧内でVAR02は${VAR02}; } echo 外でVAR02は${VAR02} ##TEST3 ( VAR03=CCC; echo 小括弧内でVAR03は${VAR03}; ) echo 外でVAR03は${VAR03} ~実行結果~ 中括弧内でVAR01はAAA 小括弧内でVAR01はAAA 中括弧内でVAR02はBBB 外でVAR02はBBB 小括弧内でVAR03はCCC 外でVAR03は  VAR02、VAR03の結果は納得できます。 またVAR01が{ }に引き継がれているのも納得できました。 しかし、VAR01はシェル変数として作成した(exportによる環境変数化はしていない)にも関わらず、子プロセスである( )内処理にも引き継がれているのはなぜなのでしょうか? 

  • Linuxでの別プロセス起動について

    コマンド置換を使用した場合に、シェル自体が子プロセスとして立ち上がる場合とそうでない場合があります。 具体的には以下のような場合です。 【パターン1】 コマンド置換内の処理が1行の場合は子プロセスが発生しない abc=`pstree` echo $abc -sshd-+-sshd---bash---pstree (該当部分だけを切り出しています) 【パターン2】 コマンド置換内で処理が2行以上ある場合は子プロセスが発生する abc=`echo; pstree` echo $abc -sshd-+-sshd---bash---bash---pstree (該当部分だけを切り出しています) パイプを使用した場合には子プロセスとして処理が実行されることは認識しているのですが、コマンド置換の場合も子プロセスとして実行されるのが普通なのでしょうか? だとすれば、パターン1のように処理が1行だけの場合には子プロセスとして立ち上がらない理由は何でしょうか? このあたりに詳しい方、ご教示いただければ幸いです。

  • 配列の値を一行あけて出力

    配列の値を一行あけて出力したいのですが、うまくいきません。 ご教授お願いいたします。 <shell> #!/bin/bash ARRAY=("aaaa" "bbbb" "cccc") IFS=$'¥n'$'¥n' echo "${ARRAY[*]}" <現在の出力> aaaa bbbb cccc <期待する出力> aaaa bbbb cccc