stdinが閉じてもプロセスは閉じません。

stdinが閉じてもプロセスは閉じません。

プロセスを開始するためにプログラムを使用しています。プログラムが終了するとstdinが失われ、プロセスも終了したいと思います。

私はプログラムを終了し、proc/pid/fdに行ってプロセスを見て、そのプログラムのstdinがまだ/dev/pts/2に接続されていることを発見しました。

この場合、プロセスが終了しないのはなぜですか?より良い点は、標準入力パイプが閉じられたときにプログラムを閉じるようにするために使用できるラッパーまたは技術がありますか?

答え1

stdinファイル記述子です0。プロセスのファイル記述子を閉じることは、プロセス自体によってのみアクティブに実行できます。プロセスが終了することを決定すると、stdinも終了します。

これで、プロセスのstdinがパイプの読み取り端である場合、パイプのもう一方の端は1つ以上の他のプロセスによって開くことができます。もう一方の端にあるすべてのファイル記述子が閉じられると、パイプから読み取るとパイプにまだ残っているデータが読み込まれますが、最終的に(より多くのデータを待つのではなく)何も返しません。これはファイルが完了したことを意味します。

cat、、cut...などのwc標準入力から読み取るアプリケーションは、通常、これが発生すると終了します。なぜなら、そのアプリケーションの役割は、入力がなくなったときに最後まで入力を処理することです。

入力の終わりにアプリケーションを終了させる魔法のメカニズムはありません。

存在する:

echo foo | cat

echoを書く"foo\n"と終了し、パイプの書き込みの終わりを閉じ、もう一方の端はread()0バイトを返し、それ以上読み取ることができないことを示す次の終了を決定します。catcatcat

存在する

echo foo | sleep 1

sleep1秒経過後にのみ終了します。閉じたパイプである標準入力はそれとは何の関係もなく、sleep標準入力からは読みません。

パイプ(またはソケット)の書き込み面が異なります。

write()読み取り側のすべてのfdが閉じられたときに書き込み側で開かれたfdに書き込もうとすると、SIGPIPEはプロセスに送信され、プロセスは終了します(信号を無視しない限り、この場合)EPIPE

しかし、これは書き込みをしようとしたときにのみ発生します。

たとえば、

sleep 1 | true

trueすぐに終了し、読取り側が直ちに閉じてもsleep標準出力に書き込もうとしないため終了しません。


これで、/proc/fd/pid/n出力ではaboutが赤で表示されますls -l --color(例:質問の最初のバージョン)、これは、対応lsするシンボリックリンクの結果に対して実行すると、リンク先の種類を決定するためです。lstat()readlink()

パイプ、ソケット、またはその他の名前空間のファイルまたは削除されたファイルで開かれたファイル記述子の場合、結果はreadlinkファイルシステムの実際のパスではないため、2番目にlstat()完了した操作はls失敗し、lsそれを壊れたシンボリックリンクと見なします。壊れたシンボリックリンクは赤で表示されます。パイプのもう一方の端が閉じられているかどうかにかかわらず、パイプの両端のfdからその結果を得ることができます。ls --color=always -l /proc/self/fd | catたとえば、試してみてください。

fdが破損したパイプを指していることを確認するには、Linuxでlsofこの-Eオプションを試すことができます。

$ exec 3> >(:) 4> >(sleep 999)
$ lsof -ad3-4 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r

fd 3の場合、lsofはパイプの読取り側で他のプロセスを見つけることができません。ただし、次のような結果が出ることがあります。

$ exec 5<&3
$ lsof -ad3-5 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe 13155,zsh,5w
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r
zsh     13155 stephane    5w  FIFO   0,10      0t0 5322414 pipe 392,sleep,3w 13155,zsh,3w

fds 3と5は読み取り側にfdがないため、まだ破損したパイプです(lsofにはsleep壊れたパイプにfd 3が開いているという事実がどこにも反映されないという点でバグがあるようです)。


標準入力で開いているパイプが最後のライターを失うとすぐに(破損した)プロセスを終了するには、次のようにします。

run_under_watch() {
  perl -MIO::Poll -e '
     if ($pid = fork) {
       $SIG{CHLD} = sub {
         wait;
         exit($? & 127 ? ($? & 127) + 128 : $? >> 8);
       };
       $p = IO::Poll->new; $p->mask(STDIN, POLLERR); $p->poll;
       kill "TERM", $pid;
       sleep 1;
       kill "KILL", $pid;
       exit(1);
     } else {
       exec @ARGV
     }' "$@"
 }

エラー条件に対してstdinを監視し(Linuxでは、パイプにデータがあってもライターが残っていない限り発生するように見えます)、発生したらすぐにサブコマンドを終了します。たとえば、

 sleep 1 | run_under_watch sleep 2

sleep 21秒後、プロセスは終了します。

一般的にこれを行うのは少し愚かなことです。これは、入力の終わりを処理する時間が経過する前にコマンドを終了できることを意味します。たとえば、

 echo test | run_under_watch cat

cat場合によっては、出力(または読み取り)時間になる前に終了することがあります"test\n"。この問題を解決する方法はありません。観察者コマンドが入力を処理するのにかかる時間を知る方法はありません。私たちができることは、kill "TERM"命令がパイプに残っているものを読み、必要な作業を実行するのに十分であることを望む前に猶予期間を与えることです。

関連情報