Ошибка ввода-вывода: [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
по замыслу прекратить преждевременное чтение из канала, как только они получат достаточно данных.
- Это не обязательно условие ошибки; некоторые утилиты Unix, такие как
По умолчанию - т.е., если процесс записи явно не перехватывает
SIGPIPE
- процесс записи просто прекращается, и его код выхода устанавливается на141
, который рассчитывается как128
(сигнализировать об окончании сигналом в целом) +13
(SIGPIPE
специфический номер сигнала).По замыслу, однако, сам Python ловит
SIGPIPE
и переводит его в PythonIOError
экземпляр с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, который сделал это прекрасно для меня.