IOError: [ Errno 32]管道传输时管道破裂: ‘ pro.py | other cmd’

我有一个非常简单的 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
247297 次浏览

I haven't reproduced the issue, but perhaps this method would solve it: (writing line by line to stdout rather than using print)

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

You could catch the broken pipe? This writes the file to stdout line by line until the pipe is closed.

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

You also need to make sure that othercommand is reading from the pipe before it gets too big - https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

A "Broken Pipe" error occurs when you try to write to a pipe that has been closed on the other end. Since the code you've shown doesn't involve any pipes directly, I suspect you're doing something outside of Python to redirect the standard output of the Python interpreter to somewhere else. This could happen if you're running a script like this:

python foo.py | someothercommand

The issue you have is that someothercommand is exiting without reading everything available on its standard input. This causes your write (via print) to fail at some point.

I was able to reproduce the error with the following command on a Linux system:

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

If I close the less pager without scrolling through all of its input (1000 lines), Python exits with the same IOError you have reported.

The problem is due to SIGPIPE handling. You can solve this problem using the following code:

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

Update: As pointed out in the comments, python docs already have a good answer.

See here for background on this solution. Better answer here.

This can also occur if the read end of the output from your script dies prematurely

ie open.py | otherCommand

if otherCommand exits and open.py tries to write to stdout

I had a bad gawk script that did this lovely to me.

Closes should be done in reverse order of the opens.

To bring information from the many helpful answers together, with some additional information:

  • Standard Unix signal SIGPIPE is sent to a process writing to a pipe when there's no process reading from the pipe (anymore).

    • This is not necessarily an error condition; some Unix utilities such as head by design stop reading prematurely from a pipe, once they've received enough data.
    • Therefore, an easy way to provoke this error is to pipe to head[1]; e.g.:
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • By default - i.e., if the writing process does not explicitly trap ABC0 - the writing process is simply terminated, and its exit code is set to 141, which is calculated as 128 (to signal termination by signal in general) + 13 (SIGPIPE's specific signal number).

  • However, by design Python itself traps SIGPIPE and translates it into a Python ABC1 (Python 3) / IOError (Python 2) instance with errno value errno.EPIPE.

    • Note: If you use a Unix emulation environment on Windows, the error may surface differently - see this answer.
  • If a Python script does not catch the exception, Python outputs error message BrokenPipeError: [Errno 32] Broken pipe (Python 3, possibly twice, with Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> sandwiched in between) / IOError: [Errno 32] Broken pipe (Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>0) and Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>1Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>2 - this is the symptom Johannes (the OP) saw.

Windows considerations (SIGPIPE is a Unix-only signal)

  • If your script needs to run directly on Windows too, you may have to conditionally bypass code that references SIGPIPE, as shown in this answer.

  • If your script runs in a Unix subsystem on Windows, the SIGPIPE signal may surface differently than on Unix - see this answer.


There are two ways to solve this problem:

Generally, it is not advisable to silence this exception, as it may signal a severe error condition, depending on your script's purpose, such as the receiving end of a network socket unexpectedly closing.

  • However, if your script is a command-line utility, where quiet termination may not only be acceptable but preferred so as to play nicely with the standard head utility, for instance, you can abort quietly as follows, using signal.signal() to install the platform's default signal handler (which behaves as described above), as also shown in akhan's answer (works in both Python 3 and 2):
# 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)
  • Otherwise, if you want to handle the SIGPIPE-triggered exception yourself (works in both Python 3 and 2, adapted from the 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] Note that in bash you will by default only see head's exit code - which is 0 - reflected in $? afterwards. Use echo ${PIPESTATUS[0]} to see Python's exit code.

[2] Curiously, on macOS 10.15.7 (Catalina), with Python 3.9.2 (but not 2.x), I see exit code 120, but the docs say 1, and that's what I also see on Linux.

I feel obliged to point out that the method using

signal(SIGPIPE, SIG_DFL)

is indeed dangerous (as already suggested by David Bennet in the comments) and in my case led to platform-dependent funny business when combined with multiprocessing.Manager (because the standard library relies on BrokenPipeError being raised in several places). To make a long and painful story short, this is how I fixed it:

First, you need to catch the IOError (Python 2) or BrokenPipeError (Python 3). Depending on your program you can try to exit early at that point or just ignore the exception:

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

However, this isn't enough. Python 3 may still print a message like this:

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

Unfortunately getting rid of that message is not straightforward, but I finally found http://bugs.python.org/issue11380 where Robert Collins suggests this workaround that I turned into a decorator you can wrap your main function with (yes, that's some crazy indentation):

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

I know this is not the "proper" way to do it, but if you are simply interested in getting rid of the error message, you could try this workaround:

python your_python_code.py 2> /dev/null | other_command

The top answer (if e.errno == errno.EPIPE:) here didn't really work for me. I got:

AttributeError: 'BrokenPipeError' object has no attribute 'EPIPE'

However, this ought to work if all you care about is ignoring broken pipes on specific writes. I think it's safer than trapping SIGPIPE:

try:
# writing, flushing, whatever goes here
except BrokenPipeError:
exit( 0 )

You obviously have to make a decision as to whether your code is really, truly done if you hit the broken pipe, but for most purposes I think that's usually going to be true. (Don't forget to close file handles, etc.)

Depending on the exact cause of the issue, it might help to set an environment variable PYTHONUNBUFFERED=1, which forces the stdout and stderr streams to be unbuffered. See: https://docs.python.org/3/using/cmdline.html#cmdoption-u

So, your command

open.py | othercommand

becomes:

PYTHONUNBUFFERED=1 open.py | othercommand

Example:

$ python3 -m http.server | tee -a access.log
^CException ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe


$ PYTHONUNBUFFERED=1 python3 -m http.server | tee -a access.log
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
^C
$