IOError: [Errno 32] Сломанный канал: Python

88

У меня очень простой скрипт 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
JOHANNES_NYÅTT
источник
@squiguy, строка 2:print(f1.readlines())
JOHANNES_NYÅTT
2
У вас есть две операции ввода-вывода, выполняемые в строке 2: чтение из a.txtи запись в stdout. Возможно, попробуйте разделить их на отдельные строки, чтобы увидеть, какая операция вызывает исключение. Если stdoutэто канал и конец чтения был закрыт, это может объяснить EPIPEошибку.
Джеймс Хенстридж
1
Я могу воспроизвести эту ошибку на выходе (при правильных условиях), поэтому подозреваю, что виноват printвызов. @ JOHANNES_NYÅTT, не могли бы вы пояснить, как вы запускаете свой скрипт Python? Вы куда-то перенаправляете стандартный вывод?
Blckknght 08
2
Это возможный дубликат следующего вопроса: stackoverflow.com/questions/11423225/…

Ответы:

45

Я не воспроизводил проблему, но, возможно, этот метод решит ее: (запись строка за строкой 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он читает из канала, прежде чем он станет слишком большим - /unix/11946/how-big-is-the-pipe-buffer

Alex L
источник
7
Хотя это хорошая практика программирования, я не думаю, что это имеет какое-либо отношение к ошибке сломанной трубы, которую получает спрашивающий (что, вероятно, связано с printвызовом, а не с чтением файлов).
Blckknght 08
@Blckknght Я добавил несколько вопросов и альтернативных методов и надеялся на обратную связь от автора. Если проблема заключается в отправке большого количества данных из открытого файла непосредственно в оператор печати, возможно, одна из вышеперечисленных альтернатив решит ее.
Alex L
(Самые простые решения часто оказываются лучшими - если нет особой причины загрузить весь файл, а затем распечатать его, сделайте это по-другому)
Alex L
1
Отличная работа по устранению неполадок! Хотя я мог принять этот ответ как должное, я смог оценить это только после того, как увидел, как другие ответы (и мой собственный подход) бледнеют по сравнению с вашим ответом.
Jesvin Jose 07
117

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

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

См. Здесь информацию об этом решении. Лучше ответь здесь .

Ахан
источник
13
Как я только что обнаружил, это очень опасно, потому что если вы когда-нибудь получите SIGPIPE на сокете (httplib или что-то еще), ваша программа просто выйдет без предупреждения или ошибки.
Дэвид Беннетт
1
@DavidBennett, я уверен, что это зависит от приложения, и для ваших целей принятый ответ является правильным. Существует гораздо более тщательный Q & здесь для людей , чтобы пройти и принять обоснованное решение. IMO, для инструментов командной строки, вероятно, в большинстве случаев лучше игнорировать сигнал канала.
Ахан
1
Есть ли способ сделать это временно?
Nate Glenn
2
@NateGlenn Вы можете сохранить существующий обработчик и восстановить его позже.
akhan 07
3
Может ли кто-нибудь ответить мне, почему люди считают статью о блоге лучшим источником истины, чем официальная документация (подсказка: откройте ссылку, чтобы узнать, как правильно исправить ошибку сломанной трубы)? :)
Юрий Рабешко
92

Для того, чтобы принести полезный ответ Алекс Лоэнгрина , полезный ответ Ахан игровая и полезный ответ Blckknght в вместе с некоторой дополнительной информацией:

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

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

  • Однако по своей задумке Python сам улавливаетSIGPIPE и преобразует его вIOError экземпляр Python со errnoзначением errno.EPIPE, так что скрипт Python может поймать его, если он того пожелает - см . Ответ Alex L. о том, как это сделать.

  • Если 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) 
      
mklement0
источник
32

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

python foo.py | someothercommand

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

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

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

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

Blckknght
источник
10
Да, это правда, но как мне это исправить?
JOHANNES_NYÅTT
2
дайте мне знать, как это исправить.
JOHANNES_NYÅTT
1
@ JOHANNES_NYÅTT: Это может работать для небольших файлов из-за буферизации, обеспечиваемой конвейерами в большинстве Unix-подобных систем. Если вы можете записать все содержимое файлов в буфер, это не вызовет ошибки, если другая программа никогда не прочитает эти данные. Однако, если запись блокируется (из-за того, что буфер заполнен), она завершится ошибкой, когда другая программа завершится. Повторюсь: что это за другая команда? Мы больше не можем вам помочь, используя только код Python (поскольку это не та часть, которая делает неправильные вещи).
Blckknght
1
У меня возникла эта проблема при подключении к голове ... исключение после десяти строк вывода. Вполне логично, но все же неожиданно :)
André Laszlo
4
@Blckknght: Хорошая информация в целом, но вновь " исправить , что: а„часть , которая делает неправильно вещь“: а SIGPIPEсигнал не обязательно указывает на ошибки условие, некоторые Unix утилиты, в частности head, в соответствии с проектом, во время нормальной работы вблизи трубы рано, как только они прочитают столько данных, сколько им нужно.
mklement0
20

Я считаю своим долгом отметить, что метод, использующий

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
Trehn
источник
2
Похоже, это не помогло мне.
Кайл Брайденстайн
У меня это сработало после того, как я добавил, кроме BrokenPipeError: передать функцию supress_broken_pipe_msg
Rupen B
2

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

python your_python_code.py 2> /dev/null | other_command
ссанч
источник
2

Главный ответ ( if e.errno == errno.EPIPE:) здесь не сработал для меня. Я получил:

AttributeError: 'BrokenPipeError' object has no attribute 'EPIPE'

Однако это должно работать, если все, что вас волнует, - это игнорирование сломанных каналов при определенных операциях записи. Думаю, это безопаснее, чем перехватить SIGPIPE:

try:
    # writing, flushing, whatever goes here
except BrokenPipeError:
    exit( 0 )

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

Джей Ди Болдуин
источник
1

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

т.е. open.py | otherCommand

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

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

lkreinitz
источник
2
Дело не в том, что процесс чтения из конвейера обязательно умирает : некоторые утилиты Unix, в частности head, по дизайну, во время нормальной работы закрывают конвейер раньше, как только они прочитали столько данных, сколько им нужно. Большинство интерфейсов командной строки просто подчиняются системе за ее поведение по умолчанию: незаметное завершение процесса чтения и сообщение кода выхода 141(который в оболочке не всегда очевиден, потому что последняя команда конвейера определяет общий код выхода). К сожалению, поведение Python по умолчанию - шумно умирать .
mklement0 07
-1

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

Павел
источник
4
Хотя в целом это хорошая практика, бездействие само по себе не является проблемой и не объясняет симптомы ОП.
mklement0