[英]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.