IOError: [Errno 32] Tubería rota: Python

88

Tengo un script Python 3 muy simple:

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

Pero siempre dice:

IOError: [Errno 32] Broken pipe

Vi en Internet todas las formas complicadas de solucionar esto, pero copié este código directamente, así que creo que hay algo mal con el código y no con el SIGPIPE de Python.

Estoy redirigiendo la salida, por lo que si el script anterior se llamara "open.py", entonces mi comando para ejecutar sería:

open.py | othercommand
JOHANNES_NYÅTT
fuente
@squiguy línea 2:print(f1.readlines())
JOHANNES_NYÅTT
2
Tiene dos operaciones de E / S que ocurren en la línea 2: una lectura desde a.txty una escritura hacia stdout. Quizás intente dividirlos en líneas separadas para que pueda ver qué operación desencadena la excepción. Si stdoutes una tubería y el extremo de lectura se ha cerrado, entonces eso podría explicar el EPIPEerror.
James Henstridge
1
Puedo reproducir este error en la salida (dadas las condiciones adecuadas), por lo que sospecho que la printllamada es la culpable. @ JOHANNES_NYÅTT, ¿puede aclarar cómo está iniciando su secuencia de comandos de Python? ¿Está redirigiendo la salida estándar a alguna parte?
Blckknght
2
Este es un posible duplicado de la siguiente pregunta: stackoverflow.com/questions/11423225/…

Respuestas:

45

No he reproducido el problema, pero tal vez este método lo resolvería: (escribiendo línea por línea en stdoutlugar de usar print)

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

¿Podrías atrapar la tubería rota? Esto escribe el archivo stdoutlínea por línea hasta que se cierra la tubería.

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

También debe asegurarse de othercommandleer desde la tubería antes de que sea demasiado grande: /unix/11946/how-big-is-the-pipe-buffer

Alex L
fuente
7
Si bien esta es una buena práctica de programación, no creo que tenga nada que ver con el error de tubería rota que está recibiendo el interrogador (que probablemente tenga que ver con la printllamada, no con la lectura de los archivos).
Blckknght
@Blckknght Agregué algunas preguntas y métodos alternativos y esperaba recibir comentarios del autor. Si el problema es enviar una gran cantidad de datos desde un archivo abierto directamente a la declaración de impresión, quizás una de las alternativas anteriores lo solucionaría.
Alex L
(Las soluciones más simples suelen ser las mejores, a menos que haya una razón particular para cargar un archivo completo y luego imprimirlo, hacerlo de una manera diferente)
Alex L
1
¡Impresionante trabajo para solucionar este problema! Si bien podría dar por sentada esta respuesta, pude apreciar esto solo después de ver cómo las otras respuestas (y mi propio enfoque) palidecían en comparación con su respuesta.
Jesvin Jose
117

El problema se debe al manejo de SIGPIPE. Puede resolver este problema utilizando el siguiente código:

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

Consulte aquí los antecedentes de esta solución. Mejor respuesta aquí .

Akhan
fuente
13
Esto es muy peligroso, como acabo de descubrir, porque si alguna vez obtiene un SIGPIPE en un socket (httplib o lo que sea), su programa simplemente se cerrará sin advertencia ni error.
David Bennett
1
@DavidBennett, estoy seguro de que su aplicación depende y para sus propósitos la respuesta aceptada es la correcta. Hay una gran Q y A a fondo aquí que la gente vaya a través y tomar una decisión informada. En mi opinión, para las herramientas de línea de comandos, probablemente sea mejor ignorar la señal de tubería en la mayoría de los casos.
Akhan
1
¿Alguna forma de hacer esto solo temporalmente?
Nate Glenn
2
@NateGlenn Puede guardar el controlador existente y restaurarlo más tarde.
Akhan
3
¿Podría alguien responderme por qué la gente está considerando un artículo de blogspot como una mejor fuente de verdad que la documentación oficial (pista: abra el enlace para ver cómo corregir correctamente el error de tubería rota)? :)
Yurii Rabeshko
92

Para traer la respuesta de Alex L. útil , respuesta útil de Akhan y respuesta útil de Blckknght junto con alguna información adicional:

  • La señal Unix estándarSIGPIPE se envía a un proceso que escribe en una tubería cuando ya no hay un proceso que lea desde la tubería.

    • Ésta no es necesariamente una condición de error ; Algunas utilidades de Unix, como head por ejemplo , dejan de leer prematuramente desde una tubería, una vez que han recibido suficientes datos.
  • De forma predeterminada , es decir, si el proceso de escritura no intercepta explícitamente SIGPIPE, el proceso de escritura simplemente se termina y su código de salida se establece en141 , que se calcula como 128(para señalar la terminación por señal en general) + 13( número deSIGPIPE señal específico ) .

  • Sin embargo, por diseño, Python lo atrapaSIGPIPE y lo traduce a unaIOError instancia de Python con errnovalor errno.EPIPE, de modo que una secuencia de comandos de Python pueda atraparlo, si así lo desea; consulte la respuesta de Alex L. para saber cómo hacerlo.

  • Si un script de Python no lo detecta , Python genera un mensaje de errorIOError: [Errno 32] Broken pipe y termina el script con un código de salida1 ; este es el síntoma que vio el OP.

  • En muchos casos, esto es más perturbador que útil , por lo que es deseable volver al comportamiento predeterminado :

    • El uso del signalmódulo permite precisamente eso, como se indica en la respuesta de Akhan ; signal.signal()toma una señal para manejar como primer argumento y un manejador como segundo; El valor del controlador especial SIG_DFLrepresenta el comportamiento predeterminado del sistema :

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

Se produce un error de "Tubería rota" cuando intenta escribir en una tubería que se ha cerrado en el otro extremo. Dado que el código que ha mostrado no involucra ninguna tubería directamente, sospecho que está haciendo algo fuera de Python para redirigir la salida estándar del intérprete de Python a otro lugar. Esto podría suceder si está ejecutando un script como este:

python foo.py | someothercommand

El problema que tiene es que someothercommandsale sin leer todo lo disponible en su entrada estándar. Esto hace que su escritura (vía print) falle en algún momento.

Pude reproducir el error con el siguiente comando en un sistema Linux:

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

Si cierro el lessbuscapersonas sin desplazarme por todas sus entradas (1000 líneas), Python sale con lo mismo IOErrorque ha informado.

Blckknght
fuente
10
Sí, esto es cierto, pero ¿cómo lo soluciono?
JOHANNES_NYÅTT
2
por favor déjeme saber cómo solucionarlo.
JOHANNES_NYÅTT
1
@ JOHANNES_NYÅTT: Puede funcionar para archivos pequeños debido al almacenamiento en búfer proporcionado por las tuberías en la mayoría de los sistemas similares a Unix. Si puede escribir todo el contenido de los archivos en el búfer, no genera un error si el otro programa nunca lee esos datos. Sin embargo, si los bloques de escritura (porque el búfer está lleno), fallará cuando el otro programa se cierre. Para decirlo de nuevo: ¿Cuál es el otro comando? No podemos ayudarlo más con solo el código Python (ya que no es la parte la que está haciendo lo incorrecto).
Blckknght
1
Tengo este problema al conectar a la cabeza ... excepción después de diez líneas de salida. Bastante lógico, pero aún inesperado :)
André Laszlo
4
@Blckknght: Buena información en general, pero vuelva a " arreglar eso: y" la parte que está haciendo lo incorrecto ": una SIGPIPEseñal no indica necesariamente una condición de error ; algunas utilidades de Unix, en particular head, por diseño, durante el funcionamiento normal cierran la tubería temprano, una vez que se han leído todos los datos que se necesitan.
mklement0
20

Me siento obligado a señalar que el método que utiliza

signal(SIGPIPE, SIG_DFL) 

es de hecho peligroso (como ya sugirió David Bennet en los comentarios) y en mi caso condujo a negocios divertidos dependientes de la plataforma cuando se combinó con multiprocessing.Manager(porque la biblioteca estándar se basa en BrokenPipeError que se genera en varios lugares). Para resumir una historia larga y dolorosa, así es como lo arreglé:

Primero, debe capturar el IOError(Python 2) o BrokenPipeError(Python 3). Dependiendo de su programa, puede intentar salir temprano en ese momento o simplemente ignorar la excepción:

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

Sin embargo, esto no es suficiente. Python 3 aún puede imprimir un mensaje como este:

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

Desafortunadamente, deshacerse de ese mensaje no es sencillo, pero finalmente encontré http://bugs.python.org/issue11380 donde Robert Collins sugiere esta solución que convertí en un decorador con el que puedes envolver tu función principal (sí, eso sangría):

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
fuente
2
Esto no pareció arreglarlo para mí.
Kyle Bridenstine
Me funcionó después de que agregué, excepto BrokenPipeError: pasar la función supress_broken_pipe_msg
Rupen B
2

Sé que esta no es la forma "correcta" de hacerlo, pero si simplemente está interesado en deshacerse del mensaje de error, puede probar esta solución alternativa:

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

La respuesta principal ( if e.errno == errno.EPIPE:) aquí realmente no funcionó para mí. Tengo:

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

Sin embargo, esto debería funcionar si lo único que le importa es ignorar las tuberías rotas en escrituras específicas. Creo que es más seguro que atrapar SIGPIPE:

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

Obviamente, tienes que tomar una decisión sobre si tu código está realmente hecho si te encuentras con la tubería rota, pero para la mayoría de los propósitos, creo que eso suele ser cierto. (No olvide cerrar los identificadores de archivos, etc.)

JD Baldwin
fuente
1

Esto también puede ocurrir si el final de lectura de la salida de su script muere prematuramente

es decir, open.py | otherCommand

si sale otherCommand y open.py intenta escribir en stdout

Tenía un guión malísimo que me hizo esto de maravilla.

lkreinitz
fuente
2
No se trata necesariamente de que el proceso de lectura de la tubería muera , necesariamente: algunas utilidades de Unix, en particular head, por diseño, durante el funcionamiento normal cierran la tubería temprano, una vez que han leído todos los datos que necesitaban. La mayoría de las CLI simplemente ceden al sistema por su comportamiento predeterminado: terminar silenciosamente el proceso de lectura y reportar el código de salida 141(que, en un shell, no es evidente, porque el último comando de una canalización determina el código de salida general). El comportamiento predeterminado de Python , desafortunadamente, es morir ruidosamente .
mklement0
-1

Los cierres deben realizarse en orden inverso al de las aperturas.

Paul
fuente
4
Si bien esa es una buena práctica en general, no hacerlo no es un problema en sí mismo y no explica los síntomas del OP.
mklement0