繁体   English   中英

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

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

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

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

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

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

将许多有用的答案中的信息汇总在一起,并附上一些附加信息:

  • 当没有进程从管道(不再)读取时, 标准 Unix 信号SIGPIPE被发送到写入管道的进程。

    • 这不一定是错误条件; 一些 Unix 实用程序(例如head by design)在接收到足够的数据后会过早地停止从管道中读取数据。
    • 因此,一个简单的方法来引发此错误是管head [1]; 例如:
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • 默认情况下- 即,如果写入进程没有明确捕获SIGPIPE - 写入进程被简单地终止,并且其退出代码设置为141 ,计算为128 (通常通过信号发出终止信号)+ 13SIGPIPE ' s 特定信号编号)。

  • 但是,按照设计, 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(OP)看到的症状。

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,改编自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 上看到的。

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

当您尝试写入另一端已关闭的管道时,会出现“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退出。

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

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

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

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

最重要的答案( if e.errno == errno.EPIPE: )在这里对我不起作用。 我有:

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

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

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

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

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

即 open.py | 其他命令

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

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

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

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

暂无
暂无

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

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