IOError: [Errno 32] Uszkodzony potok: Python

88

Mam bardzo prosty skrypt w Pythonie 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()

Ale zawsze mówi:

IOError: [Errno 32] Broken pipe

Widziałem w Internecie wszystkie skomplikowane sposoby rozwiązania tego problemu, ale skopiowałem ten kod bezpośrednio, więc myślę, że coś jest nie tak z kodem, a nie SIGPIPE Pythona.

Przekierowuję dane wyjściowe, więc jeśli powyższy skrypt został nazwany „open.py”, moje polecenie do uruchomienia wyglądałoby tak:

open.py | othercommand
JOHANNES_NYÅTT
źródło
@squiguy wiersz 2:print(f1.readlines())
JOHANNES_NYÅTT
2
W linii 2 występują dwie operacje IO: odczyt a.txti zapis stdout. Być może spróbuj podzielić je na oddzielne wiersze, aby zobaczyć, która operacja wyzwala wyjątek. Jeśli stdoutjest to potok i koniec odczytu został zamknięty, może to wyjaśniać EPIPEbłąd.
James Henstridge
1
Mogę odtworzyć ten błąd na wyjściu (w odpowiednich warunkach), więc podejrzewam, że przyczyną printjest połączenie. @ JOHANNES_NYÅTT, czy możesz wyjaśnić, w jaki sposób uruchamiasz skrypt w Pythonie? Czy przekierowujesz gdzieś standardowe wyjście?
Blckknght
2
To jest możliwy duplikat następującego pytania: stackoverflow.com/questions/11423225/…

Odpowiedzi:

45

Nie odtworzyłem problemu, ale być może ta metoda go rozwiązała: (pisanie wiersz po wierszu stdoutzamiast używania print)

import sys
with open('a.txt', 'r') as f1:
    for line in f1:
        sys.stdout.write(line)

Możesz złapać pękniętą rurę? Spowoduje to zapisanie pliku stdoutwiersz po wierszu, aż potok zostanie zamknięty.

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

Musisz także upewnić się, że othercommandczyta z rury, zanim zrobi się za duża - /unix/11946/how-big-is-the-pipe-buffer

Alex L.
źródło
7
Chociaż jest to dobra praktyka programistyczna, nie sądzę, że ma to coś wspólnego z błędem zepsutego potoku, który otrzymuje pytający (co prawdopodobnie ma związek z printwywołaniem, a nie odczytem plików).
Blckknght
@Blckknght Dodałem kilka pytań i alternatywnych metod i liczyłem na opinie autora. Jeśli problem polega na wysyłaniu dużej ilości danych z otwartego pliku bezpośrednio do instrukcji print, być może rozwiązałaby go jedna z powyższych alternatyw.
Alex L
(Najprostsze rozwiązania są często najlepsze - chyba że istnieje szczególny powód, aby załadować cały plik, a następnie go wydrukować, zrób to w inny sposób)
Alex L
1
Świetna praca w rozwiązywaniu tego problemu! Chociaż mogłem przyjąć tę odpowiedź za pewnik, doceniłem to dopiero po zobaczeniu, jak inne odpowiedzi (i moje własne podejście) bledną w porównaniu z Twoją odpowiedzią.
Jesvin Jose
117

Problem jest spowodowany obsługą SIGPIPE. Możesz rozwiązać ten problem za pomocą następującego kodu:

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

Zobacz tutaj, aby zapoznać się z tłem tego rozwiązania. Lepiej odpowiedz tutaj .

akhan
źródło
13
Jest to bardzo niebezpieczne, jak właśnie odkryłem, ponieważ jeśli kiedykolwiek dostaniesz SIGPIPE na gnieździe (httplib lub cokolwiek innego), twój program po prostu zakończy działanie bez ostrzeżenia lub błędu.
David Bennett
1
@DavidBennett, jestem pewien, że jego aplikacja jest zależna i dla twoich celów przyjęta odpowiedź jest właściwa. Jest tu wiele szczegółowych pytań i odpowiedzi , przez które ludzie mogą przejść i podjąć świadomą decyzję. IMO, w przypadku narzędzi wiersza poleceń, w większości przypadków najlepiej jest ignorować sygnał potoku.
akhan
1
Czy można to zrobić tylko tymczasowo?
Nate Glenn
2
@NateGlenn Możesz zapisać istniejącą procedurę obsługi i przywrócić ją później.
akhan
3
Czy ktoś mógłby mi odpowiedzieć, dlaczego ludzie uważają artykuł w blogspocie za lepsze źródło prawdy niż oficjalna dokumentacja (wskazówka: otwórz link, aby zobaczyć, jak poprawnie naprawić błąd zepsutej rury)? :)
Yurii Rabeshko
92

Przynieść użytecznej odpowiedzi Alex L. za , użytecznej odpowiedzi akhan męska i użytecznej odpowiedzi Blckknght za razem z dodatkowymi informacjami:

  • Standardowy sygnał UnixSIGPIPE jest wysyłany do procesu zapisującego do potoku, gdy nie ma już odczytu procesu z potoku (już).

    • Nie jest to koniecznie stan błędu ; niektóre narzędzia uniksowe, takie jak head projekt, przestają przedwcześnie czytać z potoku, gdy otrzymają wystarczającą ilość danych.
  • Domyślnie - tj. Jeśli proces zapisu nie jest jawnie przechwytywany SIGPIPE - proces zapisu jest po prostu kończony , a jego kod zakończenia jest ustawiony na141 , co jest obliczane jako 128(ogólnie rzecz biorąc, aby zakończyć sygnał przez sygnał) + 13( SIGPIPEokreślony numer sygnału ) .

  • Jednak z założenia sam Python przechwytujeSIGPIPE i tłumaczy je naIOError instancję Pythona z errnowartością errno.EPIPE, dzięki czemu skrypt Pythona może ją przechwycić, jeśli tak zdecyduje - zobacz odpowiedź Alexa L., jak to zrobić.

  • Jeśli skrypt w Pythonie go nie wychwyci , Python wyświetla komunikat o błędzieIOError: [Errno 32] Broken pipe i kończy skrypt kodem zakończenia1 - to jest objaw, który zobaczył OP.

  • W wielu przypadkach jest to bardziej uciążliwe niż pomocne , więc powrót do domyślnego zachowania jest pożądany :

    • Korzystanie z signalmodułu pozwala właśnie na to, jak stwierdzono w odpowiedzi Achana ; signal.signal()przyjmuje sygnał do obsłużenia jako pierwszy argument i funkcję obsługi jako drugi; specjalna wartość obsługi SIG_DFLreprezentuje domyślne zachowanie systemu:

      from signal import signal, SIGPIPE, SIG_DFL
      signal(SIGPIPE, SIG_DFL) 
      
mklement0
źródło
32

Podczas próby zapisu do potoku, który został zamknięty na drugim końcu, pojawia się błąd „Zerwana rura”. Ponieważ kod, który pokazałeś, nie obejmuje bezpośrednio żadnych potoków, podejrzewam, że robisz coś poza Pythonem, aby przekierować standardowe wyjście interpretera Pythona do innego miejsca. Może się to zdarzyć, jeśli uruchamiasz taki skrypt:

python foo.py | someothercommand

Problem polega na tym, że someothercommandwychodzi się bez czytania wszystkiego, co jest dostępne na jego standardowym wejściu. To powoduje, że twój zapis (przezprint w pewnym momencie ) kończy się niepowodzeniem.

Udało mi się odtworzyć błąd za pomocą następującego polecenia w systemie Linux:

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

Jeśli zamknę lesspager bez przewijania wszystkich jego danych wejściowych (1000 linii), Python zakończy pracę z tym samym IOError, co zgłosiłeś.

Blckknght
źródło
10
Tak, to prawda, ale jak to naprawić?
JOHANNES_NYÅTT
2
daj mi znać, jak to naprawić.
JOHANNES_NYÅTT
1
@ JOHANNES_NYÅTT: Może działać dla małych plików ze względu na buforowanie zapewniane przez potoki w większości systemów uniksopodobnych. Jeśli możesz zapisać całą zawartość plików w buforze, nie zgłosi to błędu, jeśli inny program nigdy nie odczyta tych danych. Jeśli jednak zapis blokuje się (ponieważ bufor jest pełny), zakończy się niepowodzeniem, gdy inny program zakończy pracę. Powiedzieć jeszcze raz: jakie jest drugie polecenie? Nie możemy Ci już pomóc tylko z kodem Pythona (ponieważ nie jest to część, która robi źle).
Blckknght
1
Mam ten problem podczas orurowania do głowy ... wyjątek po dziesięciu liniach wyjścia. Całkiem logiczne, ale wciąż nieoczekiwane :)
André Laszlo
4
@Blckknght: Ogólnie dobre informacje, ale poprawka to:” i „część, która robi złą rzeczą»: a SIGPIPEsygnał nie musi oznaczać błędu warunku; niektóre narzędzia Unix, w szczególności head, przez projekt, podczas normalnej pracy blisko rura wcześnie, raz oni czytać jak najwięcej danych, jak to potrzebne.
mklement0
20

Czuję się zobowiązany zwrócić uwagę, że metoda przy użyciu

signal(SIGPIPE, SIG_DFL) 

jest rzeczywiście niebezpieczny (jak już zasugerował David Bennet w komentarzach) iw moim przypadku doprowadził do zabawnego biznesu zależnego od platformy w połączeniu z multiprocessing.Manager(ponieważ standardowa biblioteka polega na podnoszeniu BrokenPipeError w kilku miejscach). Krótko mówiąc, długą i bolesną historię naprawiłem w następujący sposób:

Najpierw musisz złapać IOError(Python 2) lub BrokenPipeError(Python 3). W zależności od programu możesz w tym momencie spróbować zakończyć pracę wcześniej lub po prostu zignorować wyjątek:

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

Jednak to nie wystarczy. Python 3 może nadal wyświetlać następujący komunikat:

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

Niestety pozbycie się tej wiadomości nie jest proste, ale w końcu znalazłem http://bugs.python.org/issue11380, gdzie Robert Collins sugeruje to obejście, które zmieniłem w dekorator, którym możesz zawinąć swoją główną funkcję (tak, to trochę szalone wcięcie):

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
źródło
2
To chyba nie rozwiązało problemu.
Kyle Bridenstine
Zadziałało dla mnie po dodaniu z wyjątkiem BrokenPipeError: pass w funkcji supress_broken_pipe_msg
Rupen B
2

Wiem, że to nie jest „właściwy” sposób, aby to zrobić, ale jeśli chcesz po prostu pozbyć się komunikatu o błędzie, możesz wypróbować to obejście:

python your_python_code.py 2> /dev/null | other_command
ssanch
źródło
2

Najwyższa odpowiedź ( if e.errno == errno.EPIPE:) tutaj tak naprawdę nie działa dla mnie. Mam:

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

Powinno to jednak zadziałać, jeśli wszystko, na czym ci zależy, to ignorowanie pękniętych rur w określonych zapisach. Myślę, że to bezpieczniejsze niż złapanie SIGPIPE:

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

Oczywiście musisz podjąć decyzję, czy twój kod jest naprawdę, naprawdę zrobiony, jeśli trafisz na zepsutą potokę, ale w większości przypadków myślę, że zwykle będzie to prawda. (Nie zapomnij zamknąć uchwytów plików itp.)

JD Baldwin
źródło
1

Może to również wystąpić, jeśli koniec odczytu danych wyjściowych skryptu umiera przedwcześnie

czyli open.py | otherCommand

jeśli otherCommand kończy działanie i open.py próbuje pisać na standardowe wyjście

Miałem zły scenariusz gawk, który zrobił mi to cudownie.

lkreinitz
źródło
2
Tu nie chodzi o proces czytania z rury umiera , koniecznie: niektóre narzędzia Unix, zwłaszcza head, przez projekt, podczas normalnej pracy blisko rura wcześnie, raz oni czytać jak najwięcej danych, jak to potrzebne. Większość CLI po prostu odracza system ze względu na jego domyślne zachowanie: ciche zakończenie procesu odczytu i zgłoszenie kodu zakończenia 141(co w powłoce nie jest łatwo widoczne, ponieważ ostatnie polecenie potoku określa ogólny kod zakończenia). Niestety, domyślnym zachowaniem Pythona jest głośna śmierć .
mklement0
-1

Zamknięcia należy wykonywać w odwrotnej kolejności do otwierania.

Paweł
źródło
4
Chociaż jest to ogólnie dobra praktyka, brak działania sam w sobie nie stanowi problemu i nie wyjaśnia objawów PO.
mklement0