77 Stimmen

Wie verwende ich subprocess.Popen, um mehrere Prozesse über Pipes zu verbinden?

Wie führe ich den folgenden Shell-Befehl unter Verwendung der Python subprocess Modul?

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

Die Eingabedaten werden aus einer Zeichenkette stammen, daher brauche ich eigentlich keine echo . Ich habe so weit gekommen, kann jemand erklären, wie ich es zu bekommen, um durch Rohr sort auch?

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

UPDATE : Beachten Sie, dass die unten stehende akzeptierte Antwort die Frage nicht wirklich beantwortet, aber ich glaube, dass S. Lott Recht hat und es besser ist, dieses Problem gar nicht erst zu lösen!

2voto

I159 Punkte 27156

Inspiriert durch die Antwort von @Cristian. Ich hatte genau das gleiche Problem, allerdings mit einem anderen Befehl. Deshalb füge ich hier mein getestetes Beispiel ein, das meiner Meinung nach hilfreich sein könnte:

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

Dies wird getestet.

Was wurde getan?

  • Für faul erklärt grep Ausführung mit stdin aus der Pipe. Dieser Befehl wird in der ps Befehlsausführung, wenn die Pipe mit dem stdout von ps .
  • Aufrufen des primären Befehls ps mit stdout an die Pipe gerichtet, die von der grep Befehl.
  • Grep kommuniziert, um stdout aus der Pipe zu holen.

Ich mag diese Art und Weise, weil es eine natürliche Rohrkonzeption ist, die sanft mit subprocess Schnittstellen.

1 Stimmen

Um Zombies zu vermeiden, rufen Sie ps_proc.wait() nach grep_proc.communicate() . err ist immer None es sei denn, Sie setzen stderr=subprocess.PIPE .

2voto

uncleremus Punkte 158

In den vorherigen Antworten wurde ein wichtiger Punkt übersehen. Ersetzen der Shell-Pipeline ist grundsätzlich korrekt, wie von geocar hervorgehoben. Es ist fast ausreichend für den Betrieb communicate auf das letzte Element der Pipe.

Das verbleibende Problem ist die Übergabe der Eingangsdaten zur Pipeline. Bei mehreren Unterprozessen kann eine einfache communicate(input_data) auf das letzte Element funktioniert nicht - es bleibt für immer hängen. Sie müssen eine Pipeline und ein Kind manuell wie folgt erstellen:

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

Jetzt liefert das Kind die Eingabe über die Pipe, und das Elternteil ruft communicate() auf, was wie erwartet funktioniert. Mit diesem Ansatz können Sie beliebig lange Pipelines erstellen, ohne einen Teil der Arbeit an die Shell delegieren zu müssen. Leider ist die Teilprozessdokumentation wird dies nicht erwähnt.

Es gibt Möglichkeiten, den gleichen Effekt ohne Rohre zu erzielen:

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

Verwenden Sie nun stdin=tf para p_awk . Es ist eine Frage des Geschmacks, was Sie bevorzugen.

Die obige Lösung ist immer noch nicht 100%ig äquivalent zu Bash-Pipelines, da die Signalverarbeitung anders ist. Sie können dies sehen, wenn Sie ein weiteres Pipe-Element hinzufügen, das die Ausgabe von sort z.B. head -n 10 . Mit dem obigen Code, sort gibt eine Fehlermeldung "Broken pipe" an stderr . Sie werden diese Meldung nicht sehen, wenn Sie dieselbe Pipeline in der Shell ausführen. (Das ist allerdings der einzige Unterschied, das Ergebnis in stdout ist derselbe). Der Grund dafür scheint zu sein, dass Pythons Popen setzt SIG_IGN para SIGPIPE , während die Schale sie bei SIG_DFL et sort Die Signalverarbeitung ist in diesen beiden Fällen unterschiedlich.

0 Stimmen

Es genügt, die Kommunikation auf dem letzten Prozess laufen zu lassen, siehe meine Antwort.

1voto

Kyle Strand Punkte 15027

EDITAR: pipes ist unter Windows verfügbar, scheint aber nicht wirklich zu funktionieren. Arbeit unter Windows. Siehe Kommentare unten.

Die Python-Standardbibliothek enthält jetzt die pipes Modul für diese Aufgabe:

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

Ich bin mir nicht sicher, wie lange es dieses Modul schon gibt, aber dieser Ansatz scheint wesentlich einfacher zu sein als das Herumhantieren mit subprocess .

0 Stimmen

pipes existierte schon vor subprocess Modul. Es erstellt eine (*nix) Shell-Pipeline (eine Zeichenkette mit "|" die in /bin/sh ). Sie ist nicht tragbar. Es ist keine Alternative zu subprocess Modul, das portabel ist und nicht erfordert, dass eine Shell gestartet wird, um einen Befehl auszuführen. pipes Schnittstelle stammt aus der Zeit, als Enterprise JavaBeans noch eine glänzende Neuheit waren (sie ist no ein Kompliment). Könnten Sie uns pipes Code-Beispiel, das ist "wesentlich einfacher" als Unterprozess": check_call('echo "input data" | a | b > outfile.txt', shell=True) aus meiner Antwort ?

0 Stimmen

@J.F.Sebastian Huh. Sollte nicht Ihr check_call Befehl ebenso wenig portabel sein? DOS bietet Standard (d.h. *NIX-kompatibel, zumindest AFAIK) | Verhalten, also welche Systeme erwarten Sie pipes nicht zu bearbeiten? Ich gebe zu, dass die Verwendung check_call mit einer Zeichenkette, die Ihren Shell-Befehl darstellt, ist wohl genauso einfach wie die Verwendung von pipes , aber ich hoffte auf etwas, das den programmatischen Aufbau einer Pipeline erleichtern würde, anstatt nur eine einzelne Zeichenfolge an die Shell zu übergeben (a la Ihre anderen Beispiele).

0 Stimmen

Wie ich in meinem Kommentar sagte, plumbum sieht gut aus - es scheint genau die Einfachheit, Flexibilität und Leistung zu bieten, nach der ich suche. Allerdings ist die Syntax völlig undurchsichtig und nicht pythonisch. Was ich also will, ist etwas, das ungefähr so einfach und leicht ist wie Standard-*NIX-Shell-Pipes (wenn vielleicht leicht weniger prägnant), während es syntaktisch und stilistisch immer noch wie Python "aussieht". pipes Wenn Sie jedoch Recht haben, dass es nicht portabel ist (was wahrscheinlich der Fall ist), dann ist es natürlich die am wenigsten attraktive Option.

1voto

mwag Punkte 2818

Für mich ist der folgende Ansatz der sauberste und am einfachsten zu lesende

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

die wie folgt aufgerufen werden kann:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X