- PID, PPID
- 関数の場合
- サブシェルとして実行された関数内でのほんとのPIDを取得する方法
- PIDが初期化されてるプロセスでの
$SHELL...
について - Bash Version 4の場合
- Zshの場合
- まとめ
PID, PPID
通常、スクリプトでもコマンドラインでも、自分のプロセスIDは$$
に入っています。
(以下、特記がないものはMac OS X 10.9でBash 3.2.51でやっています。)
また、$PPID
には自分の親プロセスのIDが入っています。
なので、
1 2 |
|
こんなスクリプトを作って実行すると
$ echo PPID=$PPID, PID=$$
PPID=26005, PID=26051
$ ./ppid.sh
PPID=26051, PID=34768
こんな感じで、スクリプト実行でサブシェルが立ち上がるので、 スクリプト内においては親プロセスがコマンドラインでの プロセスIDなってることが分かります。
関数の場合
上のスクリプトを関数にして
$ get_ids () { echo PPID=$PPID, PID=$$; }
こんな感じで定義すると、
$ echo PPID=$PPID, PID=$$
PPID=26005, PID=26051
$ get_ids
PPID=26005, PID=26051
と今度は同じ値を返します。関数を呼ぶ場合は 関数の中身も同じプロセスとして実行されてる、ということ。
ここで、上のコマンドをパイプでも$()
でもいいので
サブシェルとして実行してみてもスクリプトを実行した時と同じく
新たなPIDが割り振られてる気がするんですが
$ echo test| get_ids
PPID=26005, PID=26051
$ echo "$(get_ids)"
PPID=26005, PID=26051
となり、上と同じ値を示します。
実際に同じかどうか、psで見てみます。
$ show_ps () { ps -a -o ppid,pid,pgid,tty,comm; echo;}
$ show_ps
PPID PID PGID TTY COMM
...
26005 26051 26051 ttys003 /bin/bash
26051 78054 78054 ttys003 ps
...
$ echo "$(show_ps)"
PPID PID PGID TTY COMM
...
26005 26051 26051 ttys003 /bin/bash
26051 79211 26051 ttys003 /bin/bash
79211 79212 26051 ttys003 ps
...
こんな感じで$()
で実行した時は1つbashのプロセスが生まれて、その下で
psが実行されています。(以下psの結果はここで試してた時に使っていた
関係あるttys003
以外のttyのプロセスは削除してかいてあります)
パイプや$()
として起動されたプロセスはBashとしての初期化が行われないので、
PID等のシェル変数が再定義されず親の物をそのまま引き継いでしまう様です。
サブシェルとして実行された関数内でのほんとのPIDを取得する方法
直接$$
の変数が使えないわけですが、
この関数の中でシェルを1つ立ち上げてその親を見てあげると
結果的に関数自体のPIDを得ることが出来ます。
$ get_pid () { $SHELL -c 'echo $PPID';}
$ get_pid
26051
$ echo "$(show_ps;echo pid in func;get_pid;)"
PPID PID PGID TTY COMM
26005 26051 26051 ttys003 /bin/bash
26051 96877 26051 ttys003 /bin/bash
96877 96878 26051 ttys003 ps
pid in func
96877
こんな感じで最後に96877
と、確かに2番めのbashのPIDが取得出来ています。
ただし、注意として、PIDを得たいときに、
$ echo "$( pid=$(get_pid);echo PID=$pid)"
などとしてしまうとpid
はさらに下のプロセスに行ってしまうので
上手く行きません。
ので、使いたい時はファイルに一時書き出しして、みたいにするしかないでしょうか? (結局うまい方法が思い浮かばなかった。。。)
PIDが初期化されてるプロセスでの$SHELL...
について
$ get_pid () { $SHELL -c 'echo $PPID';}
という関数なのですが、これを関数として定義すると、 コマンドラインから実行してもサブシェル内で実行しても 1つ下でシェルを起動してその中下で親プロセスを見るので 上に書いた様に新しく起動したプロセスIDになります。
ただ、これを関数でなく、直接コマンドを打つと
$ echo $$
26051
$ $SHELL -c 'echo $PPID'
26051
$ echo "$($SHELL -c 'echo $PPID')"
26051
と上と違ってサブシェルが立ち上がって無い様に見えます。 これを
$ ($SHELL -c 'echo $PPID')
26051
の様に()
だけにして実行してみても一緒。
psも単独でコマンドだけで見てみると
$ echo "$(ps -a -o ppid,pid,pgid,tty,comm)"
PPID PID PGID TTY COMM
26051 22526 26051 ttys003 ps
26005 26051 26051 ttys003 /bin/bash
となって、実際ps
コマンドが現在のコマンドラインプロセスの直下に動いています。
ここに、なんでもいいんですが前後どちらかにコマンドを加えると
$ echo "$(ps -a -o ppid,pid,pgid,tty,comm;echo)"
PPID PID PGID TTY COMM
26051 24146 26051 ttys003 /bin/bash
24146 24147 26051 ttys003 ps
26005 26051 26051 ttys003 /bin/bash
というふうに、ps
の時点でサブシェルが出来ててps
コマンドもその下に出来ます。
他にも
$ echo "$(sleep 10)"
として他から覗いて見ると、bashのプロセスはこのttyで一つだけで
$ echo "$(sleep 10;echo)"
とするともうひとつbashのプロセスが出てきてその下にsleep
が動いていました。
一方、サブシェル下で同じようなことをしてみると
$ echo "$(echo "$(ps -a -o ppid,pid,pgid,tty,comm)")"
PPID PID PGID TTY COMM
26005 26051 26051 ttys003 /bin/bash
26051 28170 26051 ttys003 /bin/bash
28170 28171 26051 ttys003 /bin/bash
28171 28172 26051 ttys003 ps
$ echo "$(echo "$(ps -a -o ppid,pid,pgid,tty,comm;echo)")"
PPID PID PGID TTY COMM
26005 26051 26051 ttys003 /bin/bash
26051 31325 26051 ttys003 /bin/bash
31325 31326 26051 ttys003 /bin/bash
31326 31327 26051 ttys003 ps
と言った感じで単独コマンドでも最初からサブシェル下にさらにサブシェルを作ってそこの下で 実行していることが分かります。
どうも、関数にしたり、複数コマンドが呼ばれる場合には$()
1
の中にサブシェルがきちんと作られますが、
中が単独コマンドだとサブシェルを立ち上げずに現在のプロセスの直下で
コマンドを実行する様です。
この仕様についてman bash
を読んでみてもいまいち分からなかったんですが、
ちょっと気持ち悪い。。。
(きっと何か意味があると思うのですが。。。余計なプロセスを作りたくないだけ?)。
ちなみに、下に挙げてるBash Version 4やZsh (5.0.2)でも同じような 結果になりました。
Bash Version 4の場合
実はBash 4以降では$BASHPID
と言うシェル変数が加えられていて、
この変数はサブシェルにおいてもきちんと初期化?されてそのPIDを正しく示します。
ので、Bash 4以降では上みたいなget_pid
なんかせずにecho $BASHPID
だけでOK。
$BASHPID
と$$
の値は上の様なサブシェルの場合には違うので
ちょっと注意が必要です。
このBASHPIDも上の場合に当てはめるとまたちょっと特殊?で、
$ echo "$(echo $BASHPID)"
とするとコマンドラインでのBASHPID
(=$$
)と違う値を示すので、
この場合は何故か単独コマンドでもいきなりサブシェルを立ち上げてる様子。
さらに不可解。。。(BASHPIDという変数その物が何かトリガーかけてる?)
Zshの場合
Zshの場合、差し当たり探してみた感じではBASHPIDの様な変数はありません。
上のget_pid
みたいな関数はBash同様に使えるので、
Bash 3.Xと同じ様なことは出来ます。
まとめ
ちょっと関数の中がどこで動いてるのか調べようと思ったら
結構面倒なことになってしまったんですが、
単純にデバッグ用とかで表示させるだけならget_pid
を使って簡単に出来そうです。
ただし、ここで関数にしてるのが重要で、関数にしないと サブシェルみたく呼んだ時と通常の個所で呼んだ場合で結果が変わってしまいます。
更にこの値を直接使おうと思うと結構面倒で、下手に渡そうとすると さらにサブシェルを立ち上げてしまうので、出力を一時ファイルとかに 出して読み込み直すのが一番手っ取り早いと思うのですが、 もっとスマートな方法がありそうなものですが。
まあ、Bash 4.x系ではBASHPIDという便利な変数があるので それ使えばいいじゃん、という話にもなったりするわけですが。
いずれにしろ、何か色々な不思議な仕様が見えた 2話でした。