Подтвердить что ты не робот

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
4b9b3361

Ответ 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