Um Programme aus meinen Python-Skripten zu starten, verwende ich die folgende Methode:
def execute(command):
process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
Wenn ich also einen Prozess wie Process.execute("mvn clean install")
starte, wartet mein Programm, bis der Prozess abgeschlossen ist, und erst dann bekomme ich die vollständige Ausgabe meines Programms. Das ist ärgerlich, wenn ich einen Prozess durchführe, der einige Zeit in Anspruch nimmt.
Kann ich mein Programm die Prozessausgabe Zeile für Zeile schreiben lassen, indem die Prozessausgabe abgefragt wird, bevor sie in einer Schleife oder etwas beendet wird?
** [EDIT] Es tut mir leid, dass ich nicht sehr gut nach dieser Frage gesucht habe. Einfädeln ist eigentlich der Schlüssel. Hier ein Beispiel gefunden, das zeigt, wie es geht: ** Python Subprocess.Popen aus einem Thread
Sie können iter verwenden, um Zeilen zu verarbeiten, sobald der Befehl sie ausgibt: lines = iter(fd.readline, "")
. Hier ist ein vollständiges Beispiel, das einen typischen Anwendungsfall zeigt (dank @jfs für das Helfen):
from __future__ import print_function # Only Python 2.x
import subprocess
def execute(cmd):
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
for stdout_line in iter(popen.stdout.readline, ""):
yield stdout_line
popen.stdout.close()
return_code = popen.wait()
if return_code:
raise subprocess.CalledProcessError(return_code, cmd)
# Example
for path in execute(["locate", "a"]):
print(path, end="")
Ok, ich habe es geschafft, es ohne Threads zu lösen (alle Vorschläge, warum die Verwendung von Threads besser wäre, werden geschätzt), indem ein Ausschnitt aus dieser Frage verwendet wird. Stdout eines Unterprozesses abfangen, während er läuft
def execute(command):
process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Poll process for new output until finished
while True:
nextline = process.stdout.readline()
if nextline == '' and process.poll() is not None:
break
sys.stdout.write(nextline)
sys.stdout.flush()
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
So drucken Sie die Ausgabe des Unterprozesses Zeile für Zeile, sobald der Puffer stdout in Python 3 geleert ist:
from subprocess import Popen, PIPE, CalledProcessError
with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
for line in p.stdout:
print(line, end='') # process line here
if p.returncode != 0:
raise CalledProcessError(p.returncode, p.args)
Hinweis: Sie brauchen p.poll()
nicht - die Schleife endet, wenn eof erreicht ist. Und Sie brauchen iter(p.stdout.readline, '')
nicht - der Read-Ahead-Fehler wurde in Python 3 behoben.
Siehe auch Python: Lese-Streaming-Eingabe von subprocess.communicate () .
@tokland
sie haben Ihren Code ausprobiert und für 3.4 und Windows korrigiert dir.cmd ist ein einfacher Befehl, der als cmd-Datei gespeichert wird
import subprocess
c = "dir.cmd"
def execute(command):
popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
lines_iterator = iter(popen.stdout.readline, b"")
while popen.poll() is None:
for line in lines_iterator:
nline = line.rstrip()
print(nline.decode("latin"), end = "\r\n",flush =True) # yield line
execute(c)
Jeder, der versucht, die Antworten auf diese Frage zu erhalten, um den Standardwert aus einem Python-Skript zu erhalten, stellt fest, dass Python seinen Standardwert puffert. Daher kann es eine Weile dauern, bis der Standardwert angezeigt wird.
Sie können dies korrigieren, indem Sie nach jedem stdout-Schreiben im Zielskript Folgendes hinzufügen:
sys.stdout.flush()
In Python> = 3.5 funktioniert subprocess.run
für mich:
import subprocess
cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, Shell=True)
(Das Abrufen der Ausgabe während der Ausführung funktioniert auch ohne Shell=True
) https://docs.python.org/3/library/subprocess.html#subprocess.run
Für den Fall, dass jemand mit Threads gleichzeitig stdout
und stderr
lesen möchte, habe ich Folgendes gefunden:
import threading
import subprocess
import Queue
class AsyncLineReader(threading.Thread):
def __init__(self, fd, outputQueue):
threading.Thread.__init__(self)
assert isinstance(outputQueue, Queue.Queue)
assert callable(fd.readline)
self.fd = fd
self.outputQueue = outputQueue
def run(self):
map(self.outputQueue.put, iter(self.fd.readline, ''))
def eof(self):
return not self.is_alive() and self.outputQueue.empty()
@classmethod
def getForFd(cls, fd, start=True):
queue = Queue.Queue()
reader = cls(fd, queue)
if start:
reader.start()
return reader, queue
process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)
# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
# Process all available lines from the stdout Queue.
while not stdoutQueue.empty():
line = stdoutQueue.get()
print 'Received stdout: ' + repr(line)
# Do stuff with stdout line.
# Process all available lines from the stderr Queue.
while not stderrQueue.empty():
line = stderrQueue.get()
print 'Received stderr: ' + repr(line)
# Do stuff with stderr line.
# Sleep for a short time to avoid excessive CPU use while waiting for data.
sleep(0.05)
print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()
# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()
print "Waiting for process to exit..."
returnCode = process.wait()
if returnCode != 0:
raise subprocess.CalledProcessError(returnCode, command)
Ich wollte das nur teilen, da ich bei dieser Frage versuche, etwas Ähnliches zu tun, aber keine der Antworten löste mein Problem. Hoffentlich hilft es jemandem!
Beachten Sie, dass in meinem Anwendungsfall ein externer Prozess den Prozess beendet, den wir Popen()
verwenden.
Dieser PoC liest ständig die Ausgabe eines Prozesses und kann bei Bedarf aufgerufen werden. Nur das letzte Ergebnis wird beibehalten, alle anderen Ausgaben werden verworfen und verhindern, dass der PIPE zu viel Speicherplatz bekommt:
import subprocess
import time
import threading
import Queue
class FlushPipe(object):
def __init__(self):
self.command = ['python', './print_date.py']
self.process = None
self.process_output = Queue.LifoQueue(0)
self.capture_output = threading.Thread(target=self.output_reader)
def output_reader(self):
for line in iter(self.process.stdout.readline, b''):
self.process_output.put_nowait(line)
def start_process(self):
self.process = subprocess.Popen(self.command,
stdout=subprocess.PIPE)
self.capture_output.start()
def get_output_for_processing(self):
line = self.process_output.get()
print ">>>" + line
if __== "__main__":
flush_pipe = FlushPipe()
flush_pipe.start_process()
now = time.time()
while time.time() - now < 10:
flush_pipe.get_output_for_processing()
time.sleep(2.5)
flush_pipe.capture_output.join(timeout=0.001)
flush_pipe.process.kill()
print_date.py
#!/usr/bin/env python
import time
if __== "__main__":
while True:
print str(time.time())
time.sleep(0.01)
ausgabe: Sie können deutlich sehen, dass nur im ~ 2,5s-Intervall nichts dazwischen liegt.
>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
Um die ursprüngliche Frage zu beantworten, lenken Sie IMO am besten einfach mit dem Unterprozess stdout
direkt zur stdout
Ihres Programms. (Optional kann dies auch für stderr
wie im folgenden Beispiel ausgeführt werden.)
p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
Dies funktioniert zumindest in Python3.4
import subprocess
process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
print(line.decode().strip())
Keine der Antworten hier hat alle meine Bedürfnisse angesprochen.
Ein kleiner Hintergrund: Ich verwalte mit einem ThreadPoolExecutor einen Pool von Threads, wobei jeder einen Unterprozess startet und diese gleichzeitig ausführt. (In Python2.7, aber dies sollte auch in neueren 3.x funktionieren). Ich möchte keine Threads nur zum Sammeln von Ausgaben verwenden, da ich so viele wie möglich für andere Zwecke zur Verfügung haben möchte (ein Pool von 20 Prozessen würde 40 Threads nur zum Ausführen verwenden; 1 für den Prozessthread und 1 für stdout ... und mehr, wenn Sie stderr wollen, denke ich)
Ich ziehe eine Menge Ausnahmen zurück und solche hier, also ist dies basierend auf Code, der in der Produktion funktioniert. Hoffentlich habe ich es beim Kopieren und Einfügen nicht ruiniert. Auch Feedback sehr willkommen!
import time
import fcntl
import subprocess
import time
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
"""A little inline function to handle the stdout business. """
# fcntl makes readline non-blocking so it raises an IOError when empty
try:
for s in iter(proc_stream.readline, ''): # replace '' with b'' for Python 3
my_buffer.append(s)
if echo_streams:
sys.stdout.write(s)
if log_file:
log_file.write(s)
except IOError:
pass
# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
handle_stdout(proc_stdout, stdout_parts)
# ...Check for other things here...
# For example, check a multiprocessor.Value('b') to proc.kill()
time.sleep(0.01)
# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)
stdout_str = "".join(stdout_parts) # Just to demo
Ich bin sicher, dass hier zusätzliche Kosten anfallen, aber das ist in meinem Fall kein Problem. Funktionell macht es was ich brauche. Das einzige, was ich nicht gelöst habe, ist, warum dies für Protokollnachrichten perfekt funktioniert, aber ich sehe, dass einige print
Nachrichten später und auf einmal angezeigt werden.