当使用管道`prog.py | othercmd`时出现了"IOError: [Errno 32] Broken pipe"错误。

118

我有一个非常简单的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 第二行:print(f1.readlines()) - JOHANNES_NYÅTT
2
第2行发生了两个IO操作:从a.txt读取和写入到stdout。也许尝试将它们分成单独的行,以便您可以看到哪个操作触发了异常。如果stdout是一个管道,并且读端已关闭,则可能会导致EPIPE错误。 - James Henstridge
1
我可以在输出上重现这个错误(在正确的条件下),所以我怀疑print调用是罪魁祸首。@JOHANNES_NYÅTT,你能澄清一下你是如何启动你的Python脚本的吗?你是否将标准输出重定向到某个地方? - Blckknght
2
这可能是以下问题的重复:https://dev59.com/SGgu5IYBdhLWcg3wTVY6 - user2443147
10个回答

127

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

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

更新:如评论中所指出,Python 文档 已经给出了一个很好的答案。

有关此解决方案的背景,请点击这里。更好的答案请点击这里


19
这是非常危险的事情,正如我刚刚发现的那样,如果你在一个套接字上收到 SIGPIPE 信号 (例如 httplib 或其它), 你的程序将会毫无警告和错误地退出。 - David Bennett
2
@DavidBennett,我相信这取决于应用程序,并且对于您的目的,被接受的答案是正确的。有一个更详细的问答在这里,供人们参考并做出明智的决定。在我看来,对于命令行工具,在大多数情况下最好忽略管道信号。 - akhan
2
@NateGlenn 你可以保存现有的处理程序,稍后再恢复它。 - akhan
7
有人能回答我为什么人们认为Blogspot文章比官方文档更可信吗?(提示:打开链接查看如何正确修复损坏的管道错误) :) - Yurii Rabeshko
1
@YuriiRabeshko 因为官方文档不好。下面的一个答案演示了实际解决方案的复杂性。 - Nickolay
显示剩余5条评论

122
为了将有用的答案整合在一起,并添加一些额外的信息:
  • 当一个进程从一个管道写入数据时,如果没有其他进程在读取该管道(或已经停止读取),则会向该进程发送标准Unix信号SIGPIPE

    • 这不一定是一个错误的状态;一些Unix实用程序(如head)在接收到足够的数据后就有意地提前停止从管道中读取。
    • 因此,触发此错误的简单方法是使用head命令进行管道传输(参见以下示例):
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • 默认情况下,即如果写入进程没有显式地捕获SIGPIPE信号,则该进程将被简单地终止,并且其退出代码将被设置为141,计算公式为128(一般情况下表示通过信号终止) + 13(代表SIGPIPE信号)。

  • 然而,Python 本身捕获SIGPIPE信号,并将它转换为一个Python BrokenPipeError(Python 3)/IOError(Python 2)实例,其errno值为errno.EPIPE

    • 注意:如果您在Windows上使用Unix仿真环境,则错误可能会以不同的方式显示。详见此回答
  • 如果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(原问题作者)所看到的症状。

Windows注意事项(SIGPIPE是仅适用于Unix的信号)

  • 如果您的脚本还需要直接在Windows上运行,您可能需要有条件地绕过引用SIGPIPE的代码,如此答案所示。

  • 如果您的脚本在Windows上的Unix子系统中运行,则SIGPIPE信号可能会与Unix上的不同方式出现 - 请参见此答案


解决此问题有两种方法:

通常,不建议 消除 此异常,因为它可能表示严重错误条件,取决于您的脚本目的,例如网络套接字接收端意外关闭。

  • 但是,如果您的脚本是一个命令行实用程序,其中安静的终止不仅可以被接受,而且还可以优先考虑,以便与标准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)
  • 否则,如果你想要自己处理由SIGPIPE触发的异常(适用于Python 3和2,改编自文档):
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 - 在之后反映在$?中。使用echo ${PIPESTATUS[0]}来查看Python的退出码。

[2] 奇怪的是,在macOS 10.15.7(Catalina)上,使用Python 3.9.2(但不是2.x),我看到退出码为120,但文档说是1,这也是我在Linux上看到的。


51

我没有复现这个问题,但也许这种方法可以解决它:(逐行写入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


9
虽然这是良好的编程实践,但我不认为它与提问者遇到的断开管道错误有任何关系(这可能与print调用有关,而不是读取文件) 。 - Blckknght
@Blckknght,我添加了一些问题和替代方法,并希望从作者那里得到一些反馈。如果问题是直接从打开的文件发送大量数据到打印语句,则上面的其中一种替代方法可能会解决它。 - Alex L
最简单的解决方案通常是最好的 - 除非有特殊原因需要加载整个文件然后打印它,否则可以用其他方式实现。 - Alex L
1
你在故障排除方面做得太棒了!虽然我本可以把这个答案视为理所当然,但只有在看到其他答案(以及我的方法)相比你的答案黯然失色后,我才能真正欣赏它。 - Jesvin Jose
你可能还想要一个换行符:sys.stdout.write(line + '\n') - undefined

31

"Broken Pipe"错误会在你尝试向一个已经被关闭了的管道写入数据时发生。由于你展示的代码并没有直接涉及任何管道,我猜测你正在Python之外进行一些操作,将Python解释器的标准输出重定向到其他地方。如果你像这样运行脚本,就可能会遇到这种问题:

python foo.py | someothercommand

你遇到的问题是 someothercommand 没有读取完其标准输入而退出。这会导致你通过 print 写入的内容在某个时刻失败。

我在 Linux 系统上使用以下命令重现了这个错误:

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

如果我没有滚动查看所有输入(1000行)就关闭less分页器,则Python会出现与您报告的相同的IOError错误。


14
没问题,这是真的,但我该怎么修复它? - JOHANNES_NYÅTT
2
请告诉我如何修复它。 - JOHANNES_NYÅTT
2
当我使用管道传输到head时,出现了这个问题...在输出十行后出现异常。相当合乎逻辑,但仍然出乎意料 :) - André Laszlo
2
这是因为你的管道可能只有大约64kB - 如果你试图向其中写入太多内容而其他进程没有读取它,那么它会抛出一个错误。关于那个评论,我不认为这是正确的 - 下一次写入将简单地阻塞,直到读者通过读取[部分]缓冲区来清空它。 - Tom Dalton
4
总体而言,信息很好,但是关于“fix that:”和“the part that's doing the wrong thing”的部分需要澄清:SIGPIPE信号并不一定表示出现了错误情况;一些Unix实用程序(特别是head)在正常操作期间有意为之,在它们读取完自己所需的数据后提前关闭管道。 - mklement0
显示剩余4条评论

22

我感到有责任指出,使用的方法是

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

4
对我来说,这似乎没有解决问题。 - Kyle Bridenstine
1
在 suppress_broken_pipe_msg 函数中添加 except BrokenPipeError: pass 后,它对我起作用了。 - Rupen B
是的,这需要同时使用 suppress_broken_pipe_msg 位来静默掉 <_io.TextIOWrapper 消息中的 Exception ignored in: 提示,并且第一个 except broken_pipe_exception 代码块可以在 Python 2/3 兼容的情况下处理断开的管道异常。 - Nickolay

4

我知道这不是正式的做法,但是如果你只是想把错误消息去掉,可以尝试这个解决方法:

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

python your_python_code.py | tee /dev/null | other_command也可以工作。但我不明白为什么它能处理stderr - Ilia w495 Nikitin
这会将整个输出发送到 /dev/null 吗? - ssanch

3
这里的最佳答案(if e.errno == errno.EPIPE:)对我并没有起作用。我收到了以下错误信息:
AttributeError: 'BrokenPipeError' object has no attribute 'EPIPE'

然而,如果你只关心特定写入时忽略损坏的管道,这应该会生效。我认为这比捕获SIGPIPE更安全:

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

当你遇到“broken pipe”错误时,显然你需要决定你的代码是否真正完成,但大多数情况下我认为通常是正确的。(不要忘记关闭文件句柄等操作。)


2
根据问题的确切原因,设置一个环境变量PYTHONUNBUFFERED=1可能会有帮助,它可以强制stdout和stderr流无缓冲。参见:https://docs.python.org/3/using/cmdline.html#cmdoption-u 因此,您的命令是:
open.py | othercommand

变成:

PYTHONUNBUFFERED=1 open.py | othercommand

例子:

$ 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
$ 

1
这也可能发生在你的脚本输出的读取端意外死亡时。例如,open.py | otherCommand,如果otherCommand退出,并且open.py尝试写入stdout,我曾经遇到一个糟糕的gawk脚本给我带来了这样的问题。

2
这并不一定是关于进程从管道中读取数据时“死亡”的问题:一些 Unix 实用程序,特别是 head,在正常操作期间会 设计 尽早关闭管道,一旦它们读取了所需的数据。大多数 CLI 简单地遵循系统的默认行为:静默终止读取进程并报告退出代码 141(在 shell 中不容易看出来,因为管道的 最后 命令决定了整体退出代码)。不幸的是,Python 的默认行为是发出嘈杂的错误信息。 - mklement0

-3

关闭应该按照打开的相反顺序进行。


4
虽然这是普遍的良好实践,但不这样做本身并不是问题,也不能解释原帖作者的症状。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接