2010-11-05 6 views
12

Merci aux suggestions utiles ci-dessous:débutant python sous-processus: "erreur d'écriture: Broken pipe"

Il semble donc être fixé quand je

  1. commandes séparées dans des appels individuels à Popen
  2. stderr = subprocess.PIPE en tant qu'argument pour chaque chaîne Popen.

Le nouveau code:

import subprocess 
import shlex 
import logging 

def run_shell_commands(cmds): 
    """ Run commands and return output from last call to subprocess.Popen. 
     For usage see the test below. 
    """ 
    # split the commands 
    cmds = cmds.split("|") 
    cmds = list(map(shlex.split,cmds)) 

    logging.info('%s' % (cmds,)) 

    # run the commands 
    stdout_old = None 
    stderr_old = None 
    p = [] 
    for cmd in cmds: 
     logging.info('%s' % (cmd,)) 
     p.append(subprocess.Popen(cmd,stdin=stdout_old,stdout=subprocess.PIPE,stderr=subprocess.PIPE)) 
     stdout_old = p[-1].stdout 
     stderr_old = p[-1].stderr 
    return p[-1] 


pattern = '"^85567  "' 
file = "j" 

cmd1 = 'grep %s %s | sort -g -k3 | head -10 | cut -d" " -f2,3' % (pattern, file) 
p = run_shell_commands(cmd1) 
out = p.communicate() 
print(out) 

Original Post:

J'ai passé trop de temps à essayer de résoudre un problème de tuyauterie simple subprocess.Popen.

code:

import subprocess 
cmd = 'cat file | sort -g -k3 | head -20 | cut -f2,3' % (pattern,file) 
p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE) 
for line in p.stdout: 
    print(line.decode().strip()) 

sortie pour le fichier ~ 1000 lignes de longueur:

... 
sort: write failed: standard output: Broken pipe 
sort: write error 

sortie pour le fichier> 241 lignes de longueur:

... 
sort: fflush failed: standard output: Broken pipe 
sort: write error 

sortie pour le fichier < 241 les lignes de longueur sont bien.

J'ai lu les documents et googlé comme un fou mais il y a quelque chose de fondamental au sujet du module de sous-processus qui me manque ... peut-être à faire avec des tampons. J'ai essayé p.stdout.flush() et joué avec la taille de la mémoire tampon et p.wait(). J'ai essayé de reproduire cela avec des commandes comme 'sleep 20; cat moderatefile 'mais cela semble fonctionner sans erreur.

+0

... et p2.communicate() fonctionne également mais je pense que cela peut causer des problèmes si la sortie est grande. – mathtick

+1

'Nouveau code' très utile. J'adore pouvoir utiliser exactement la même commande que j'ai utilisée pour tester dans le shell. Deux suggestions: 1) faire pluriel: run_shell_commands 2) soit supprimer, commenter, ou ajouter debug = false autour des instructions d'impression à l'intérieur de la fonction – PeterVermont

+1

Merci. Ran dans le même problème de tuyau cassé avec des fichiers sur une certaine taille. J'ai utilisé votre code et ça fonctionne comme un charme. – poof

Répondre

10

Des recettes sur subprocess docs:

# To replace shell pipeline like output=`dmesg | grep hda` 
p1 = Popen(["dmesg"], stdout=PIPE) 
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) 
output = p2.communicate()[0] 
+2

Je pensais que la communication devait être évitée pour les gros volumes? – mathtick

+1

Le shell ne causait pas le problème, mais pour une raison quelconque, la division des commandes dans le "bon" endroit semble le réparer. Merci! – mathtick

+0

@mathtick: vous devriez en effet parcourir sur PIPE au lieu d'attribuer une sortie importante à une instance de chaîne, sinon vous risquez une exception de mémoire insuffisante. –

0

Vous n'avez pas besoin shell=True. N'appelez pas le shell. Voici comment je le ferais:

p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 
stdout_value = p.communicate()[0] 
stdout_value # the output 

Voyez si vous rencontrez le problème au sujet du tampon après l'avoir utilisé?

+0

Le shell ne semblait pas causer le problème. Diviser les commandes au bon endroit semble l'avoir corrigé (voir mise à jour). Merci! – mathtick

4

C'est parce que vous ne devriez pas utiliser « tuyaux shell » dans la commande passée à subprocess.Popen, vous devez utiliser le subprocess.PIPE comme ceci:

from subprocess import Popen, PIPE 

p1 = Popen('cat file', stdout=PIPE) 
p2 = Popen('sort -g -k 3', stdin=p1.stdout, stdout=PIPE) 
p3 = Popen('head -20', stdin=p2.stdout, stdout=PIPE) 
p4 = Popen('cut -f2,3', stdin=p3.stdout) 
final_output = p4.stdout.read() 

Mais je dois dire que ce que vous essayez de faire pourrait être fait en python pur au lieu d'appeler un tas de commandes shell.

+3

Je salue plus de 13 millions de lignes pour 100k + lignes d'allumettes, pour trier, couper et prendre une «tête». Cela prend quelques secondes dans le shell. Cela prenait une éternité en python. J'ai essayé read() et j'ai pensé que j'ai essayé de séparer les commandes mais je pense que c'est le même problème. Reviendra après avoir testé plus ... – mathtick

+1

La division des commandes semble l'avoir corrigée, même si j'utilise encore shell = True. – mathtick

0

essayez d'utiliser communicate(), plutôt que de lire directement à partir de stdout.

les docs python dire ceci:

"Warning Use communicate() rather than .stdin.write, .stdout.read or .stderr.read to avoid deadlocks due to any of the other OS pipe buffers filling up and blocking the child process."

http://docs.python.org/library/subprocess.html#subprocess.Popen.stdout

p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 
output = p.communicate[0] 
for line in output: 
    # do stuff 
+0

J'ai essayé p.communicate() [0] mais cela n'a pas résolu le problème. Fractionner les commandes de manière appropriée (voir ci-dessus). Je ne comprends toujours pas pourquoi cela a arrangé les choses. – mathtick

1

J'ai eu la même erreur.Même mettre le tuyau dans un script bash et exécuté à la place du tuyau en Python. De Python il obtiendrait l'erreur de tuyau cassée, à partir de bash ce ne serait pas.

Il me semble que peut-être la dernière commande avant la tête est en train de lancer une erreur car c'est (le sort) que STDOUT est fermé. Python doit être conscient de cela alors qu'avec le shell, l'erreur est silencieuse. J'ai changé mon code pour consommer toute l'entrée et l'erreur est partie.

Cela aurait aussi du sens avec des fichiers plus petits car le tube tamponnerait probablement toute la sortie avant que la tête ne quitte. Cela expliquerait les pauses sur les fichiers plus volumineux.

par exemple, au lieu d'une « tête -1 » (dans mon cas, je voulais seulement la première ligne), je l'ai fait un awk « NR == 1 »

Il y a probablement de meilleures façons de le faire en fonction de l'endroit où la «tête -X» se produit dans le tuyau.

Questions connexes