IOError: [Errno 32] Tubo quebrado: Python

91

Eu tenho um script Python 3 muito simples:

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()

Mas sempre diz:

IOError: [Errno 32] Broken pipe

Eu vi na internet todas as maneiras complicadas de consertar isso, mas copiei esse código diretamente, então acho que há algo errado com o código e não com o SIGPIPE do Python.

Estou redirecionando a saída, portanto, se o script acima foi denominado "open.py", meu comando para executar seria:

open.py | othercommand
JOHANNES_NYÅTT
fonte
@squiguy linha 2:print(f1.readlines())
JOHANNES_NYÅTT
2
Você tem duas operações IO ocorrendo na linha 2: uma leitura de a.txte uma gravação para stdout. Talvez tente dividi-los em linhas separadas para ver qual operação dispara a exceção. Se stdoutfor um tubo e a extremidade de leitura tiver sido fechada, isso pode explicar o EPIPEerro.
James Henstridge
1
Posso reproduzir esse erro na saída (dadas as condições certas), então suspeito que a printchamada seja a culpada. @ JOHANNES_NYÅTT, você pode esclarecer como está iniciando seu script Python? Você está redirecionando a saída padrão para algum lugar?
Blckknght
2
Esta é uma possível duplicata da seguinte pergunta: stackoverflow.com/questions/11423225/…

Respostas:

48

Não reproduzi o problema, mas talvez este método resolva: (escrever linha por linha em stdoutvez de usar print)

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

Você poderia pegar o cano quebrado? Isso grava o arquivo stdoutlinha por linha até que o tubo seja fechado.

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

Você também precisa ter certeza de que othercommandestá lendo o tubo antes que fique muito grande - /unix/11946/how-big-is-the-pipe-buffer

Alex L
fonte
7
Embora essa seja uma boa prática de programação, não acho que tenha algo a ver com o erro de tubo quebrado que o questionador está recebendo (que provavelmente tem a ver com a printchamada, não com a leitura dos arquivos).
Blckknght
@Blckknght Eu adicionei algumas perguntas e métodos alternativos e esperava algum feedback do autor. Se o problema for enviar uma grande quantidade de dados de um arquivo aberto diretamente para a instrução de impressão, talvez uma das alternativas acima resolva isso.
Alex L
(As soluções mais simples são geralmente as melhores - a menos que haja um motivo específico para carregar um arquivo inteiro e, em seguida, imprimi-lo, faça-o de uma maneira diferente)
Alex L
1
Excelente trabalho para solucionar isso! Embora eu pudesse tomar essa resposta como certa, só pude apreciar isso depois de ver como as outras respostas (e minha própria abordagem) empalideceram em comparação com a sua.
Jesvin Jose
117

O problema é devido ao manuseio do SIGPIPE. Você pode resolver esse problema usando o seguinte código:

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

Veja aqui o histórico desta solução. Melhor resposta aqui .

Akhan
fonte
14
Isso é muito perigoso, como acabei de descobrir, porque se você obtiver um SIGPIPE em um socket (httplib ou qualquer outro), seu programa simplesmente sairá sem aviso ou erro.
David Bennett
1
@DavidBennett, tenho certeza de que depende da aplicação e, para seus propósitos, a resposta aceita é a correta. Há uma sessão de perguntas e respostas muito completa aqui para as pessoas analisarem e tomarem uma decisão informada. IMO, para ferramentas de linha de comando, é provavelmente melhor ignorar o sinal do pipe na maioria dos casos.
Akhan de
1
Alguma maneira de fazer isso apenas temporariamente?
Nate Glenn de
2
@NateGlenn Você pode salvar o manipulador existente e restaurá-lo posteriormente.
Akhan
3
Alguém poderia me responder por que as pessoas estão considerando um artigo do blogspot como uma fonte melhor da verdade do que a documentação oficial (dica: abra o link para ver como consertar o erro do tubo quebrado corretamente)? :)
Yurii Rabeshko
93

Para trazer a resposta útil de Alex L. , a resposta útil de akhan e a resposta útil de Blckknght junto com algumas informações adicionais:

  • O sinal Unix padrãoSIGPIPE é enviado para um processo de gravação em um pipe quando não há mais leitura de processo no pipe (mais).

    • Isso não é necessariamente uma condição de erro ; alguns utilitários Unix, head por exemplo, param de ler prematuramente de um tubo, uma vez que recebem dados suficientes.
  • Por padrão - ou seja, se o processo de gravação não capturar explicitamente SIGPIPE- o processo de gravação é simplesmente encerrado e seu código de saída é definido como141 , que é calculado como 128(para sinalizar o encerramento por sinal em geral) + 13( número deSIGPIPE sinal específico de ) .

  • Por design, no entanto, o próprio Python o capturaSIGPIPE e traduz em umaIOError instância do Python com errnovalor errno.EPIPE, de forma que um script Python possa capturá-lo, se assim o desejar - veja a resposta de Alex L. para saber como fazer isso.

  • Se um Python roteiro se não pegá-lo , Python mensagem de erro saídasIOError: [Errno 32] Broken pipe e termina o script com o código de saída1 - este é o sintoma da serra OP.

  • Em muitos casos, isso é mais perturbador do que útil , portanto, reverter para o comportamento padrão é desejável :

    • Usar o signalmódulo permite exatamente isso, conforme declarado na resposta de akhan ; signal.signal()recebe um sinal para manipular como o primeiro argumento e um manipulador como o segundo; o valor do manipulador especial SIG_DFLrepresenta o comportamento padrão do sistema :

      from signal import signal, SIGPIPE, SIG_DFL
      signal(SIGPIPE, SIG_DFL) 
      
mklement0
fonte
32

Um erro "Tubo quebrado" ocorre quando você tenta gravar em um tubo que foi fechado na outra extremidade. Como o código que você mostrou não envolve nenhum canal diretamente, suspeito que você esteja fazendo algo fora do Python para redirecionar a saída padrão do interpretador Python para outro lugar. Isso pode acontecer se você estiver executando um script como este:

python foo.py | someothercommand

O problema que você tem é someothercommandsair sem ler tudo o que está disponível em sua entrada padrão. Isso faz com que sua gravação (via print) falhe em algum ponto.

Consegui reproduzir o erro com o seguinte comando em um sistema Linux:

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

Se eu fechar o lesspager sem rolar por todas as suas entradas (1000 linhas), o Python sai com o mesmo que IOErrorvocê relatou.

Blckknght
fonte
10
Sim, isso é verdade, mas como faço para corrigir isso?
JOHANNES_NYÅTT
2
por favor, deixe-me saber como corrigi-lo.
JOHANNES_NYÅTT
1
@ JOHANNES_NYÅTT: Pode funcionar para arquivos pequenos por causa do buffer fornecido por pipes na maioria dos sistemas do tipo Unix. Se você puder gravar todo o conteúdo dos arquivos no buffer, não haverá erro se o outro programa nunca ler esses dados. No entanto, se a gravação bloquear (porque o buffer está cheio), ele falhará quando o outro programa for encerrado. Para dizer novamente: qual é o outro comando? Não podemos ajudá-lo mais apenas com o código Python (já que não é a parte que está fazendo a coisa errada).
Blckknght
2
Eu tenho esse problema ao canalizar para a cabeça ... exceção após dez linhas de saída. Bastante lógico, mas ainda assim inesperado :)
André Laszlo
4
@Blckknght: Boas informações em geral, mas " conserte isso: e" a parte que está fazendo a coisa errada ": um SIGPIPEsinal não indica necessariamente uma condição de erro ; alguns utilitários Unix, notavelmente head, por design, durante a operação normal fecham o tubo cedo, uma vez que eles já leu tanto de dados como eles necessário.
mklement0
21

Sinto-me na obrigação de salientar que o método que utiliza

signal(SIGPIPE, SIG_DFL) 

é realmente perigoso (como já sugerido por David Bennet nos comentários) e, no meu caso, levou a negócios engraçados dependentes da plataforma quando combinado commultiprocessing.Manager (porque a biblioteca padrão depende de BrokenPipeError sendo criado em vários lugares). Para encurtar uma longa e dolorosa história, é assim que eu consertei:

Primeiro, você precisa capturar o IOError(Python 2) ou BrokenPipeError(Python 3). Dependendo do seu programa, você pode tentar sair mais cedo nesse ponto ou simplesmente ignorar a exceção:

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

No entanto, isso não é suficiente. O Python 3 ainda pode imprimir uma mensagem como esta:

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

Infelizmente, livrar-se dessa mensagem não é simples, mas finalmente encontrei http://bugs.python.org/issue11380 onde Robert Collins sugere esta solução alternativa que transformei em um decorador com a qual você pode embrulhar sua função principal (sim, isso é uma loucura recuo):

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
fonte
2
Isso não pareceu consertar para mim.
Kyle Bridenstine
Funcionou para mim depois de adicionar, exceto BrokenPipeError: passagem na função supress_broken_pipe_msg
Rupen B
2

Sei que essa não é a maneira "correta" de fazer isso, mas se você estiver simplesmente interessado em se livrar da mensagem de erro, pode tentar esta solução alternativa:

python your_python_code.py 2> /dev/null | other_command
ssanch
fonte
2

A primeira resposta ( if e.errno == errno.EPIPE:) aqui realmente não funcionou para mim. Eu tenho:

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

No entanto, isso deve funcionar se você só se preocupa em ignorar tubos quebrados em gravações específicas. Acho que é mais seguro do que capturar SIGPIPE:

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

Obviamente, você tem que tomar uma decisão sobre se o seu código está realmente pronto se você acertar o tubo quebrado, mas para a maioria dos propósitos, acho que isso geralmente será verdade. (Não se esqueça de fechar os identificadores de arquivo, etc.)

JD Baldwin
fonte
1

Isso também pode ocorrer se o final de leitura da saída de seu script morrer prematuramente

ou seja, open.py | otherCommand

se otherCommand sai e open.py tenta escrever para stdout

Eu tinha um script gawk ruim que me fez isso adorável.

lkreinitz
fonte
2
Não se trata de ler o processo do tubo de morrer , necessariamente: alguns utilitários Unix, nomeadamente head, pelo projeto, durante a operação normal perto do tubo cedo, uma vez que li o máximo de dados que necessário. A maioria das CLIs simplesmente adia o sistema por seu comportamento padrão: encerrar silenciosamente o processo de leitura e relatar o código de saída 141(o que, em um shell, não é imediatamente aparente, porque o último comando de um pipeline determina o código de saída geral). O comportamento padrão do Python , infelizmente, é morrer ruidosamente .
mklement0
-2

Os fechamentos devem ser feitos na ordem inversa das aberturas.

Paulo
fonte
4
Embora essa seja uma boa prática em geral, não fazer não é um problema em si e não explica os sintomas do OP.
mklement0