103

非常に単純な Python 3 スクリプトがあります。

f1 = open('a.txt', 'r')
print(f1.readlines())
f2 = open('b.txt', 'r')
print(f2.readlines())
f3 = open('c.txt', 'r')
print(f3.readlines())
f4 = open('d.txt', 'r')
print(f4.readlines())
f1.close()
f2.close()
f3.close()
f4.close()

しかし、それは常に言う:

IOError: [Errno 32] Broken pipe

これを修正するための複雑な方法をインターネットですべて見ましたが、このコードを直接コピーしたので、Python の SIGPIPE ではなく、コードに問題があると思います。

出力をリダイレクトしているので、上記のスクリプトの名前が「open.py」の場合、実行するコマンドは次のようになります。

open.py | othercommand
4

10 に答える 10

123

問題は、SIGPIPE 処理によるものです。次のコードを使用して、この問題を解決できます。

from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL) 

更新:コメントで指摘されているように、python docsには既に適切な回答があります。

このソリューションの背景については、こちらを参照してください。ここでより良い答え。

于 2013-05-31T20:05:58.950 に答える
108

多くの役立つ回答からの情報を、いくつかの追加情報とともにまとめます。

  • 標準の Unix シグナルは、パイプからSIGPIPE読み取るプロセスがなくなったときに、パイプに書き込むプロセスに送信されます。

    • これは必ずしもエラー状態ではありません。などの一部の Unix ユーティリティは、十分なデータを受信すると、パイプからの読み取りを途中で停止するようhead に設計されています。
    • したがって、このエラーを引き起こす簡単な方法はhead[1]にパイプすることです。例えば:
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • デフォルトでは、つまり、書き込みプロセスが明示的にトラップしない場合、書き込みプロセスは単純に終了し、その終了コードは に設定されます SIGPIPE141128。これは、 (一般にシグナルによる終了を通知するため) + 13(SIGPIPEの特定のシグナル番号)として計算されます。 .

  • ただし、設計上、Python自体がそれをトラップSIGPIPEし、値を持つ Python (Python 3) / (Python 2)インスタンスに変換BrokenPipeErrorIOErrorerrnoしますerrno.EPIPE

    • 注: Windowsで Unix エミュレーション環境を使用している場合、エラーの表示が異なる場合があります。この回答を参照してください。
  • Pythonスクリプトが例外をキャッチしない場合、 Pythonはエラー メッセージBrokenPipeError: [Errno 32] Broken pipe( Python 3、おそらく2 回Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>間に挟まれている) / IOError: [Errno 32] Broken pipe( Python 2 ) を出力し、スクリプトを終了コード1[2]で終了します- これがヨハネス (OP ) 鋸。

Windowsに関する考慮事項 (SIGPIPEは Unix のみのシグナルです)

  • スクリプトを Windows でも直接実行する必要がある場合は、この回答SIGPIPEに示されているように、を参照するコードを条件付きでバイパスする必要がある場合があります。

  • スクリプトがWindowsのUnix サブシステムSIGPIPEで実行される場合、信号はUnix とは異なる方法で表示される場合があります -この回答を参照してください。


この問題を解決するには、次の 2 つの方法があります。

通常、この例外を黙らせることはお勧めできません。ネットワーク ソケットの受信側が予期せず閉じるなど、スクリプトの目的によっては、重大なエラー状態を示す可能性があるためです。

  • ただし、スクリプトがコマンド ライン ユーティリティである場合は、静かな終了が許容されるだけでなく、標準ユーティリティとうまく連携するために好ましいhead場合があります。たとえば、プラットフォームのデフォルト シグナル ハンドラをインストールするために次のように静かに中止できます。 (上記のように動作します)、akhanの回答にも示されているように(Python 3と2の両方で動作します):signal.signal()
# ONLY SUITABLE FOR COMMAND-LINE UTILITIES

# Install the default signal handler.
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE, SIG_DFL)

# Start printing many lines.
# If this gets interrupted with SIGPIPE, 
# the script aborts quietly, and the process exit code is set to
# 141 (128 + SIGPIPE)
for x in range(10000): print(x)
  • それ以外の場合、 SIGPIPE によってトリガーされた例外を自分で処理したい場合(Python 3 と 2 の両方で動作し、docsから適応されます):
import sys, os, errno

try:

  # Start printing many lines.
  for x in range(10000): print(x)

  # IMPORTANT: Flush stdout here, to ensure that the 
  # SIGPIPE-triggered exception can be caught.
  sys.stdout.flush()

except IOError as e: 
  # Note: Python 3 has the more specific BrokenPipeError,
  #       but this way the code works in Python 2 too.
  if e.errno != errno.EPIPE: raise e # Unrelated error, re-throw.

  # Python flushes standard streams on exit; redirect remaining output
  # to devnull to avoid another BrokenPipeError at shutdown
  devnull = os.open(os.devnull, os.O_WRONLY)
  os.dup2(devnull, sys.stdout.fileno())

  # ... perform other handling.
  # Note: You can't write to stdout here.
  #       (print() and sys.stdout.write won't work)
  #       However, sys.stderr.write() can be used.
  sys.stderr.write("SIGPIPE received, terminating.\n")

  # Finally, exit with an exit code of choice.
  sys.exit(141)

[1]bashデフォルトではheadの終了コードのみが表示されることに注意してください。これは後で0反映されます。Python の終了コードを表示するために$?使用します。echo ${PIPESTATUS[0]}

[2] 不思議なことに、macOS 10.15.7 (Catalina) で Python 3.9.2 (ただし 2.x ではない) を使用すると、 exit code が表示されます120が、ドキュメントには1と記載されており、これは Linux でも見られます。

于 2015-05-07T03:50:10.460 に答える
51

stdout私は問題を再現していませんが、おそらくこの方法で解決できます: (を使用するのではなく、行ごとに書き込むprint)

import sys
with open('a.txt', 'r') as f1:
    for line in f1:
        sys.stdout.write(line)

壊れたパイプをキャッチできますか?これにより、パイプが閉じられるまで、ファイルが 1stdout行ずつ書き込まれます。

import sys, errno
try:
    with open('a.txt', 'r') as f1:
        for line in f1:
            sys.stdout.write(line)
except IOError as e:
    if e.errno == errno.EPIPE:
        # Handle error

othercommandまた、パイプが大きくなりすぎる前にパイプから読み取っていることを確認する必要があります- https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

于 2013-01-08T04:33:42.813 に答える
31

もう一方の端で閉じられているパイプに書き込もうとすると、「Broken Pipe」エラーが発生します。あなたが示したコードにはパイプが直接含まれていないため、Python インタープリターの標準出力を別の場所にリダイレクトするために、Python の外部で何かを行っていると思われます。これは、次のようなスクリプトを実行している場合に発生する可能性があります。

python foo.py | someothercommand

あなたが抱えている問題はsomeothercommand、標準入力で利用可能なすべてのものを読み取らずに終了することです。これにより、ある時点で (経由でprint) 書き込みが失敗します。

Linux システムで次のコマンドを実行すると、エラーを再現できました。

python -c 'for i in range(1000): print i' | less

lessすべての入力 (1000 行) をスクロールせずにページャーを閉じると、PythonIOErrorは報告されたものと同じ状態で終了します。

于 2013-01-08T11:10:14.187 に答える
22

使用する方法が

signal(SIGPIPE, SIG_DFL) 

は確かに危険です(コメントで David Bennet が既に示唆しているようにmultiprocessing.Manager) 。長くてつらい話を短くするために、これが私がそれを修正した方法です:

IOErrorまず、 (Python 2) またはBrokenPipeError(Python 3)をキャッチする必要があります。プログラムによっては、その時点で早期に終了するか、例外を無視することができます。

from errno import EPIPE

try:
    broken_pipe_exception = BrokenPipeError
except NameError:  # Python 2
    broken_pipe_exception = IOError

try:
    YOUR CODE GOES HERE
except broken_pipe_exception as exc:
    if broken_pipe_exception == IOError:
        if exc.errno != EPIPE:
            raise

しかし、これでは十分ではありません。Python 3 では、次のようなメッセージが出力される場合があります。

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

残念ながら、そのメッセージを取り除くのは簡単ではありませんが、最終的に見つけたhttp://bugs.python.org/issue11380で、Robert Collins がこの回避策を提案し、メイン関数をラップできるデコレーターに変えました (はい、それはクレイジーです)インデント):

from functools import wraps
from sys import exit, stderr, stdout
from traceback import print_exc


def suppress_broken_pipe_msg(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except SystemExit:
            raise
        except:
            print_exc()
            exit(1)
        finally:
            try:
                stdout.flush()
            finally:
                try:
                    stdout.close()
                finally:
                    try:
                        stderr.flush()
                    finally:
                        stderr.close()
    return wrapper


@suppress_broken_pipe_msg
def main():
    YOUR CODE GOES HERE
于 2016-03-03T00:59:34.543 に答える
1

これは、スクリプトからの出力の読み取り側が途中で終了した場合にも発生する可能性があります

つまり、open.py | otherコマンド

otherCommand が終了し、open.py が stdout に書き込もうとした場合

私はこれを私に素敵にしてくれた悪いgawkスクリプトを持っていました。

于 2013-10-17T18:23:24.107 に答える
-2

クローズは、オープンの逆の順序で行う必要があります。

于 2015-01-28T09:56:05.397 に答える