wake-up-neo.net

stdout in echtzeit vom subprozess zu fangen

Ich möchte subprocess.Popen() rsync.exe in Windows und den Standardausdruck in Python drucken.

Mein Code funktioniert, aber der Fortschritt wird erst festgehalten, wenn eine Dateiübertragung erfolgt ist! Ich möchte den Fortschritt für jede Datei in Echtzeit ausdrucken.

Python 3.1 zu verwenden, seit ich gehört habe, sollte es besser mit IO umgehen.

import subprocess, time, os, sys

cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'


p = subprocess.Popen(cmd,
                     Shell=True,
                     bufsize=64,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()
64
John A

Einige Faustregeln für subprocess.

  • Niemals benutze Shell=True. Es ruft unnötigerweise einen zusätzlichen Shell-Prozess auf, um Ihr Programm aufzurufen.
  • Beim Aufruf von Prozessen werden Argumente als Listen übergeben. sys.argv in python ist eine Liste, ebenso argv in C. Sie übergeben also eine list an Popen, um Unterprozesse aufzurufen, keine Zeichenfolge.
  • Leiten Sie stderr nicht auf eine PIPE um, wenn Sie sie nicht lesen. 
  • Leiten Sie nicht stdin um, wenn Sie nicht darauf schreiben.

Beispiel:

import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]

p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, b''):
    print(">>> " + line.rstrip())

Das heißt, es ist wahrscheinlich, dass rsync seine Ausgabe puffert, wenn es feststellt, dass es mit einer Pipe anstelle eines Terminals verbunden ist. Dies ist das Standardverhalten. Wenn Programme an eine Pipe angeschlossen werden, müssen sie stdout explizit für Echtzeitergebnisse leeren, andernfalls wird die Standard-C-Bibliothek gepuffert.

Um dies zu testen, führen Sie das stattdessen aus:

cmd = [sys.executable, 'test_out.py']

und erstellen Sie eine test_out.py-Datei mit dem Inhalt:

import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")

Bei der Ausführung dieses Unterprozesses sollten Sie "Hallo" erhalten und 10 Sekunden warten, bevor Sie "Welt" angeben. Wenn dies mit dem obigen Python-Code geschieht und nicht mit rsync, bedeutet rsync selbst die Ausgabe, so dass Sie kein Glück haben.

Eine Lösung wäre, sich direkt mit einer pty zu verbinden, etwa mit pexpect.

81
nosklo

Ich weiß, das ist ein altes Thema, aber jetzt gibt es eine Lösung. Rufen Sie den Rsync mit der Option --outbuf = L auf. Beispiel:

cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
    print '>>> {}'.format(line.rstrip())
29
Elvin

Unter Linux hatte ich das gleiche Problem, die Pufferung zu beseitigen. Ich habe schließlich "stdbuf -o0" (oder "Unbuffer from erwarten") verwendet, um die PIPE-Pufferung zu beseitigen.

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout

Ich könnte dann select.select auf stdout verwenden.

Siehe auch https://unix.stackexchange.com/questions/25372/

10
Ling
for line in p.stdout:
  ...

blockiert immer bis zum nächsten Zeilenvorschub.

Für "Echtzeitverhalten" müssen Sie Folgendes tun:

while True:
  inchar = p.stdout.read(1)
  if inchar: #neither empty string nor None
    print(str(inchar), end='') #or end=None to flush immediately
  else:
    print('') #flush for implicit line-buffering
    break

Die while-Schleife bleibt stehen, wenn der untergeordnete Prozess seinen Standard beendet oder beendetread()/read(-1) würde blockieren, bis der untergeordnete Prozess seinen Standard beendet oder beendet wurde.

7
IBue

Ihr Problem ist:

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()

der Iterator selbst hat eine zusätzliche Pufferung.

Versuchen Sie es so:

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

Sie können stdout nicht dazu bringen, ungepuffert in eine Pipe zu drucken (es sei denn, Sie können das Programm, das in stdout gedruckt wird, erneut schreiben).

Leiten Sie stdout zu sterr um, was nicht gepuffert wird. '<cmd> 1>&2' sollte es tun. Öffnen Sie den Prozess wie folgt: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Sie können nicht zwischen stdout und stderr unterscheiden, aber Sie erhalten sofort alle Ausgaben.

Ich hoffe, das hilft jedem, der dieses Problem angeht.

5
Erik

Um das Zwischenspeichern der Ausgabe zu vermeiden, sollten Sie pexpect ausprobieren. 

child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
    try:
        child.expect('\n')
        print(child.before)
    except pexpect.EOF:
        break

PS: Ich weiß, dass diese Frage ziemlich alt ist und immer noch die Lösung bietet, die für mich funktioniert hat.

PPS: erhielt diese Antwort aus einer anderen Frage

2
nithin
    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)

Ich schreibe eine GUI für Rsync in Python und habe die gleichen Probleme. Dieses Problem hat mich mehrere Tage lang beschäftigt, bis ich das in pyDoc finde.

Wenn universal_newlines den Wert True hat, werden die Dateiobjekte stdout und stderr als Textdateien im Modus für universelle Zeilenumbrüche geöffnet. Zeilen können mit '\ n', der End-of-Line-Konvention von Unix, '\ r', der alten Macintosh-Konvention oder '\ r\n', der Windows-Konvention, beendet werden. Alle diese externen Repräsentationen werden vom Python-Programm als '\ n' betrachtet.

Es scheint, dass rsync '\ r' ausgibt, wenn gerade übersetzt wird.

2
xmc

Ändern Sie das stdout aus dem rsync-Prozess, um nicht gepuffert zu werden.

p = subprocess.Popen(cmd,
                     Shell=True,
                     bufsize=0,  # 0=unbuffered, 1=line-buffered, else buffer-size
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)
2
Will

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.

1
Albert

Mir ist aufgefallen, dass die Verwendung einer temporären Datei als Zwischenspeicher nicht erwähnt wird. Im Folgenden werden die Pufferungsprobleme durch Ausgabe in eine temporäre Datei umgangen und Sie können die von rsync kommenden Daten analysieren, ohne eine Verbindung zu einem pty herzustellen. Ich habe Folgendes auf einer Linux-Box getestet, und die Ausgabe von rsync unterscheidet sich zwischen den Plattformen. Daher können die regulären Ausdrücke zum Analysieren der Ausgabe variieren:

import subprocess, time, tempfile, re

pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]

p = subprocess.Popen(cmd, stdout=pipe_output, 
                     stderr=subprocess.STDOUT)
while p.poll() is None:
    # p.poll() returns None while the program is still running
    # sleep for 1 second
    time.sleep(1)
    last_line =  open(file_name).readlines()
    # it's possible that it hasn't output yet, so continue
    if len(last_line) == 0: continue
    last_line = last_line[-1]
    # Matching to "[bytes downloaded]  number%  [speed] number:number:number"
    match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
    if not match_it: continue
    # in this case, the percentage is stored in match_it.group(1), 
    # time in match_it.group(2).  We could do something with it here...
0
MikeGM