118 votes

IOError : [Errno 32] Broken pipe when piping : `prog.py | othercmd`

J'ai un Python 3 très simple script :

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

Mais ça dit toujours :

IOError: [Errno 32] Broken pipe

J'ai vu sur Internet toutes les façons compliquées de résoudre ce problème, mais j'ai copié ce code directement, donc je pense qu'il y a quelque chose qui ne va pas avec le code et non avec le SIGPIPE de Python.

Je redirige la sortie, donc si le script ci-dessus était nommé "open.py", alors ma commande à exécuter serait :

open.py | othercommand

126voto

akhan Points 677

Le problème est dû à la gestion de SIGPIPE. Vous pouvez résoudre ce problème en utilisant le code suivant :

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

Mise à jour : Comme indiqué dans les commentaires, documentation python ont déjà une bonne réponse.

Voir ici pour le contexte de cette solution. Meilleure réponse aquí .

122voto

mklement0 Points 12597

Pour rassembler les informations des nombreuses réponses utiles, avec quelques informations supplémentaires :

  • Signal standard Unix SIGPIPE est envoyé à un processus écrire à un tuyau quand il n'y a pas de processus lecture du tuyau (plus).

    • Il ne s'agit pas nécessairement d'un erreur certains utilitaires Unix tels que head par le design arrêter la lecture prématurée d'un tube, une fois qu'ils ont reçu suffisamment de données.
    • Par conséquent, un moyen facile de provoquer cette erreur est de passer par le pipe à head [1] ; par exemple :
      • python -c 'for x in range(10000): print(x)' | head -n 1
  • Par défaut - c'est-à-dire, si le processus d'écriture n'est pas explicitement piège SIGPIPE - le processus d'écriture est simplement terminé et son code de sortie est fixé à 141 qui est calculé comme suit 128 (à la terminaison du signal par le signal en général) + 13 ( SIGPIPE Le signal spécifique de l'entreprise numéro ).

  • Cependant à dessein Python lui-même pièges SIGPIPE y le traduit en un langage Python BrokenPipeError (Python 3) / IOError (Python 2) instance avec errno valor errno.EPIPE .

    • Note : Si vous utilisez un environnement d'émulation Unix sur Windows l'erreur peut apparaître différemment - voir cette réponse.
  • Si un Python script fait no attraper l'exception , Python message d'erreur en sortie BrokenPipeError: [Errno 32] Broken pipe ( Python 3 éventuellement deux fois avec Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> pris en sandwich entre les deux) / IOError: [Errno 32] Broken pipe ( Python 2 ) et termine le script avec le code de sortie 1 [2] - c'est le symptôme que Johannes (le PO) a vu.

Windows considérations ( SIGPIPE est un signal réservé à Unix)

  • Si votre script doit aussi s'exécuter directement sous Windows, vous devrez peut-être contourner de manière conditionnelle le code qui fait référence à SIGPIPE comme indiqué dans cette réponse .

  • Si votre script s'exécute dans un fichier Sous-système Unix sous Windows, le SIGPIPE le signal peut faire surface différemment que sous Unix - voir cette réponse .


Il existe deux façons de résoudre ce problème :

En général, c'est no Il est conseillé de silence cette exception car il peut signaler une condition d'erreur grave, selon l'objectif de votre script, comme la fermeture inattendue de l'extrémité de réception d'une socket réseau.

  • Cependant, si votre script est un utilitaire de ligne de commande où la résiliation silencieuse peut non seulement être acceptable mais aussi préféré afin de s'harmoniser avec la norme head par exemple, vous pouvez abandonner tranquillement comme suit, en utilisant signal.signal() pour installer le logiciel de la plateforme gestionnaire de signaux par défaut (qui se comporte comme décrit ci-dessus), comme le montre également l'exemple suivant Réponse d'akhan (fonctionne à la fois dans Python 3 et 2) :

    ONLY SUITABLE FOR COMMAND-LINE UTILITIES

    Install the default signal handler.

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

    Start printing many lines.

    If this gets interrupted with SIGPIPE,

    the script aborts quietly, and the process exit code is set to

    141 (128 + SIGPIPE)

    for x in range(10000): print(x)

  • Sinon, si vous voulez pour gérer vous-même l'exception déclenchée par SIGPIPE (fonctionne à la fois dans Python 3 et 2, adapté de l'outil docs ):

    import sys, os, errno

    try:

    Start printing many lines.

    for x in range(10000): print(x)

    IMPORTANT: Flush stdout here, to ensure that the

    SIGPIPE-triggered exception can be caught.

    sys.stdout.flush()

    except IOError as e:

    Note: Python 3 has the more specific BrokenPipeError,

    but this way the code works in Python 2 too.

    if e.errno != errno.EPIPE: raise e # Unrelated error, re-throw.

    Python flushes standard streams on exit; redirect remaining output

    to devnull to avoid another BrokenPipeError at shutdown

    devnull = os.open(os.devnull, os.O_WRONLY) os.dup2(devnull, sys.stdout.fileno())

    ... perform other handling.

    Note: You can't write to stdout here.

    (print() and sys.stdout.write won't work)

    However, sys.stderr.write() can be used.

    sys.stderr.write("SIGPIPE received, terminating.\n")

    Finally, exit with an exit code of choice.

    sys.exit(141)


[1] Notez que dans bash vous ne verrez par défaut que head Le code de sortie de l'utilisateur - qui est 0 - reflété dans $? après. Utilisez echo ${PIPESTATUS[0]} pour voir le code de sortie de Python.

[2] Curieusement, sur macOS 10.15.7 (Catalina), avec Python 3.9.2 (mais pas 2.x), j'obtiens le code de sortie suivant 120 mais les docs disent 1 et c'est ce que je vois aussi sous Linux.

51voto

Alex L Points 3048

Je n'ai pas reproduit le problème, mais peut-être que cette méthode le résoudrait : (en écrivant ligne par ligne à stdout plutôt que d'utiliser print )

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

Vous pourriez attraper le tuyau cassé ? Cela écrit le fichier sur stdout ligne par ligne jusqu'à ce que le tuyau soit fermé.

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

Vous devez également vous assurer que othercommand lit le tuyau avant qu'il ne devienne trop gros - https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

31voto

Blckknght Points 20780

L'erreur "Broken Pipe" se produit lorsque vous essayez d'écrire sur un tuyau qui a été fermé à l'autre extrémité. Puisque le code que vous avez montré n'implique pas directement de pipe, je soupçonne que vous faites quelque chose en dehors de Python pour rediriger la sortie standard de l'interpréteur Python vers un autre endroit. Cela pourrait se produire si vous exécutez un script comme ceci :

python foo.py | someothercommand

Le problème que vous rencontrez est que someothercommand se termine sans avoir lu tout ce qui est disponible sur son entrée standard. Cela fait que votre écriture (via print ) pour échouer à un moment donné.

J'ai pu reproduire l'erreur avec la commande suivante sur un système Linux :

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

Si je ferme le less sans faire défiler toutes ses entrées (1000 lignes), Python se termine avec le même message d'erreur que celui de l'utilisateur. IOError que vous avez signalée.

22voto

trehn Points 381

Je me sens obligé de souligner que la méthode qui utilise

signal(SIGPIPE, SIG_DFL) 

est en effet dangereux (comme l'a déjà suggéré David Bennet dans les commentaires) et, dans mon cas, a conduit à des affaires bizarres dépendantes de la plate-forme lorsqu'il est combiné avec multiprocessing.Manager (parce que la bibliothèque standard repose sur le fait que BrokenPipeError est levé à plusieurs endroits). Pour résumer une histoire longue et pénible, voici comment je l'ai réparée :

D'abord, vous devez attraper le IOError (Python 2) ou BrokenPipeError (Python 3). En fonction de votre programme, vous pouvez essayer de sortir prématurément à ce moment-là ou simplement ignorer l'exception :

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

Toutefois, cela ne suffit pas. Python 3 peut toujours imprimer un message comme celui-ci :

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

Malheureusement, se débarrasser de ce message n'est pas simple, mais j'ai finalement trouvé http://bugs.python.org/issue11380 où Robert Collins suggère cette solution de contournement que j'ai transformée en un décorateur avec lequel vous pouvez envelopper votre fonction principale (oui, c'est une indentation folle) :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X