IOError:[Errno 32]管道损坏:Python


87

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

@squiguy第2行:print(f1.readlines())
JOHANNES_NYÅTT

2
您在第2行上发生了两个IO操作:从中读取a.txt和向中写入stdout。也许尝试将它们分成单独的行,以便您可以看到哪个操作触发了异常。如果stdout是管道,并且读取端已关闭,则可以解决该EPIPE错误。
James Henstridge

1
我可以在正确的条件下在输出上重现此错误,因此我怀疑这print是罪魁祸首。@JOHANNES_NYÅTT,您能说明一下如何启动Python脚本吗?您是否将标准输出重定向到某个地方?
Blckknght

2
这是以下问题的可能重复:stackoverflow.com/questions/11423225/...

Answers:


44

我没有重现这个问题,但是也许这种方法可以解决这个问题:(逐行写入stdout而不是使用print

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

你能抓住破损的管道吗?这将文件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

您还需要确保othercommand在管道变得太大之前正在从管道读取-https: //unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer


7
尽管这是一种良好的编程习惯,但我认为这与发问者遇到的管道错误有关(可能与print调用有关,而不与读取文件有关)。
Blckknght

@Blckknght我添加了一些问题和替代方法,并希望作者提供一些反馈。如果问题是将大量数据从打开的文件直接发送到print语句,则上述备选方法之一可能会解决该问题。
Alex L

(最简单的解决方案通常是最好的-除非出于特殊原因需要加载整个文件然后打印,否则以其他方式进行处理)
Alex L

1
排除故障方面的出色工作!虽然我可以认为这个答案是理所当然的,但是只有在看到其他答案(和我自己的方法)与您的答案相比显得苍白之后,我才能对此表示赞赏。
Jesvin Jose

116

问题是由于SIGPIPE处理。您可以使用以下代码解决此问题:

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

有关此解决方案的背景信息,请参见此处。最好在这里回答。


13
正如我刚刚发现的那样,这非常危险,因为如果您在套接字(httplib或其他任何东西)上获得了SIGPIPE,您的程序将直接退出而不会发出警告或错误。
大卫·贝内特

1
@DavidBennett,我确定它取决于应用程序,并且对于您而言,可接受的答案是正确的答案。有一个更全面的Q&A在这里的人要经过,并作出明智的决定。IMO,对于命令行工具,在大多数情况下,最好忽略管道信号。
akhan

1
有什么办法只能暂时做到这一点吗?
Nate Glenn

2
@NateGlenn您可以保存现有的处理程序,并在以后还原它。
akhan

3
有人可以回答我,为什么人们认为Blogspot文章比官方文档更好地成为事实来源(提示:打开链接以查看如何正确解决折断的管道错误)?:)
Yurii Rabeshko

92

要将Alex L.的有用答案akhan的有用答案Blckknght的有用答案以及一些其他信息结合在一起:

  • 标准Unix信号SIGPIPE被发送到进程写入到一个管道时,有没有进程读取从管道(了)。

    • 这不一定是错误条件;一些Unix工具,如head 设计停止从管道过早地阅读,一旦他们已经收到了足够的数据。
  • 默认情况下(即,如果写入过程未显式陷入陷阱 SIGPIPE),写入过程将简单地终止,并且其退出代码设置为141,其计算公式为128(通常以信号表示信号终止)+ 13SIGPIPE特定信号编号) 。

  • 但是,根据设计,Python本身会捕获SIGPIPEIOError并将其转换为具有errnovalue的Python实例errno.EPIPE,以便Python脚本可以捕获它(如果可以选择的话)-有关方法,请参见Alex L.的答案

  • 如果一个Python脚本没有抓住它,Python的输出错误信息IOError: [Errno 32] Broken pipe终止,退出代码脚本1-这是症状的OP锯。

  • 在许多情况下,更具破坏性,而不是有所帮助,因此,需要恢复为默认行为

    • akhan的回答所述,使用signal模块可以做到这一点;将要处理的信号作为第一个参数,将处理程序作为第二个参数;特殊处理程序值表示系统的默认行为:signal.signal()SIG_DFL

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

32

当您尝试写入另一端已关闭的管道时,会发生“管道破裂”错误。由于您显示的代码不直接涉及任何管道,因此我怀疑您在Python之外进行了一些操作,以将Python解释器的标准输出重定向到其他地方。如果您正在运行以下脚本,则可能会发生这种情况:

python foo.py | someothercommand

您遇到的问题someothercommand是退出时没有阅读其标准输入上的所有可用信息。这会导致您的写入(通过print)在某些时候失败。

我能够在Linux系统上使用以下命令来重现该错误:

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

如果关闭less翻页器而不滚动其所有输入(1000行),Python会以IOError您报告的状态退出。


10
是的,这是事实,但是我该如何解决?
JOHANNES_NYÅTT

2
请让我知道如何解决它。
JOHANNES_NYÅTT

1
@JOHANNES_NYÅTT:由于大多数类Unix系统上的管道提供了缓冲,因此它可能适用于小型文件。如果您可以将文件的全部内容写入缓冲区,则即使其他程序从不读取该数据,也不会引发错误。但是,如果写块(由于缓冲区已满),则在其他程序退出时它将失败。再说一遍:另一个命令是什么?我们仅凭Python代码就无法再为您提供帮助(因为它不是做错事的部分)。
Blckknght

1
十行输出后,管道到头部时出现异常。非常合乎逻辑,但仍然出乎意料:)
安德烈·拉斯洛

4
@Blckknght:在总体上是好的信息,但重新“修正是:和‘这是做了部分错误的事情’:一个SIGPIPE信号不一定指示错误条件;一些Unix工具,特别是head通过设计,在正常运行期间关闭管道早些时候,一旦他们读取了所需的数据,可以了
。– mklement0

20

我觉得有必要指出

signal(SIGPIPE, SIG_DFL) 

确实是很危险的(正如David Bennet在评论中所建议的那样),并且在我的案例中,与之结合使用时会导致依赖平台的滑稽生意multiprocessing.Manager(因为标准库依赖在多个地方引发BrokenPipeError)。为了使一个长而痛苦的故事简短,我将其固定为:

首先,您需要捕获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

2
这似乎没有为我解决。
凯尔·布​​莱恩汀汀

在我添加了BrokenPipeError之外,它为我工作了:传递了supress_broken_pipe_msg函数
Rupen B

2

我知道这不是执行此操作的“正确”方法,但是如果您只是对摆脱错误消息感兴趣,可以尝试以下解决方法:

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

2

if e.errno == errno.EPIPE:此处的最高答案()不适用于我。我有:

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

但是,如果您只关心忽略特定写入操作中损坏的管道,则此方法应该起作用。我认为这比捕获SIGPIPE更安全:

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

显然,您必须决定如果碰到破损的管道,您的代码是否真的完成,但是对于大多数目的,我认为通常是正确的。(不要忘记关闭文件句柄等)


1

如果脚本输出的读取端过早死亡,也会发生这种情况

即open.py | otherCommand

如果otherCommand退出并且open.py尝试写入stdout

我有一个糟糕的gawk脚本,这对我来说很可爱。


2
这不是在过程中从管道读取奄奄一息,必然:一些Unix工具,特别是head通过设计,在正常运行期间关闭管道初期,一旦他们看过尽可能多的数据,他们需要的。大多数CLI仅因其默认行为而遵从系统:悄悄地终止读取过程并报告退出代码141(在外壳程序中这并不容易,因为管道的最后一条命令确定了整个退出代码)。不幸的是,Python的默认行为是死于喧嚣
mklement0

-1

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


4
虽然这通常是个好习惯,但不这样做本身也不是问题,也不能解释OP的症状。
mklement0 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.