【问题标题】:IOError: [Errno 32] Broken pipe when piping: `prog.py | othercmd`IOError:[Errno 32] 管道时管道损坏:`prog.py |其他命令`
【发布时间】:2012-12-21 21:01:59
【问题描述】:

我有一个非常简单的 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())
  • 您在第 2 行发生了两个 IO 操作:从 a.txt 读取和写入 stdout。也许尝试将它们拆分为单独的行,以便您可以查看哪个操作触发了异常。如果stdout 是一个管道并且读取端已经关闭,那么这可能是EPIPE 错误的原因。
  • 我可以在输出中重现这个错误(在正确的条件下),所以我怀疑print 调用是罪魁祸首。 @JOHANNES_NYÅTT,你能澄清一下你是如何启动你的 Python 脚本的吗?您是否将标准输出重定向到某个地方?
  • 这可能与以下问题重复:stackoverflow.com/questions/11423225/…

标签: python python-3.x sigpipe


【解决方案1】:

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

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

更新:正如 cmets 中所指出的,python docs 已经有了一个很好的答案。

See here 了解此解决方案的背景信息。更好的答案here

【讨论】:

  • 这是非常危险的,正如我刚刚发现的那样,因为如果您在套接字(httplib 或其他)上获得 SIGPIPE,您的程序将直接退出而不会发出警告或错误。
  • @DavidBennett,我相信它依赖于应用程序,并且出于您的目的,接受的答案是正确的。有一个非常彻底的问答here 供人们通过并做出明智的决定。 IMO,对于命令行工具,在大多数情况下最好忽略管道信号。
  • 有什么方法可以暂时做到这一点?
  • @NateGlenn 您可以保存existing handler 并稍后恢复。
  • 有人能回答我为什么人们认为 blogspot 文章比official documentation 更好的事实来源(提示:打开链接以查看如何正确修复损坏的管道错误)? :)
【解决方案2】:

将来自许多有用答案的信息与一些附加信息结合在一起:

  • Standard Unix signal SIGPIPE 被发送到进程写入pipe,当没有进程读取管道时(不再)。

    • 这不一定是错误条件;一些 Unix 实用程序,例如 head设计一旦收到足够的数据,就会过早地停止从管道读取。
    • 因此,引发此错误的一种简单方法是通过管道传送到head[1];例如。:
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • 默认情况下 - 即,如果写入过程没有明确陷阱 SIGPIPE - 写入过程只是终止 ,其退出码设置为141,计算为128(一般通过信号终止信号)+13SIGPIPE的具体信号编号 em>)。

  • 然而,根据设计 Python本身 会捕获 SIGPIPE将其转换为 Python BrokenPipeError(Python 3) /IOError (Python 2) 实例与errnoerrno.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 pipePython 2)和使用退出代码 1[2] 终止脚本 - 这是 Johannes(OP)看到的症状。

Windows 注意事项(SIGPIPE 是 Unix 专用信号)

  • 如果您的脚本也需要直接在 Windows 上运行,您可能必须有条件地绕过引用 SIGPIPE 的代码,如 this answer 所示。

  • 如果您的脚本在 Windows 上的 Unix 子系统中运行,SIGPIPE 信号的显示方式可能与在 Unix 上不同 - 请参阅this answer


有两种方法可以解决这个问题:

一般来说,建议沉默此异常,因为它可能表示严重的错误情况,具体取决于脚本的用途,例如网络套接字的接收端意外关闭。

  • 但是,如果您的脚本是命令行实用程序,安静终止不仅可以接受,而且首选以便播放与标准的head 实用程序完美搭配,例如,您可以安静地如下使用signal.signal() 安装平台的默认信号处理程序(其行为如上所述),如 akhan's answer 中所示(适用于 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,改编自 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——之后会反映在$? 中。使用echo ${PIPESTATUS[0]}查看Python的退出代码。

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 虽然这是一种很好的编程习惯,但我认为这与提问者遇到的管道损坏错误无关(这可能与print 调用有关,与阅读无关文件)。
    • @Blckknght 我添加了一些问题和替代方法,希望得到作者的一些反馈。如果问题是将大量数据从打开的文件直接发送到 print 语句,那么上述替代方法之一可能会解决它。
    • (最简单的解决方案通常是最好的 - 除非有特殊原因加载整个文件然后打印,否则以不同的方式进行)
    • 解决这个问题的工作很棒!虽然我可以认为这个答案是理所当然的,但只有在看到其他答案(以及我自己的方法)与您的答案相比后,我才能体会到这一点。
    【解决方案4】:

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

    python foo.py | someothercommand
    

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

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

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

    如果我关闭 less 寻呼机而不滚动其所有输入(1000 行),Python 将以您报告的相同 IOError 退出。

    【讨论】:

    • 是的,这是真的,但我该如何解决呢?
    • 请告诉我如何解决它。
    • @JOHANNES_NYÅTT:由于大多数类 Unix 系统上的管道提供缓冲,它可能适用于小文件。如果您可以将文件的全部内容写入缓冲区,则如果其他程序从不读取该数据,则不会引发错误。但是,如果写入阻塞(因为缓冲区已满),那么当其他程序退出时它将失败。再说一遍:另一个命令是什么?我们无法再仅使用 Python 代码为您提供帮助(因为这不是做错事的部分)。
    • 我在管道到头时遇到了这个问题...十行输出后出现异常。很合乎逻辑,但仍然出乎意料:)
    • @Blckknght:一般来说信息不错,但要重新“修复:和“做错误事情的部分”:@987654327 @ 信号不一定表示 错误 条件;一些 Unix 实用程序,特别是 head按照设计,在正常操作期间一旦读取为他们需要的数据。
    【解决方案5】:

    我觉得有必要指出使用的方法

    signal(SIGPIPE, SIG_DFL) 
    

    确实是dangerous(正如 David Bennet 在 cmets 中已经建议的那样)并且在我的情况下,当与 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 提出了这个解决方法,我把它变成了一个装饰器,你可以用它来包装你的 main 函数(是的,这是一些疯狂的缩进):

    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
    

    【讨论】:

    • 这似乎没有为我解决。
    • 在我添加除了 BrokenPipeError: pass in the supress_broken_pipe_msg 函数后它对我有用
    • 是的,这需要suppress_broken_pipe_msg 位以使Exception ignored in: &lt;_io.TextIOWrapper 消息静音和第一个except broken_pipe_exception 代码块以Python 2/3 兼容的方式实际处理损坏的管道异常。
    【解决方案6】:

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

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

    【讨论】:

    • python your_python_code.py | tee /dev/null | other_command — 也可以。但我不明白为什么它适用于stderr
    • 是否会将整个输出发送到/dev/null
    【解决方案7】:

    这里的最佳答案 (if e.errno == errno.EPIPE:) 并没有真正适合我。我得到了:

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

    但是,如果您只关心在特定写入时忽略损坏的管道,这应该可以工作。我认为这比捕获 SIGPIPE 更安全:

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

    你显然必须决定你的代码是否真的、真的完成了,如果你碰到坏了的管道,但对于大多数目的,我认为这通常是正确的。 (别忘了关闭文件句柄等)

    【讨论】:

      【解决方案8】:

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

      即 open.py |其他命令

      如果 otherCommand 退出并且 open.py 尝试写入标准输出

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

      【讨论】:

      • 这不是关于从管道读取的进程dying,必然是:一些 Unix 实用程序,特别是 head在正常操作期间按设计一旦他们读取了他们需要的尽可能多的数据,就尽早关闭管道。大多数 CLI 只是遵循系统的默认行为:安静地终止读取过程并报告退出代码 141(在 shell 中,这并不明显,因为管道的 last 命令确定整体退出代码)。不幸的是,Python 的默认行为是noisily
      【解决方案9】:

      根据问题的确切原因,设置环境变量 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
      $ 
      

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        • 虽然这通常是一种很好的做法,但不做本身并不是问题,也不能解释 OP 的症状。
        猜你喜欢
        • 2021-01-04
        • 2017-04-22
        • 2014-04-28
        • 2013-04-16
        • 1970-01-01
        • 2011-09-11
        • 2021-10-11
        • 1970-01-01
        • 2016-04-30
        相关资源
        最近更新 更多