webentwicklung-frage-antwort-db.com.de

Echtzeitausgabe mit Subprozess abrufen

Ich versuche, ein Wrapper-Skript für ein Befehlszeilenprogramm (svnadmin verify) zu schreiben, das einen Nice-Statusanzeiger für die Operation anzeigt. Dies erfordert, dass ich jede Zeile der Ausgabe des umschlossenen Programms sehen kann, sobald es ausgegeben wird.

Ich dachte mir, ich würde das Programm einfach mit subprocess.Popen ausführen, stdout=PIPE verwenden, dann jede Zeile so lesen, wie sie hereinkam, und entsprechend handeln. Wenn ich jedoch den folgenden Code ausführte, schien die Ausgabe irgendwo zwischengespeichert zu sein, sodass sie in zwei Abschnitten (Zeilen 1 bis 332) und dann 333 bis 439 (letzte Zeile der Ausgabe) angezeigt wurde.

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, Shell = True)
for line in p.stdout:
    print line.replace('\n', '')

Nachdem ich mir die Dokumentation zum Unterprozess ein wenig angesehen hatte, entdeckte ich den Parameter bufsize auf Popen. Daher versuchte ich, bufsize auf 1 (Puffer pro Zeile) und 0 (kein Puffer) zu setzen. Keiner der beiden Werte schien jedoch die Art, wie die Zeilen geliefert wurden, zu ändern .

An diesem Punkt fing ich an nach Strohhalmen zu greifen, also schrieb ich die folgende Ausgabeschleife:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

bekam aber das gleiche Ergebnis.

Ist es möglich, eine Echtzeit-Programmausgabe eines mit Subprozess ausgeführten Programms zu erhalten? Gibt es eine andere Option in Python, die vorwärtskompatibel ist (nicht exec*)?

109
Chris Lieb

Ich habe dies aus irgendeinem Grund ausprobiert, und zwar aus irgendeinem Grund

for line in p.stdout:
  ...

puffer aggressiv die Variante

while True:
  line = p.stdout.readline()
  if not line: break
  ...

nicht. Anscheinend ist dies ein bekannter Fehler: http://bugs.python.org/issue3907 (Das Problem ist jetzt "Closed" seit dem 29. August 2018)

70
Dave
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()
34
Corey Goldberg

Sie können dies versuchen:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Wenn Sie readline anstelle von read verwenden, kann es vorkommen, dass die Eingabemeldung nicht gedruckt wird. Versuchen Sie es mit einem Befehl, der eine Inline-Eingabe erfordert, und überzeugen Sie sich selbst.

18
Nadia Alramli

Sie können die Unterprozessausgabe direkt an die Streams weiterleiten. Vereinfachtes Beispiel:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
9
Aidan Feldman

Ich bin vor einiger Zeit auf dasselbe Problem gestoßen. Meine Lösung bestand darin, die Methode read zu iterieren, die auch dann sofort wieder angezeigt wird, wenn der Unterprozess nicht abgeschlossen ist usw.

3
Eli Courtwright

Sie können einen Iterator für jedes Byte in der Ausgabe des Unterprozesses verwenden. Dies ermöglicht die Inline-Aktualisierung (Zeilen, die mit '\ r' enden, um die vorherige Ausgabezeile zu überschreiben) aus dem Unterprozess:

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")
2
rhyno183

Problem mit der Echtzeitausgabe behoben: Ich habe in Python ein ähnliches Problem festgestellt, während ich die Echtzeitausgabe von c-Programm aufzeichnete. Ich fügte " fflush (stdout) ;" in meinem C-Code. Es hat für mich funktioniert. Hier ist der Code 

<< C-Programm >>

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

<< Python-Programm >>

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

<< AUSGABE >> Drucken: Zählen 1 Drucken: Zählen 2 Drucken: Zählen 3

Ich hoffe es hilft.

~ Sairam

2
sairam

Je nach Anwendungsfall möchten Sie möglicherweise auch die Pufferung im Unterprozess selbst deaktivieren.

Wenn der Unterprozess ein Python-Prozess ist, können Sie dies vor dem Aufruf tun:

os.environ["PYTHONUNBUFFERED"] = "1"

Oder übergeben Sie dies alternativ im Argument env an Popen.

Andernfalls können Sie unter Linux/Unix das Tool stdbuf verwenden. Z.B. mögen:

cmd = ["stdbuf", "-oL"] + cmd

Siehe auch hier über stdbuf oder andere Optionen.

(Siehe auch hier für dieselbe Antwort.)

1
Albert

Ich habe diese Lösung verwendet, um Echtzeitausgabe für einen Teilprozess zu erhalten. Diese Schleife wird angehalten, sobald der Prozess abgeschlossen ist und keine break-Anweisung oder eine mögliche Endlosschleife erforderlich ist. 

sub_process = subprocess.Popen(my_command, close_fds=True, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()
1
Jason Hedlund

Wenn Sie pexpect [ http://www.noah.org/wiki/Pexpect ] mit nicht blockierenden Readlines verwenden, wird dieses Problem behoben. Dies ist auf die Tatsache zurückzuführen, dass Pipes gepuffert werden. Die Ausgabe Ihrer App wird also von der Pipe gepuffert. Daher können Sie nicht zu dieser Ausgabe gelangen, bis der Puffer gefüllt ist oder der Prozess abbricht.

1
Gabe

Dies ist das Grundgerüst, das ich immer dazu benutze. Es vereinfacht das Implementieren von Timeouts und kann mit unvermeidlichen Hängeprozessen umgehen.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           Shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()
0
Badslacks

(Diese Lösung wurde mit Python 2.7.15 getestet.)
Sie müssen nur sys.stdout.flush () nach jeder Zeile lesen/schreiben:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
0
dan

Komplette Lösung:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()
0
Andres Buritica

Diese "Plug-and-Play" -Funktion gefunden hier . Lief wie am Schnürchen!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)
0
Deena

Der Streaming-Subprozess stdin und stdout mit asyncio in Python Blogbeitrag von Kevin McCarthy zeigt, wie es mit asyncio geht:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __== "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
0
Pablo