[英]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
将许多有用的答案中的信息汇总在一起,并附上一些附加信息:
当没有进程从管道(不再)读取时, 标准 Unix 信号SIGPIPE
被发送到写入管道的进程。
head
by design)在接收到足够的数据后会过早地停止从管道中读取数据。head
[1]; 例如:
python -c 'for x in range(10000): print(x)' | head -n 1
默认情况下- 即,如果写入进程没有明确捕获SIGPIPE
- 写入进程被简单地终止,并且其退出代码设置为141
,计算为128
(通常通过信号发出终止信号)+ 13
( SIGPIPE
' s 特定信号编号)。
但是,按照设计, Python本身会捕获SIGPIPE
并将其转换为 Python BrokenPipeError
(Python 3) / IOError
(Python 2)实例,其errno
值为errno.EPIPE
。
如果 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)
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.