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!

57voto

S.Lott Punkte 371691

Mit dem Folgenden wären Sie etwas zufriedener.

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

Delegieren Sie einen Teil der Arbeit an die Shell. Lassen Sie sie zwei Prozesse mit einer Pipeline verbinden.

Sie wären viel glücklicher, wenn Sie "script.awk" in Python umschreiben und damit awk und die Pipeline eliminieren könnten.

bearbeiten . Einige der Gründe für die Annahme, dass awk nicht hilfreich ist.

(Es gibt zu viele Gründe, um über Kommentare zu antworten.)

  1. Awk fügt einen Schritt ohne nennenswerten Wert hinzu. Es gibt nichts Einzigartiges an der Verarbeitung von Awk, das Python nicht beherrscht.

  2. Das Pipelining von awk zu sort kann bei großen Datenmengen die verstrichene Verarbeitungszeit verbessern. Bei kleinen Datenmengen hat es keinen signifikanten Vorteil. Eine schnelle Messung von awk >file ; sort file y awk | sort wird von Gleichzeitigkeit hilft offenbaren. Bei der Sortierung hilft sie nur selten, da die Sortierung kein Durchgangsfilter ist.

  3. Die Einfachheit der "Python to sort"-Verarbeitung (anstelle von "Python to awk to sort") verhindert genau die Art von Fragen, die hier gestellt werden.

  4. Python ist zwar wortreicher als awk, aber auch explizit, während awk bestimmte implizite Regeln hat, die für Neulinge undurchsichtig und für Nicht-Spezialisten verwirrend sind.

  5. Awk fügt (wie das Shell-Skript selbst) eine weitere Programmiersprache hinzu. Wenn all dies in einer Sprache (Python) erledigt werden kann, werden durch den Wegfall der Shell und der Awk-Programmierung zwei Programmiersprachen überflüssig, so dass sich jemand auf die wertschöpfenden Teile der Aufgabe konzentrieren kann.

Unterm Strich kann awk keinen nennenswerten Mehrwert bringen. In diesem Fall ist awk ein Nettokostenfaktor; es hat genug Komplexität hinzugefügt, dass es notwendig war, diese Frage zu stellen. Das Entfernen von awk wird ein Nettogewinn sein.

Seitenleiste Warum der Bau einer Pipeline ( a | b ) ist so schwierig.

Wenn die Schale konfrontiert wird mit a | b muss sie Folgendes tun.

  1. Einen Kindprozess der ursprünglichen Shell aufspalten. Dieser wird schließlich zu b.

  2. Bauen Sie ein os-Rohr. (keine Python-Subprocess.PIPE), sondern rufen Sie os.pipe() die zwei neue Dateideskriptoren zurückgibt, die über einen gemeinsamen Puffer verbunden sind. Zu diesem Zeitpunkt hat der Prozess stdin, stdout und stderr von seinem Elternteil sowie eine Datei, die "stdout von a" und "stdin von b" sein wird.

  3. Ein Kind gabeln. Das Kind ersetzt sein stdout durch das stdout des neuen a. Ausführen der a Prozess.

  4. Das Kind b schließt und ersetzt seine stdin durch die stdin des neuen b. Ausführen der b Prozess.

  5. Das Kind b wartet auf den Abschluss von a.

  6. Der Elternteil wartet auf die Fertigstellung von b.

Ich denke, dass die oben genannte Methode rekursiv verwendet werden kann, um a | b | c aber Sie müssen lange Pipelines implizit in Klammern setzen und sie so behandeln, als wären sie a | (b | c) .

Da Python über os.pipe() , os.exec() y os.fork() und Sie können die sys.stdin y sys.stdout gibt es eine Möglichkeit, das oben genannte in reinem Python zu tun. In der Tat können Sie vielleicht einige Abkürzungen ausarbeiten, indem Sie os.pipe() y subprocess.Popen .

Es ist jedoch einfacher, diesen Vorgang an die Shell zu delegieren.

0 Stimmen

Können Sie erklären, was das "-c" bewirkt?

4 Stimmen

Und ich denke, Awk ist tatsächlich eine gute Passform für das, was ich tue, der Code ist kürzer und einfacher als der entsprechende Python-Code (es ist eine domänenspezifische Sprache, nachdem alle).

1 Stimmen

-c teilt der Shell (der eigentlichen Anwendung, die Sie starten) mit, dass das folgende Argument ein Befehl ist, der ausgeführt werden soll. In diesem Fall ist der Befehl eine Shell-Pipeline.

35voto

Cristian Punkte 319
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)

0 Stimmen

Ausgezeichnet! Ich habe es modifiziert, um ein in sich geschlossenes Beispiel ohne das awk-Skript zu machen, es verwendet sed: sam.nipl.net/code/python/pipeline.py

2 Stimmen

@SamWatkins: Sie brauchen nicht p1.wait() in Ihrem Code. p1.communicate() erntet das Kind Prozess.

8 Stimmen

Ist diese Antwort nicht eher pythonisch und besser? Sie verwendet nicht shell=True wie in der Dokumentation der Unterprozesse empfohlen. Ich kann nicht erkennen, warum die Antwort von @S.Lott hochgestuft wurde.

21voto

jfs Punkte 370717

Um eine Shell-Pipeline zu emulieren:

from subprocess import check_call

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

ohne die Shell aufzurufen (siehe 17.1.4.2. Ersetzen der 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 bietet etwas Syntaxzucker:

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

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

Das Analogon von:

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

ist:

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

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()

0 Stimmen

Plumbum sieht sehr hübsch aus, aber ich bin misstrauisch gegenüber "Magie". Das ist nicht Perl!

0 Stimmen

Plumbum sieht wirklich gut aus! Ich würde mir keine Sorgen über die Magie @KyleStrand - von einem kurzen Blick auf die Dokumente, Sie sind nicht verpflichtet, die "Magie" Bits verwenden, das Modul hat andere Möglichkeiten, die gleiche Sache zu tun - und ein kurzer Blick auf den Code zeigt, dass die Magie ist harmlos und eigentlich ziemlich glatt, überhaupt nicht böse.

0 Stimmen

@Tom Ich weiß nicht, das ist eine Menge Operatorüberladung mit potenziell überraschenden Bedeutungen. Ein Teil von mir liebt es, aber ich würde zögern, es irgendwo anders als in einem persönlichen Projekt zu verwenden.

13voto

Omry Yadan Punkte 27963

Die akzeptierte Antwort ist ein Ausweichen vor dem Thema. Hier ist ein Schnipsel, der die Ausgabe mehrerer Prozesse verkettet: Beachten Sie, dass er auch den (einigermaßen) äquivalenten Shell-Befehl ausgibt, damit Sie ihn ausführen und sicherstellen können, dass die Ausgabe korrekt ist.

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

0 Stimmen

Si p*.returncode 0 zurückgibt, kann ich davon ausgehen, dass kein Fehler erzeugt wurde? @Omry Yadan

0 Stimmen

Sie können sicher sein, dass es 0 zurückgegeben hat. "Fehler erzeugt" ist nicht gut definiert. es kann immer noch Dinge in stderr ausgeben.

0 Stimmen

So wie ich verstehe, muss ich stderr als auch überprüfen, wenn es leere Zeichenfolge kann ich sicher sein, dass es Fehler erzeugt wird.

2voto

geocar Punkte 8805

http://www.python.org/doc/2.5.2/lib/node535.html hat dies ziemlich gut abgedeckt. Gibt es einen Teil, den Sie nicht verstanden haben?

Ihr Programm wäre ziemlich ähnlich, aber die zweite Popen hätte stdout= in eine Datei, und Sie bräuchten nicht die Ausgabe der .communicate() .

0 Stimmen

Was ich nicht verstehe (angesichts des Beispiels in der Dokumentation) ist, wenn ich sage p2.communicate("input data"), wird das tatsächlich an p1.stdin gesendet?

0 Stimmen

Nein. Das stdin-Arg von p1 wäre auf PIPE gesetzt, und Sie würden p1.communicate('foo') schreiben und die Ergebnisse mit p2.stdout.read() abrufen.

0 Stimmen

@Leonid - Die Python-Leute sind nicht sehr gut in Sachen Abwärtskompatibilität. Sie können viele der gleichen Informationen von erhalten: docs.python.org/2/library/subprocess.html#popen-objects aber ich habe den Link trotzdem durch einen Link der Wayback Machine ersetzt.

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