Comment utiliser subprocess.Popen pour connecter plusieurs processus par des tuyaux?

Comment puis-je exécuter la commande shell suivante en utilisant le Python subprocess module?

echo "input data" | awk -f script.awk | sort > outfile.txt

Les données d'entrée viendra à partir d'une chaîne, donc je n'ai pas réellement besoin echo. Je suis arrivé jusqu'ici, est-ce que quelqu'un peut expliquer comment je l'ai fait passer à travers sort trop?

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

UPDATE: notez que même si la réponse acceptée ci-dessous ne répond pas à la question telle qu'elle est posée, je crois que S. Lott a raison et qu'il est préférable d'éviter d'avoir à résoudre le problème en premier lieu!

40
demandé sur twasbrillig 2008-11-17 15:17:49

7 réponses

Vous seriez un peu plus heureux avec le suivant.

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )

déléguer une partie du travail au shell. Laissez connecter deux processus avec un pipeline.

vous seriez bien plus heureux de réécrire le script.il s'enfonce dans Python, éliminant awk et le pipeline.

Modifier. Certaines des raisons suggérant qu'awk n'aide pas.

[Il y a trop de raisons de répondre via commentaire.]

  1. Awk ajoute une étape sans valeur significative. Il n'y a rien d'unique dans le traitement d'awk que Python ne gère pas.

  2. la pipelinette de awk à sort, pour de grands ensembles de données, peut améliorer le temps de traitement écoulé. Pour de courtes séries de données, il n'a pas d'avantage significatif. Un rapide de mesure de awk >file ; sort file et awk | sort révélera de simultanéité de l'aide. Avec le tri, il aide rarement parce que le tri n'est pas une filtrer.

  3. la simplicité du traitement" Python to sort "(au lieu de" Python to awk to sort") empêche le type exact de questions posées ici.

  4. Python -- alors que wordier than awk -- est aussi explicite quand awk a certaines règles implicites qui sont opaques pour les débutants, et déroutantes pour les non-spécialistes.

  5. Awk (comme le script shell lui-même) ajoute encore un autre langage de programmation. Si tout cela peut être fait dans un langage (Python), en éliminant le shell et la programmation awk élimine deux langages de programmation, permettant à quelqu'un de se concentrer sur les parties de la tâche qui produisent de la valeur.

conclusion: awk ne peut pas ajouter de valeur significative. Dans ce cas, awk est un coût net; il a ajouté suffisamment de complexité pour qu'il soit nécessaire de poser cette question. Supprimer awk sera un gain net.

barre latérale pourquoi construire un pipeline (a | b) est donc dur.

Quand le shell est confronté à a | b il doit faire la chose suivante.

  1. fourche procédé enfant de la coquille d'origine. Cela deviendra éventuellement B.

  2. construisez une pipe os. (pas un sous-processus Python.PIPE) but call os.pipe() qui renvoie deux nouveaux descripteurs de fichier connectés via common buffer. À ce stade, le processus a stdin, stdout, stderr de son parent, plus un fichier qui sera "stdout de A"" et "b stdin".

  3. fourche un enfant. L'enfant remplace son stdout par le nouveau stdout de A. Exec the a processus.

  4. l'Enfant B Ferme remplace sa ddin avec la nouvelle ddin du B. Exec the b processus.

  5. l'Enfant B attend a pour terminer.

  6. le parent attend que b soit terminé.

je pense que le ci-dessus peut être utilisé récursivement pour frayer a | b | c, mais vous devez implicitement mettre entre parenthèses les longs pipelines, les traitant comme s'ils étaient a | (b | c).

depuis Python a os.pipe(),os.exec() et os.fork(), et vous pouvez remplacer sys.stdin et sys.stdout, il y a un moyen de faire ce qui précède en Python pur. En effet, vous pouvez être en mesure de travailler sur quelques raccourcis en utilisant os.pipe() et subprocess.Popen.

cependant, il est plus facile de déléguer cette opération au shell.

38
répondu S.Lott 2016-04-02 17:42:29
import subprocess

some_string = b'input_data'

sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                 stdin=subprocess.PIPE).communicate(some_string)
18
répondu Cristian 2015-03-14 15:39:10

Pour émuler un oléoduc de shell:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', shell=True)

sans invoquer le shell (voir 17.1.4.2. Remplacement du pipeline shell):

#!/usr/bin/env python
from subprocess import Popen, PIPE

a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
    with a.stdout, open("outfile.txt", "wb") as outfile:
        b = Popen(["b"], stdin=a.stdout, stdout=outfile)
    a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already

plumbum fournit du sucre de syntaxe:

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

analogique:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

est:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
16
répondu jfs 2015-03-14 14:13:50

http://www.python.org/doc/2.5.2/lib/node535.html couvrait assez bien cette question. Y a-t-il quelque chose que vous n'avez pas compris?

votre programme serait assez similaire, mais le second Popen aurait stdout= à un fichier, vous n'avez pas besoin de la sortie de son .communicate().

3
répondu geocar 2014-06-28 12:49:49

inspiré de la réponse de @Cristian. J'ai rencontré le même problème, mais avec une commande Différente. Donc je mets mon exemple testé, qui je crois pourrait être utile:

grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()

ceci est testé.

Ce qui a été fait

  • déclaré paresseux grep exécution avec stdin de pipe. Cette commande sera exécutée au ps exécution de la commande quand le tuyau sera rempli avec le stdout de ps.
  • appelé la commande principale ps avec stdout dirigé vers le canal utilisé par le grep la commande.
  • Grep communiqué pour sortir stdout du tuyau.

j'aime cette façon parce qu'il est naturel de tuyau de conception délicatement enveloppé avec subprocess interfaces.

2
répondu I159 2015-02-10 10:50:04

EDIT:pipes est disponible sur Windows mais, surtout, ne semble pas réellement sur Windows. Voir les commentaires ci-dessous.

la bibliothèque standard de Python inclut maintenant pipes module pour gérer cela:

https://docs.python.org/2/library/pipes.html, https://docs.python.org/3.4/library/pipes.html

Je ne sais pas combien de temps ce module a été autour, mais cette approche apparaît à être nettement plus simple que de coucher avec subprocess.

1
répondu Kyle Strand 2014-12-04 21:23:10

les réponses précédentes ont omis un point important. Remplacement d'oléoduc de shell est fondamentalement correcte, comme le souligne geocar. C'est presque suffisant pour exécuter communicate sur le dernier élément de la pipe.

le problème restant est de passer le données d'entrée au pipeline. Avec plusieurs sous-processus, un simple communicate(input_data) sur le dernier élément ne fonctionne pas - il se bloque toujours. Vous devez créer un pipeline et d'un enfant manuellement comme ceci:

import os
import subprocess

input = """\
input data
more input
""" * 10

rd, wr = os.pipe()
if os.fork() != 0: # parent
    os.close(wr)
else:              # child
    os.close(rd)
    os.write(wr, input)
    os.close(wr)
    exit()

p_awk = subprocess.Popen(["awk", "{ print ; }"],
                         stdin=rd,
                         stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"], 
                          stdin=p_awk.stdout,
                          stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())

maintenant l'enfant fournit L'entrée par la pipe, et le parent appelle communicate (), ce qui fonctionne comme prévu. Avec cette approche, vous pouvez créer de longs pipelines arbitraires sans avoir recours à la "délégation d'une partie du travail au shell". Malheureusement, le documentation des sous-processus n'en parle pas.

il y a des moyens d'obtenir le même effet sans pipes:

from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)

maintenant utilisez stdin=tfp_awk. C'est un question de goût ce que vous préférez.

ce qui précède n'est toujours pas 100% équivalent à bash pipelines parce que la manipulation du signal est différente. Vous pouvez voir si vous ajoutez un autre tuyau élément qui tronque la sortie de sort, e.g. head -n 10. Avec le code ci-dessus, sort affichera un message d'erreur" broken pipe " à stderr. Vous ne verrez pas ce message lorsque vous exécutez le même pipeline dans le shell. (C'est la seule différence, le résultat stdout est la même). Raison semble que python Popen configure SIG_IGNSIGPIPE, alors que la coque laisse au SIG_DFL et

1
répondu uncleremus 2017-11-18 15:50:43