IOError: [Errno 32] Сломанная труба: Python
У меня очень простой Python 3 script:
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.
Я перенаправляю вывод, поэтому, если вышеуказанный script был назван "open.py", тогда моя команда для запуска:
open.py | othercommand
Ответы
Ответ 1
Я не воспроизвел проблему, но, возможно, этот метод разрешил бы ее: (запись строки за строкой до 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
Ответ 2
Проблема связана с обработкой SIGPIPE. Вы можете решить эту проблему, используя следующий код:
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL)
См. Здесь информацию об этом решении. Лучше ответьте здесь.
Ответ 3
Принести полезный ответ Alex L., полезный ответ akhan и Полезный ответ Blckknght вместе с дополнительной информацией:
-
Стандартный сигнал Unix SIGPIPE
отправляется в процесс, записывая pipe, когда нет процесса чтения из канала (больше).
- Это не обязательно условие ошибки; некоторые утилиты Unix, такие как
head
по дизайну, перестают читать преждевременно из трубы, как только они получили достаточное количество данных.
-
По умолчанию - то есть , если процесс записи явно не ловушка SIGPIPE
- процесс записи просто завершается, а его код выхода устанавливается на 141
, который рассчитывается как 128
(для завершения сигнала по сигналу в целом) + 13
(SIGPIPE
номер конкретного сигнала).
-
Однако по дизайну сам Python ловушки SIGPIPE
и переводит его в экземпляр Python IOError
с errno
значением errno.EPIPE
, так что Python script может его поймать, если он так захочет - см. ответить Alex L., как это сделать.
-
Если Python script не видит его, Python выводит сообщение об ошибке IOError: [Errno 32] Broken pipe
и завершает script с кодом выхода 1
- это симптом, который увидел ОР.
-
Во многих случаях это более разрушительно, чем полезно, поэтому желательно вернуться к поведению по умолчанию:
-
Использование модуля signal
позволяет только это, как указано в akhan answer; signal.signal()
принимает сигнал для обработки как 1-й аргумент, а обработчик - второй; значение специального обработчика SIG_DFL
представляет поведение системы по умолчанию:
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE, SIG_DFL)
Ответ 4
Ошибка "Broken Pipe" возникает, когда вы пытаетесь записать в трубу, которая была закрыта на другом конце. Так как код, который вы показали, не содержит прямых труб, я подозреваю, что вы делаете что-то за пределами Python, чтобы перенаправить стандартный вывод интерпретатора Python в другое место. Это может произойти, если вы используете script следующим образом:
python foo.py | someothercommand
Проблема заключается в том, что someothercommand
выходит, не читая все доступные на своем стандартном входе. Это заставляет вашу запись (через print
) сбой в какой-то момент.
Я смог воспроизвести ошибку с помощью следующей команды в системе Linux:
python -c 'for i in range(1000): print i' | less
Если я закрою пейджер less
без прокрутки всего его ввода (1000 строк), Python выйдет с тем же IOError
, о котором вы сообщили.
Ответ 5
Я чувствую себя обязанным указать, что метод с использованием
signal(SIGPIPE, SIG_DFL)
действительно опасно (как уже было предложено Дэвидом Беннетом в комментариях), и в моем случае это привело к зависящей от платформы смешной работе в сочетании с 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, где Роберт Коллинз предлагает это обходное решение, которое я превратил в декоратора, который вы можете обернуть основная функция с (да, это какой-то сумасшедший отступ):
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
Это также может произойти, если конец вывода вывода из вашего script преждевременно умирает
т.е. open.py | otherCommand
если otherCommand завершает работу и open.py пытается записать в stdout
У меня был плохой gawk script, который сделал это прекрасно для меня.
Ответ 7
Закрытие должно выполняться в обратном порядке.
Ответ 8
Я знаю, что это не "правильный" способ сделать это, но если вы просто заинтересованы в том, чтобы избавиться от сообщения об ошибке, вы можете попробовать это обходное решение:
python your_python_code.py 2> /dev/null | other_command