Bashスクリプトで壊れたパイプのプロセスを終了する

Bashスクリプトで壊れたパイプのプロセスを終了する

コードを考えてみましょう:

printf '%s\n' 1 2 3 4 5 | head -n 2

出力は次のとおりです。

1
2

私が理解したのは、head最初の2行を読んだ後にプロセスがパイプを損傷すると、プロセスが破損したprintfパイプを捕捉して正常に終了することです。

私のスクリプトの1つは、破損したパイプで正常に終了せず、代わりに設定されたタスクが完了するか、他の理由で終了するまで実行され続けるPythonアプリケーションを使用します。標準出力に書き込もうとし、失敗するたびに文句を言いますbroken pipe

これ記事Pythonで壊れたパイプを処理する方法を説明してください。

アプリケーション開発者に壊れたパイプ処理を実装することをお勧めします。何らかの理由で、私は彼らがそうであると疑います。おそらくアプリケーションをフォークすることができますが、かなり複雑なので、したくありません。

私が残した唯一のオプションは、シェルがパイプに書き込もうとしたときにプロセスを終了させる方法を見つけることです。可能ですか?では、どうすればよいですか?

答え1

write()はい、python3にはSIGPIPEを無視し、EPIPEが失敗したときに例外を発生させる迷惑な動作があります。壊れたパイプが寿命と正常な動作の一部であるにもかかわらず。

出力を渡し、SIGTERMを使用してpython3を終了するラッパーを使用すると、実際にこの問題を解決できます(たとえば、出力がパイプを壊した場合など)。

bashしかし、これは私がこの種の作業に使用するシェルではありません。

あなたが使用できるperl

perl -e '
  $pid = open CMD, "-|", @ARGV;
  $SIG{PIPE} = "IGNORE";
  while (sysread CMD, $buf, 8192) {
    if (!syswrite STDOUT, $buf) {
      kill "TERM", $pid;
      last;
    }
  }
  close CMD;
  exit($? & 127 ? ($? & 127) | 128 : $? >> 8)' -- your-python-program

シェルが必須の場合、zshはより良い選択になります。

zsh -c '
  zmodload zsh/system
  coproc {"$0" "$@" <&3 3<&-} 3<&0
  trap "" PIPE
  while sysread -s 8192 buf <&p; do
    syswrite -- $buf || {
      kill $! 2> /dev/null
      break
    }
  done
  wait $!' your-python-program

例:

$ python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
Traceback (most recent call last):
  File "<string>", line 1, in <module>
BrokenPipeError: [Errno 32] Broken pipe
$ zsh -c that-code python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
$ echo $pipestatus
143 0
$ kill -l 143
TERM

今では、python3の迷惑を解決するために、プロセスが追加のパイプを介して出力をプッシュするのに時間を費やすことは過度に見えます。

python3別のアプローチは、最初からこれらの信号が無視されるのを防ぐことです。

$ strace -qqqZ -e signal=none -e rt_sigaction -e inject=rt_sigaction:retval=0 python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo

すべてのPython3システムコール呼び出しを効果的にstrace短絡し、rt_sigaction()インストールを防ぎます。どの信号ハンドラまたは変更信号処理(すべての信号)。

したがって、python3スクリプトを停止したときに表示される迷惑なメッセージも削除されますが^C、Pythonスクリプトが終了したときにクリーンアップするためにいくつかのシグナルハンドラをインストールすると危険です。

SIGPIPEシグナルの呼び出しに対してのみこれを実行する方が良いですが、私が知っている限り、straceはこれを実行できません。ただし、次のような$LD_PRELOADいくつかのトリックを使用してこれを達成できます。

$ cat leave-sigpipe-alone.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int sigaction(int signum, const struct sigaction * restrict act, struct sigaction * restrict oldact)
{
  static int (*orig_sigaction)(int, const struct sigaction * restrict, struct sigaction * restrict) = 0;

  if (!orig_sigaction)
    orig_sigaction = (int (*)(int, const struct sigaction * restrict, struct sigaction * restrict)) dlsym (RTLD_NEXT, "sigaction");

  if (signum == SIGPIPE) return 0;
  return orig_sigaction(signum, act, oldact);
}
$ gcc -fPIC -shared -o leave-sigpipe-alone.so leave-sigpipe-alone.c -ldl
$ LD_PRELOAD=$PWD/leave-sigpipe-alone.so python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
$ echo $pipestatus
141 0
$ kill -l 141
PIPE

python3正常に動作する実行ファイルと同様に、SIGPIPE によって自動的に終了します。

straceこの-fオプションがあるかどうかにかかわらず、sigaction()呼び出しは親プロセス(実行プロセス)によってのみブロックされますpython3。個々のコマンドを実行しても引き続きそれを傍受しますが、サブコマンド内ではそれを行いません(該当する-fオプションを渡さない限り)。このLD_PRELOADトリックは、個々のコマンドを実行しても(そのコマンドが動的にリンクされている場合)、すべての子プロセスに影響します。

python3プログラミング言語(ほとんどの人が使用する言語)のcpythonインタプリタで使用するためのよりきれいなアプローチは、カスタムからSIGPIPEの基本的なシグナリング設定を復元することですsitecustomize.py

$ cat ~/lib/python3-leave-sigpipe-alone/sitecustomize.py
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL)
$ PYTHONPATH=~/lib/python3-leave-sigpipe-alone python3 -uc 'import time; print("foo"); time.sleep(0.2); print("bar")' | head -n1
foo
$ echo $pipestatus
141 0

ここでは、システムディレクトリを変更する代わりにsitecusomize.py専用ディレクトリのディレクトリを変更するので、破損したPYTHONPATHパイプに書き込もうとしたときにSIGPIPEによって終了されることを望むpython3スクリプトに対してのみ、変数をそのディレクトリに設定できます。

それは適用されないようですpypy3

関連情報