簡體   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