IOError:[Errno 32] 管道传输时管道损坏:`prog.py | othercmd`
- 2025-04-16 08:55:00
- admin 原创
- 23
问题描述:
我有一个非常简单的 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
解决方案 1:
这个问题是由于 SIGPIPE 处理引起的。您可以使用以下代码解决此问题:
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL)
更新:正如评论中指出的那样,python 文档已经有了一个很好的答案。
有关此解决方案的背景信息,请参见此处。更好的答案在这里。
解决方案 2:
将许多有用的答案中的信息汇总在一起,并提供一些附加信息:
SIGPIPE
当没有进程从管道读取(不再读取)时,会向写入管道的进程发送标准 Unix 信号。**
+ 这不一定是*错误*情况;一些 Unix 实用程序(例如,`head` *按照设计)*一旦收到足够的数据就会过早停止从管道读取。
+ 因此,引发此错误的一个简单方法是通过管道传输到`head`[1];例如:
- `python -c 'for x in range(10000): print(x)' | head -n 1`
默认情况下- 即,如果写入过程没有明确捕获
SIGPIPE
- 写入过程将简单地终止,并且其退出代码设置为141
,其计算方式为128
(通常通过信号终止信号)+13
(SIGPIPE
的特定信号编号)。然而,根据设计,Python本身会捕获
SIGPIPE
它并将其转换为具有值的PythonBrokenPipeError
(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)并以退出代码[2]终止脚本1
- 这是 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,改编自文档):
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.
")
# 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
解决方案 4:
当您尝试写入另一端已关闭的管道时,会出现“管道损坏”错误。由于您显示的代码没有直接涉及任何管道,我怀疑您在 Python 之外做了一些操作,将 Python 解释器的标准输出重定向到其他地方。如果您运行如下脚本,可能会发生这种情况:
python foo.py | someothercommand
您遇到的问题是,它someothercommand
没有读取标准输入中所有可用内容就退出了。这会导致您的写入 (via print
) 在某个时刻失败。
我能够在 Linux 系统上使用以下命令重现该错误:
python -c 'for i in range(1000): print i' | less
如果我关闭less
寻呼机而没有滚动浏览其所有输入(1000 行),Python 就会退出并显示IOError
您所报告的内容。
解决方案 5:
我觉得有必要指出,使用的方法
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
解决方案 6:
我知道这不是“正确”的方法,但如果您只是想摆脱错误消息,您可以尝试这种解决方法:
python your_python_code.py 2> /dev/null | other_command
解决方案 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:
根据问题的具体原因,设置环境变量可能会有所帮助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
$
解决方案 9:
如果脚本输出的读取端过早终止,也会发生这种情况
即 open.py | otherCommand
如果 otherCommand 退出并且 open.py 尝试写入 stdout
我有一个糟糕的 gawk 脚本,它对我产生了这种不良影响。
解决方案 10:
关闭应按照打开的相反顺序进行。
扫码咨询,免费领取项目管理大礼包!