IOError: [Errno 32] Pipe cassée: Python

88

J'ai un script Python 3 très 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()

Mais il 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 pas le SIGPIPE de Python.

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

open.py | othercommand
JOHANNES_NYÅTT
la source
@squiguy ligne 2:print(f1.readlines())
JOHANNES_NYÅTT
2
Vous avez deux opérations d'E / S sur la ligne 2: une lecture depuis a.txtet une écriture vers stdout. Essayez peut-être de les diviser sur des lignes séparées afin de voir quelle opération déclenche l'exception. Si stdoutest un tube et que la fin de lecture a été fermée, cela pourrait expliquer l' EPIPEerreur.
James Henstridge
1
Je peux reproduire cette erreur en sortie (dans les bonnes conditions), donc je soupçonne que l' printappel est le coupable. @ JOHANNES_NYÅTT, pouvez-vous clarifier comment vous lancez votre script Python? Redirigez-vous la sortie standard quelque part?
Blckknght
2
Il s'agit d'un doublon possible de la question suivante: stackoverflow.com/questions/11423225/…

Réponses:

45

Je n'ai pas reproduit le problème, mais peut-être que cette méthode le résoudrait: (écrire ligne par ligne stdoutplutô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 stdoutligne 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 la othercommandlecture est effectuée à partir du tube avant qu'il ne devienne trop gros - /unix/11946/how-big-is-the-pipe-buffer

Alex L
la source
7
Bien que ce soit une bonne pratique de programmation, je ne pense pas que cela ait quoi que ce soit à voir avec l'erreur de tuyau cassé que l'interrogateur obtient (ce qui est probablement lié à l' printappel, pas à la lecture des fichiers).
Blckknght
@Blckknght J'ai ajouté quelques questions et des méthodes alternatives et j'espérais des commentaires de l'auteur. Si le problème est d'envoyer une grande quantité de données à partir d'un fichier ouvert directement vers l'instruction d'impression, alors peut-être qu'une des alternatives ci-dessus le résoudrait.
Alex L
(Les solutions les plus simples sont souvent les meilleures - à moins qu'il n'y ait une raison particulière de charger un fichier entier puis de l'imprimer, faites-le d'une manière différente)
Alex L
1
Excellent travail pour résoudre ce problème! Bien que je puisse prendre cette réponse pour acquise, je n'ai pu l'apprécier qu'après avoir vu à quel point les autres réponses (et ma propre approche) pâlissaient par rapport à votre réponse.
Jesvin Jose
117

Le problème est dû à la gestion SIGPIPE. Vous pouvez résoudre ce problème à l'aide du code suivant:

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

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

Akhan
la source
13
C'est très dangereux, comme je viens de le découvrir, car si jamais vous obtenez un SIGPIPE sur une socket (httplib ou autre), votre programme se fermera sans avertissement ni erreur.
David Bennett
1
@DavidBennett, je suis sûr que son application dépendra et pour vos besoins, la réponse acceptée est la bonne. Il y a beaucoup Questions et réponses complet ici pour les gens de passer et prendre une décision éclairée. IMO, pour les outils de ligne de commande, il est probablement préférable d'ignorer le signal de canal dans la plupart des cas.
akhan
1
Un moyen de ne faire cela que temporairement?
Nate Glenn
2
@NateGlenn Vous pouvez enregistrer le gestionnaire existant et le restaurer plus tard.
akhan
3
Quelqu'un pourrait-il me dire pourquoi les gens considèrent un article de blogspot comme une meilleure source de vérité que la documentation officielle (indice: ouvrez le lien pour voir comment corriger correctement l'erreur de tuyau cassé)? :)
Yurii Rabeshko
92

Pour apporter réponse utile Alex L. , réponse utile de Akhan et réponse utile de Blckknght ainsi que des informations supplémentaires:

  • Le signal Unix standardSIGPIPE est envoyé à un processus qui écrit sur un tube lorsqu'il n'y a plus de lecture de processus depuis le tube.

    • Ce n'est pas nécessairement une condition d' erreur ; certains utilitaires Unix tels que head par conception arrêtent de lire prématurément à partir d'un tube, une fois qu'ils ont reçu suffisamment de données.
  • Par défaut - c'est-à-dire, si le processus d'écriture ne piège pas explicitement SIGPIPE- le processus d'écriture est simplement terminé , et son code de sortie est mis à141 , qui est calculé comme 128(pour signaler la fin par le signal en général) + 13( SIGPIPEle numéro de signal spécifique de ) .

  • De par sa conception, cependant, Python lui-même le piègeSIGPIPE et le traduit en uneIOError instance Python avec une errnovaleur errno.EPIPE, afin qu'un script Python puisse l'attraper, s'il le souhaite - voir la réponse d'Alex L. pour savoir comment faire.

  • Si un python de script ne pas l' attraper , Python message d'erreur deIOError: [Errno 32] Broken pipe et termine le script avec le code de sortie1 - c'est le symptôme de la scie OP.

  • Dans de nombreux cas, cela est plus perturbateur qu'utile , il est donc souhaitable de revenir au comportement par défaut :

    • L'utilisation du signalmodule permet justement cela, comme indiqué dans la réponse d'Akhan ; signal.signal()prend un signal à traiter comme 1er argument et un gestionnaire comme 2ème; la valeur du gestionnaire spécial SIG_DFLreprésente le comportement par défaut du système :

      from signal import signal, SIGPIPE, SIG_DFL
      signal(SIGPIPE, SIG_DFL) 
      
mklement0
la source
32

Une erreur "Broken Pipe" se produit lorsque vous essayez d'écrire dans un canal qui a été fermé à l'autre extrémité. Puisque le code que vous avez montré n'implique aucun tube directement, je suppose que vous faites quelque chose en dehors de Python pour rediriger la sortie standard de l'interpréteur Python vers un autre endroit. Cela peut arriver si vous exécutez un script comme celui-ci:

python foo.py | someothercommand

Le problème que vous avez est que vous someothercommandquittez sans lire tout ce qui est disponible sur son entrée standard. Cela provoque votre écriture (viaprint échec de ) à 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 lesspager sans faire défiler toutes ses entrées (1000 lignes), Python sort avec le même que IOErrorvous avez signalé.

Blckknght
la source
10
Oui, c'est vrai, mais comment y remédier?
JOHANNES_NYÅTT
2
s'il vous plaît laissez-moi savoir comment y remédier.
JOHANNES_NYÅTT
1
@ JOHANNES_NYÅTT: Cela peut fonctionner pour les petits fichiers à cause de la mise en mémoire tampon fournie par les tubes sur la plupart des systèmes de type Unix. Si vous pouvez écrire tout le contenu des fichiers dans la mémoire tampon, cela ne génère pas d'erreur si l'autre programme ne lit jamais ces données. Cependant, si l'écriture se bloque (car la mémoire tampon est pleine), elle échouera lorsque l'autre programme se fermera. Pour redire: quelle est l'autre commande? Nous ne pouvons plus vous aider uniquement avec le code Python (puisque ce n'est pas la partie qui fait la mauvaise chose).
Blckknght
1
J'ai eu ce problème lors de la tuyauterie à la tête ... exception après dix lignes de sortie. Tout à fait logique, mais toujours inattendu :)
André Laszlo
4
@Blckknght: Bonne info en général, mais re " fix que: et « la partie qui est de faire la mauvaise chose »: un SIGPIPEsignal ne signifie pas nécessairement une erreur état, certains utilitaires Unix, notamment head, par la conception, pendant le fonctionnement normal près du tuyau au début, une fois qu'ils ont lu autant de données que ils avaient besoin.
mklement0
20

Je me sens obligé de préciser que la méthode utilisant

signal(SIGPIPE, SIG_DFL) 

est en effet dangereux (comme déjà suggéré par David Bennet dans les commentaires) et dans mon cas, a conduit à des activités amusantes dépendant de la plate-forme lorsqu'il est combiné avec multiprocessing.Manager(car la bibliothèque standard repose sur BrokenPipeError soulevé à plusieurs endroits). Pour faire une histoire longue et douloureuse, voici comment je l'ai corrigée:

Tout d'abord, vous devez attraper le IOError(Python 2) ou BrokenPipeError(Python 3). En fonction de votre programme, vous pouvez essayer de quitter tôt à ce stade 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

Cependant, 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 décorateur avec lequel vous pouvez envelopper votre fonction principale (oui, c'est un peu fou échancrure):

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
la source
2
Cela n'a pas semblé résoudre le problème pour moi.
Kyle Bridenstine
Cela a fonctionné pour moi après avoir ajouté sauf BrokenPipeError: passer dans la fonction supress_broken_pipe_msg
Rupen B
2

Je sais que ce n'est pas la manière «appropriée» de le faire, mais si vous souhaitez simplement vous débarrasser du message d'erreur, vous pouvez essayer cette solution de contournement:

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

La réponse principale ( if e.errno == errno.EPIPE:) ici n'a pas vraiment fonctionné pour moi. J'ai eu:

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

Cependant, cela devrait fonctionner si tout ce dont vous vous souciez est d'ignorer les tuyaux cassés sur des écritures spécifiques. Je pense que c'est plus sûr que de piéger SIGPIPE:

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

Vous devez évidemment prendre une décision quant à savoir si votre code est vraiment, vraiment fait si vous frappez le tuyau cassé, mais pour la plupart des cas, je pense que cela sera généralement vrai. (N'oubliez pas de fermer les descripteurs de fichiers, etc.)

JD Baldwin
la source
1

Cela peut également se produire si la fin de lecture de la sortie de votre script meurt prématurément

ie open.py | autreCommande

si otherCommand se ferme et open.py essaie d'écrire dans stdout

J'ai eu un mauvais script qui m'a fait du bien.

Lkreinitz
la source
2
Il ne s'agit pas nécessairement de la mort du processus de lecture du tube : certains utilitaires Unix,head , par la conception, pendant le fonctionnement normal près du tuyau au début, une fois qu'ils ont lu autant de données que leur besoin. La plupart des CLI s'en remettent simplement au système pour son comportement par défaut: terminer discrètement le processus de lecture et signaler le code de sortie 141(ce qui, dans un shell, n'est pas évident, car la dernière commande d' un pipeline détermine le code de sortie global). Le comportement par défaut de Python , malheureusement, est de mourir bruyamment .
mklement0
-1

Les fermetures doivent être effectuées dans l'ordre inverse des ouvertures.

Paul
la source
4
Bien que ce soit une bonne pratique en général, ne pas faire n'est pas un problème en soi et n'explique pas les symptômes du PO.
mklement0