プロセスを開始するためにプログラムを使用しています。プログラムが終了すると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バイトを返し、それ以上読み取ることができないことを示す次の終了を決定します。cat
cat
cat
存在する
echo foo | sleep 1
sleep
1秒経過後にのみ終了します。閉じたパイプである標準入力はそれとは何の関係もなく、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 2
1秒後、プロセスは終了します。
一般的にこれを行うのは少し愚かなことです。これは、入力の終わりを処理する時間が経過する前にコマンドを終了できることを意味します。たとえば、
echo test | run_under_watch cat
cat
場合によっては、出力(または読み取り)時間になる前に終了することがあります"test\n"
。この問題を解決する方法はありません。観察者コマンドが入力を処理するのにかかる時間を知る方法はありません。私たちができることは、kill "TERM"
命令がパイプに残っているものを読み、必要な作業を実行するのに十分であることを望む前に猶予期間を与えることです。