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

@squiguy рядок 2:print(f1.readlines())
JOHANNES_NYÅTT

2
У вас є дві операції введення-виведення, що відбуваються в рядку 2: читання з a.txtі запис у stdout. Можливо, спробуйте розділити їх на окремі рядки, щоб побачити, яка операція викликає виняток. Якщо stdoutце труба, а кінець зчитування закритий, то це може спричинити EPIPEпомилку.
James Henstridge

1
Я можу відтворити цю помилку на виході (з урахуванням правильних умов), тому я підозрюю print, що виною є виклик. @ JOHANNES_NYÅTT, ти можеш пояснити, як ти запускаєш свій сценарій Python? Ви перенаправляєте кудись стандартний вивід?
Blckknght

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


7
Хоча це хороша практика програмування, я не думаю, що це має щось спільне з помилкою зламаної труби, яку отримує запитувач (що, ймовірно, пов’язане із printдзвінком, а не з читанням файлів).
Blckknght

@Blckknght Я додав кілька запитань та альтернативні методи і сподівався на якийсь відгук від автора. Якщо проблема полягає в надсиланні великої кількості даних із відкритого файлу безпосередньо в оператор друку, можливо, одна з альтернатив, наведених вище, це виправила б.
Alex L

(Найпростіші рішення часто є найкращими - якщо немає особливої ​​причини завантажити цілий файл, а потім надрукувати його, зробити це по-іншому)
Alex L

1
Чудова робота з усунення несправностей! Хоча я міг сприймати цю відповідь як належне, я міг оцінити це лише після того, як побачив, як інші відповіді (і мій власний підхід) зблідли порівняно з вашою відповіддю.
Jesvin Jose

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

3
Хтось міг відповісти мені, чому люди розглядають статтю blogspot як краще джерело істини, ніж офіційна документація (підказка: відкрийте посилання, щоб дізнатись, як правильно виправити помилку, що поламалася) :)
Юрій Рабешко

92

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

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

    • Це не обов'язково умова помилки ; деякі утиліти Unix, такі як head за проектом, припиняють передчасно читати з труби, як тільки отримають достатньо даних.
  • За замовчуванням - тобто, якщо процес запису явно не затримується SIGPIPE - процес запису просто припиняється , і для його вихідного коду встановлюється значення141 , яке обчислюється як 128(для сигналізації припинення сигналом загалом) + 13( SIGPIPEконкретний номер сигналу ) .

  • Однак за задумом сам Python захоплюєSIGPIPE та перетворює його наIOError екземпляр Python зі 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) 
      

32

Помилка "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ви повідомляли.


10
Так, це правда, але як це виправити?
JOHANNES_NYÅTT

2
будь ласка, дайте мені знати, як це виправити.
JOHANNES_NYÅTT

1
@ JOHANNES_NYÅTT: Це може працювати для невеликих файлів через буферизацію, яку забезпечують канали в більшості Unix-подібних систем. Якщо ви можете записати весь вміст файлів у буфер, це не спричинить помилки, якщо інша програма ніколи не зчитує ці дані. Однак, якщо блоки запису (оскільки буфер заповнений), він не вдасться, коли вийде інша програма. Ще раз сказати: Яка інша команда? Ми більше не можемо вам допомогти лише з кодом Python (оскільки це не та частина, яка робить неправильну справу).
Blckknght

1
Я отримав цю проблему під час трубопроводу до голови ... виняток через десять рядків виводу. Цілком логічно, але все ж несподівано :)
Андре Ласло

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

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 виходить і open.py намагається написати в stdout

У мене був поганий сценарій gawk, який зробив це прекрасне для мене.


2
Мова не йде про зчитування процесу з труби, яка вмирає : деякі утиліти Unix, зокрема head, за задумом, під час нормальної роботи закривають трубу достроково, як тільки прочитають стільки даних, скільки їм потрібно. Більшість інтерфейсів командного інтерфейсу просто відкладають на систему за поведінку за замовчуванням: тихо припиняє процес читання та повідомляє код виходу 141(що, в оболонці, не очевидно, оскільки остання команда конвеєра визначає загальний код виходу). Типова поведінка Python , на жаль, полягає в тому, щоб померти шумно .
mklement0

-1

Закриття слід робити в зворотному порядку відкриття.


4
Хоча це загалом хороша практика, відмова сама по собі не є проблемою і не пояснює симптоми ОП.
mklement0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.