简体   繁体   English

IOError: [Errno 32] 管道破裂:`prog.py | 其他命令`

[英]IOError: [Errno 32] Broken pipe when piping: `prog.py | othercmd`

I have a very simple Python 3 script:我有一个非常简单的 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()

But it always says:但它总是说:

IOError: [Errno 32] Broken pipe

I saw on the internet all the complicated ways to fix this, but I copied this code directly, so I think that there is something wrong with the code and not Python's SIGPIPE.我在网上看到了各种复杂的解决方法,但是我直接复制了这段代码,所以我认为是代码有问题,而不是Python的SIGPIPE。

I am redirecting the output, so if the above script was named "open.py", then my command to run would be:我正在重定向输出,所以如果上面的脚本被命名为“open.py”,那么我要运行的命令是:

open.py | othercommand

The problem is due to SIGPIPE handling.问题是由于 SIGPIPE 处理造成的。 You can solve this problem using the following code:您可以使用以下代码解决此问题:

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

See here for background on this solution. 有关此解决方案的背景信息, 请参见此处 Better answer here .更好的答案在这里

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).当没有进程从管道(不再)读取时, 标准 Unix 信号SIGPIPE被发送到写入管道的进程。

    • 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.一些 Unix 实用程序(例如head by design)在接收到足够的数据后会过早地停止从管道中读取数据。
    • Therefore, an easy way to provoke this error is to pipe to head [1] ;因此,一个简单的方法来引发此错误是管head [1]; eg:例如:
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • By default - ie, if the writing process does not explicitly trap SIGPIPE - 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 ).默认情况下- 即,如果写入进程没有明确捕获SIGPIPE - 写入进程被简单地终止,并且其退出代码设置为141 ,计算为128 (通常通过信号发出终止信号)+ 13SIGPIPE ' s 特定信号编号)。

  • However , by design Python itself traps SIGPIPE and translates it into a Python BrokenPipeError (Python 3) / IOError (Python 2) instance with errno value errno.EPIPE .但是,按照设计, Python本身会捕获SIGPIPE并将其转换为 Python BrokenPipeError (Python 3) / IOError (Python 2)实例,其errno值为errno.EPIPE

    • Note: If you use a Unix emulation environment on Windows , the error may surface differently - see this answer.注意:如果您在 Windows 上使用 Unix 仿真环境,则错误可能会以不同方式出现 - 请参阅此答案。
  • 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 ( Python 2 ) and terminates the script with exit code 1 [2] - this is the symptom Johannes (the OP) saw.如果 Python脚本捕获异常,则 Python输出错误消息BrokenPipeError: [Errno 32] Broken pipe ( Python 3 ,可能两次,在以下Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>夹在中间) / IOError: [Errno 32] Broken pipe ( Python 2 ) 并用退出代码1 [2]终止脚本- 这是 Johannes(OP)看到的症状。

Windows considerations ( SIGPIPE is a Unix-only signal) Windows考虑( SIGPIPE是一个仅限 Unix 的信号)

  • 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 .如果您的脚本也需要直接在 Windows 上运行,您可能必须有条件地绕过引用SIGPIPE代码,如本答案所示。

  • If your script runs in a Unix subsystem on Windows, the SIGPIPE signal may surface differently than on Unix - see this answer .如果您的脚本在 Windows 上的Unix 子系统中运行,则SIGPIPE信号可能Unix 上的不同- 请参阅此答案


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):但是,如果您的脚本是命令行实用程序,那么安静终止不仅是可以接受的,而且是首选,以便与标准的head实用程序很好地配合使用,例如,您可以按如下方式安静地中止,使用signal.signal()安装平台的默认信号处理程序(其行为如上所述),如akhan 的回答中所示(适用于 Python 3 和 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 ):否则,如果您想自己处理 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] Note that in bash you will by default only see head 's exit code - which is 0 - reflected in $? [1] 请注意,在bash ,默认情况下您只会看到head的退出代码 - 即0 - 反映在$? afterwards.然后。 Use echo ${PIPESTATUS[0]} to see Python's exit code.使用echo ${PIPESTATUS[0]}查看 Python 的退出代码。

[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. [2] 奇怪的是,在 macOS 10.15.7 (Catalina) 上,使用 Python 3.9.2(但不是 2.x),我看到退出代码120 ,但文档说1 ,这也是我在 Linux 上看到的。

I haven't reproduced the issue, but perhaps this method would solve it: (writing line by line to stdout rather than using print )我没有重现这个问题,但也许这个方法可以解决它:(逐行写入stdout而不是使用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.这会将文件逐行写入stdout ,直到管道关闭。

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您还需要确保othercommand在它变得太大之前从管道中读取 - 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.当您尝试写入另一端已关闭的管道时,会出现“Broken Pipe”错误。 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.由于您显示的代码不直接涉及任何管道,我怀疑您正在 Python 之外做一些事情来将 Python 解释器的标准输出重定向到其他地方。 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.您遇到的问题是someothercommand正在退出,而没有读取其标准输入上可用的所有内容。 This causes your write (via print ) to fail at some point.这会导致您的写入(通过print )在某些时候失败。

I was able to reproduce the error with the following command on a Linux system:我能够在 Linux 系统上使用以下命令重现该错误:

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.如果我在不滚动所有输入(1000 行)的情况下关闭了less寻呼机,Python 会以您报告的相同IOError退出。

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).确实很危险(正如 David Bennet 在评论中已经建议的那样),在我的情况下,当与multiprocessing.Manager结合时会导致依赖于平台的有趣业务(因为标准库依赖于在多个地方引发的 BrokenPipeError)。 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).首先,您需要捕获IOError (Python 2) 或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: Python 3 可能仍会打印如下消息:

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):不幸的是,摆脱该消息并不简单,但我终于找到了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

我知道这不是“正确”的方法,但如果您只是想摆脱错误消息,您可以尝试以下解决方法:

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.最重要的答案( if e.errno == errno.EPIPE: )在这里对我不起作用。 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:我认为这比捕获 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.) (不要忘记关闭文件句柄等)

This can also occur if the read end of the output from your script dies prematurely如果脚本输出的读取端过早终止,也会发生这种情况

ie open.py |即 open.py | otherCommand其他命令

if otherCommand exits and open.py tries to write to stdout如果 otherCommand 退出并且 open.py 尝试写入标准输出

I had a bad gawk script that did this lovely to me.我有一个糟糕的 gawk 脚本,它对我来说很可爱。

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.根据问题的确切原因,设置环境变量PYTHONUNBUFFERED=1可能会有所帮助,这会强制 stdout 和 stderr 流无缓冲。 See: https://docs.python.org/3/using/cmdline.html#cmdoption-u请参阅: 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
$ 

关闭应该以与打开相反的顺序进行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM