IOError: [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, питона.

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

open.py | othercommand
7 61

7 ответов:

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

посмотреть здесь на фоне этого решения. Лучше ответь здесь.

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

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

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

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

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

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

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

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

ошибка "сломанная труба" возникает при попытке записи в трубу, которая была закрыта на другом конце. Поскольку код, который вы показали, не включает в себя никаких каналов напрямую, я подозреваю, что вы делаете что-то за пределами 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

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

ie open.py / otherCommand

Если другая команда выходит и open.py пытается написать в stdout

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

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