いきなり問題
さて問題です。次のコードを実行した時、どうなるでしょうか?
$ mkfifo "HOGEPIPE"
$ { cat < "HOGEPIPE" >/dev/null; } &
$ killall cat
-
cat
コマンドプロセスが終了され、サブシェルとなっている括弧プロセスが終了する。(その結果、[1]+ Done { cat < "HOGEPIPE"; }
などのメッセージが出る) -
cat
コマンドプロセスが見つからず、killall
コマンドがエラーを返す。 -
名前付きパイプ
"HOGEPIPE"
が使えなくなり、一旦削除して作り直すしかなくなる。
正解
1番、と見せかけて、実は2番です。あたりましたか?
理由
2番の「killall
コマンドがエラーを返す」とは一体どういうことでしょう?cat
コマンドが名前付きパイプからデータが流れてくるのを今か今かと待ち構えているのではないのでしょうか。
じつはこの時、cat
コマンドはまだ起動していないのです。いくら待とうがダメです。後で"HOGEPIPE"
にデータを流し込むその時、はじめて起動するのです。
名前付きパイプが空のうちは、シェルはリダイレクション先のプロセスを起動しない
では名前付きパイプにまだデータが流し込まれていない時、
{ cat < "HOGEPIPE" >/dev/null; } &
はどういう状態なのでしょうか?
上記のサブシェル{ cat < "HOGEPIPE" >/dev/null; }
自体は立ち上がっています。しかしそのサブシェルが、catコマンドをまだ起動していないのです。リダイレクションなので、サブシェルがまず名前付きパイプ"HOGEPIPE"
を開こうとしますが、その結果まだデータが到来していないのでサブシェル自身がブロック状態になってしまっているのです。
その様子を観察してみましょう。冒頭の「問題」のコマンドを打ち込んだ後に、さらに次のコマンドを打ち込んでみてください。今、この議論に関係するプロセスのみが表示されます。
$ ps -Ao pid,ppid,command | egrep $$'|'cat | egrep -v grep'|'ps
27488 27487 -bash ← (親)今コマンド打ち込んでいるシェルのプロセス
27673 27488 -bash ← (子)catを呼びだすサブシェルのプロセス
このように、コマンドを打ちこんでいるシェルのプロセスと、{ cat < "HOGEPIPE" >/dev/null; }
のサブシェルの2つしか居ません。
では、パイプにデータを流し始めてみたらどうなるか見てみましょう。まずは次のコマンドを打ちこみ、データを流し始めてみてください。
$ yes > "HOGEPIPE" &
そして、もう一度関係するプロセスを列挙してみましょう。
$ ps -Ao pid,ppid,command | egrep $$'|'cat | egrep -v grep'|'ps
27488 27487 -bash ← (親)今コマンド打ち込んでいるシェルのプロセス
27673 27488 -bash ← (子)catを呼びだすサブシェルのプロセス
27674 27673 cat ← (孫)上記のサブシェルから呼びだされたcat
27686 27488 yes ← (子)名前付きパイプ経由でcatにデータを送っているyes
$ killall yes # 余計な負荷を掛けぬよう、確認後、yesはすぐに止めましょう
というわけでようやくcat
コマンドが起動しましたね。
起動しないのはリダイレクションで受け取る場合だけ
ただし、次のようにして入力リダイレクション表記を使わない場合はちゃんとkillできます。
$ mkfifo "HOGEPIPE"
$ { cat "HOGEPIPE" >/dev/null; } &
$ killall cat
理由は、最初に名前付きパイプ"HOGEPIPE"
を開くのがサブシェルでななく、cat
コマンドに任せられるからです。