Ошибка ввода-вывода: [Errno 32] Сломанный канал: Python

У меня очень простой скрипт на 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

Я видел в интернете все сложные способы исправить это, но я скопировал этот код напрямую, так что я думаю, что с кодом что-то не так, а не SIGPIPE Python.

Я перенаправляю вывод, поэтому если приведенный выше скрипт был назван "open.py", то моя команда для запуска будет выглядеть так:

open.py | othercommand

10 ответов

Решение

Я не воспроизвел проблему, но, возможно, этот метод решит ее (пишу построчно 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

Проблема связана с обработкой SIGPIPE. Вы можете решить эту проблему, используя следующий код:

from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL) 

Смотрите здесь для справки об этом решении. Лучше ответ здесь.

Чтобы принести полезный ответ Алекса Л., полезный ответ Ахана и полезный ответ Блекнхта вместе с некоторой дополнительной информацией:

  • Стандартный сигнал Unix SIGPIPE отправляется процессу, пишущему в канал, когда нет чтения процесса из канала (больше).

    • Это не обязательно условие ошибки; некоторые утилиты Unix, такие как head по замыслу прекратить преждевременное чтение из канала, как только они получат достаточно данных.
  • По умолчанию - т.е., если процесс записи явно не перехватывает SIGPIPE - процесс записи просто прекращается, и его код выхода устанавливается на 141, который рассчитывается как 128 (сигнализировать об окончании сигналом в целом) + 13 (SIGPIPE специфический номер сигнала).

  • По замыслу, однако, сам Python ловит SIGPIPE и переводит его в Python IOError экземпляр с errno значение errno.EPIPE, чтобы скрипт Python мог его перехватить, если он того пожелает - см . ответ Алекса Л., как это сделать.

  • Если скрипт Python не ловит его, Python выводит сообщение об ошибке IOError: [Errno 32] Broken pipe и завершает скрипт с кодом выхода 1 - это симптом, который видел ОП.

  • Во многих случаях это более разрушительно, чем полезно, поэтому желательно вернуться к поведению по умолчанию:

    • С использованием signal модуль позволяет именно это, как указано в ответе ахана; signal.signal() принимает сигнал для обработки в качестве 1-го аргумента и обработчик в качестве 2-го; специальное значение обработчика SIG_DFL представляет поведение системы по умолчанию:

      from signal import signal, SIGPIPE, SIG_DFL
      signal(SIGPIPE, SIG_DFL) 
      

Ошибка "Broken Pipe" возникает при попытке записи в канал, который был закрыт на другом конце. Поскольку код, который вы показали, не включает никаких каналов напрямую, я подозреваю, что вы делаете что-то вне Python, чтобы перенаправить стандартный вывод интерпретатора Python куда-то еще. Это может произойти, если вы запускаете такой скрипт:

python foo.py | someothercommand

Проблема в том, что someothercommand выходит без чтения всего, что доступно на его стандартном вводе. Это вызывает вашу запись (через print) потерпеть неудачу в какой-то момент.

Мне удалось воспроизвести ошибку с помощью следующей команды в системе Linux:

python -c 'for i in range(1000): print i' | less

Если я закрою less пейджер без прокрутки всего его ввода (1000 строк), Python выходит с тем же IOError Вы сообщили.

Я чувствую себя обязанным указать, что метод, использующий

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

Я знаю, что это не "правильный" способ сделать это, но если вы просто заинтересованы в том, чтобы избавиться от сообщения об ошибке, вы можете попробовать этот обходной путь:

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 )

Вы, очевидно, должны принять решение относительно того, действительно ли ваш код выполнен, если вы столкнетесь с поломкой трубы, но для большинства целей я думаю, что обычно это правда. (Не забудьте закрыть дескрипторы файлов и т. Д.)

В зависимости от точной причины проблемы может помочь установка переменной среды 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
$ 

Это также может произойти, если конец чтения вывода вашего сценария преждевременно умирает

то есть open.py | otherCommand

если otherCommand завершается и open.py пытается записать в stdout

У меня был плохой сценарий gawk, который сделал это прекрасно для меня.

Закрытие должно быть сделано в обратном порядке открытий.

Другие вопросы по тегам