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

StackOverflow https://stackoverflow.com/questions/295459

  •  08-07-2019
  •  | 
  •  

Question

Comment puis-je exécuter la commande shell suivante avec le sous-processus module?

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

Les données d'entrée proviendront d'une chaîne, je n'ai donc pas besoin de echo . J’en suis encore là. Quelqu'un peut-il expliquer comment je parviens à le faire passer aussi par tri ?

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

MISE À JOUR : notez que même si la réponse acceptée ci-dessous ne répond pas à la question posée, je pense que S.Lott a raison et qu'il est préférable d'éviter de devoir résoudre ce problème en premier lieu. !

Était-ce utile?

La solution

Vous seriez un peu plus heureux avec ce qui suit.

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-le connecter deux processus avec un pipeline.

Vous seriez beaucoup plus heureux de réécrire 'script.awk' en Python, en éliminant awk et le pipeline.

Modifier . Certaines des raisons de suggérer que awk n’aide pas.

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

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

  2. Le traitement en pipeline de awk à trier, pour de grands ensembles de données, peut améliorer le temps de traitement écoulé. Pour de courts ensembles de données, cela ne présente aucun avantage significatif. Une mesure rapide de awk > file; trier le fichier et awk | sort révélera les aides de concurrence. Avec le tri, cela aide rarement car le tri n’est pas un filtre à passage unique.

  3. La simplicité de "python pour trier" Le traitement (au lieu de "Python pour effacer pour trier") empêche le type exact de questions posées ici.

  4. Python - bien que plus verbeux que awk - est également explicite là où awk a certaines règles implicites qui sont opaques pour les débutants et qui déroutent les non-spécialistes.

  5. Awk (comme le script shell lui-même) ajoute Yet Another Programming. Si tout cela peut être fait dans un seul langage (Python), éliminer le shell et la programmation awk supprime deux langages de programmation, permettant ainsi à quelqu'un de se concentrer sur les parties de la tâche productrices de valeur.

En bout de ligne: awk ne peut pas ajouter de valeur significative. Dans ce cas, awk est un coût net; cela 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 la création d'un pipeline ( a | b ) est-elle si difficile?

Lorsque le shell est confronté à a | b il doit faire ce qui suit.

  1. Lancez un processus enfant du shell d'origine. Cela deviendra éventuellement b.

  2. Construisez un tuyau en os. (pas un sous-processus Python.PIPE) mais appelez os.pipe () qui renvoie deux nouveaux descripteurs de fichier connectés via un tampon commun. À ce stade, le processus a stdin, stdout, stderr de son parent, plus un fichier qui sera "une a stdout". et "b's stdin".

  3. Fourchez un enfant. L'enfant remplace sa sortie standard par la nouvelle sortie standard a. Exécutez le processus a .

  4. L’enfant b se ferme remplace son stdin par le nouveau stdin de b. Exécutez le processus b .

  5. L’enfant b attend la fin de a.

  6. Le parent attend que b soit terminé.

Je pense que ce qui précède peut être utilisé de manière récursive pour générer a | b | c , mais vous devez implicitement mettre entre parenthèses de longs pipelines, en les traitant comme s'ils étaient a | (b | c) .

Puisque Python a os.pipe () , os.exec () et os.fork () , vous pouvez remplacer < code> sys.stdin et sys.stdout , il existe un moyen de faire ce qui précède en pur Python. En effet, vous pourrez peut-être trouver des raccourcis avec os.pipe () et subprocess.Popen .

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

Autres conseils

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)

Pour émuler un pipeline shell:

from subprocess import check_call

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

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

#!/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 un sucre syntaxique:

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

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

L'analogue de:

#!/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")()

http://www.python.org/doc/2.5.2/lib/node535.html a très bien couvert cela. Y a-t-il une partie de ce que vous n'avez pas comprise?

Votre programme serait assez similaire, mais le second Popen aurait stdout = dans un fichier, et vous n’auriez pas besoin de la sortie de son .communicate () .

Inspiré par 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 pense, 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

  • Exécution déclarée paresseuse grep avec stdin from pipe. Cette commande sera exécutée à l'exécution de la commande ps lorsque le canal sera rempli avec la sortie standard de ps .
  • Appelé la commande principale ps avec stdout dirigée vers le canal utilisé par la commande grep .
  • Grep a communiqué pour obtenir la sortie standard du tuyau.

J'aime cette façon, car il s'agit d'une conception de tuyau naturelle, enveloppée dans des interfaces sous-processus .

La réponse acceptée est de contourner le problème. Voici un extrait qui enchaîne la sortie de plusieurs processus: Notez qu'il imprime également la commande (quelque peu) équivalente du shell afin que vous puissiez l'exécuter et vous assurer que le résultat est correct.

#!/usr/bin/env python3

from subprocess import Popen, PIPE

# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")

p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)

print("Output from last process : " + (p3.communicate()[0]).decode())

# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)

MODIFIER: Les tubes sont disponibles sous Windows, mais il est à noter que cela ne semble pas réellement fonctionner sous Windows. Voir les commentaires ci-dessous.

La bibliothèque standard Python inclut maintenant le module pipes pour gérer ceci:

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

Je ne sais pas depuis combien de temps ce module existe, mais cette approche semble être beaucoup plus simple que de bidouiller avec le sous-processus .

Les réponses précédentes ont manqué un point important. Le remplacement du pipeline shell est fondamentalement correct, comme indiqué par géocar. Il est presque suffisant d'exécuter communic sur le dernier élément du canal.

Le problème qui reste à résoudre consiste à transmettre les données d'entrée au pipeline. Avec plusieurs sous-processus, une simple communication (input_data) sur le dernier élément ne fonctionne pas - elle se bloque pour toujours. Vous devez créer un pipeline et un enfant manuellement, comme suit:

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 $2; }"],
                         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 les informations par le biais du canal de communication et les appels parents communiquent (), 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, la la documentation sur les sous-processus ne mentionne pas cela.

Il existe des moyens d'obtenir le même effet sans tuyaux:

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

Utilisez maintenant stdin = tf pour p_awk . C'est une question de goût que vous préférez.

Ce qui précède n’est toujours pas équivalent à 100% aux pipelines bash car le traitement du signal est différent. Vous pouvez le voir si vous ajoutez un autre élément de tuyau qui tronque la sortie de sort , par exemple. head -n 10 . Avec le code ci-dessus, sort imprimera un " Tube cassé " message d'erreur à stderr . Vous ne verrez pas ce message lorsque vous exécutez le même pipeline dans le shell. (C'est la seule différence cependant, le résultat dans stdout est le même). La raison semble être que Popen de python définit SIG_IGN pour SIGPIPE , alors que le shell le laisse à SIG_DFL , et Le traitement du signal de sort est différent dans ces deux cas.

Pour moi, l'approche ci-dessous est la plus propre et la plus facile à lire

from subprocess import Popen, PIPE

def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
    with open(output_filename, 'wb') as out_f:
        p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
        p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
        p1.communicate(input=bytes(input_s))
        p1.wait()
        p2.stdin.close()
        p2.wait()

qui peut être appelé comme suit:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top