Подтвердить что ты не робот

Ловить stdout в реальном времени из подпроцесса

Я хочу subprocess.Popen() rsync.exe в Windows и распечатать stdout в Python.

Мой код работает, но он не догоняет прогресс, пока не будет выполнена передача файлов! Я хочу напечатать прогресс для каждого файла в реальном времени.

Использование Python 3.1 теперь, так как я слышал, что лучше работать с IO.

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

Ответ 1

Некоторые эмпирические правила для subprocess.

  • Никогда используйте shell=True. Он бесполезно вызывает дополнительный процесс оболочки для вызова вашей программы.
  • При вызове процессов аргументы передаются как списки. sys.argv в python - это список, и поэтому argv в C. Поэтому вы передаете список в Popen для вызова подпроцессов, а не строки.
  • Не переадресовывайте stderr в PIPE, когда вы его не читаете.
  • Не переадресовывайте stdin, когда вы не пишете на него.

Пример:

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

Тем не менее, вероятно, что rsync буферизует свой вывод, когда обнаруживает, что он подключен к каналу, а не к терминалу. Это поведение по умолчанию - при подключении к каналу программы должны явно скрывать stdout для результатов в реальном времени, иначе стандартная библиотека C буферизует.

Чтобы проверить это, попробуйте запустить это вместо:

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

и создайте файл test_out.py с содержимым:

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

Выполнение этого подпроцесса должно дать вам "Привет" и подождать 10 секунд, прежде чем дать "Мир". Если это происходит с кодом python выше, а не с rsync, это означает, что rsync сам является буферизацией, поэтому вам не повезло.

Решение заключалось бы в непосредственном подключении к pty, используя что-то вроде pexpect.

Ответ 2

Я знаю, что это старая тема, но сейчас есть решение. Вызовите rsync с опцией --outbuf = L. Пример:

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

Ответ 3

В Linux у меня была такая же проблема, как избавиться от буферизации. Я, наконец, использовал "stdbuf -o0" (или unbuffer from expect), чтобы избавиться от буферизации PIPE.

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

Тогда я мог бы использовать select.select на stdout.

См. также https://unix.stackexchange.com/questions/25372/

Ответ 4

for line in p.stdout:
  ...

всегда блокируется до следующей строки.

Для поведения "в реальном времени" вы должны сделать что-то вроде этого:

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

Цикл while остается, когда дочерний процесс закрывает свой stdout или завершает работу. read()/read(-1) будет блокироваться до тех пор, пока дочерний процесс не завершит свою работу или не выйдет.

Ответ 5

Вы не можете заставить stdout печатать небуферизованные в трубе (если вы не можете переписать программу, которая печатает на stdout), так что вот мое решение:

Перенаправить stdout в sterr, который не буферизируется. '<cmd> 1>&2' должен это сделать. Откройте процесс следующим образом: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Вы не можете отличить от stdout или stderr, но вы получаете все выходные данные.

Надеюсь, это поможет любому, кто решает эту проблему.

Ответ 6

Ваша проблема:

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

сам итератор имеет дополнительную буферизацию.

Попробуйте сделать так:

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

Ответ 7

Измените stdout из процесса rsync, чтобы он не был загружен.

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)

Ответ 8

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

Я пишу GUI для rsync в python и имею те же самые пробники. Эта проблема беспокоила меня в течение нескольких дней, пока я не найду это в pyDoc.

Если universal_newlines имеет значение True, объекты файлов stdout и stderr открываются в виде текстовых файлов в универсальном режиме новых строк. Строки могут быть прерваны любым из "\n", окончательного соглашения Unix, "\ r", старого соглашения Macintosh или "\ r\n", соглашения Windows. Все эти внешние представления рассматриваются как "\n" в программе Python.

Кажется, что rsync выведет '\ r', когда переводится.

Ответ 9

Чтобы избежать кэширования вывода, вы можете попробовать pexpect,

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

PS: Я знаю, что этот вопрос довольно старый, все еще предоставляя решение, которое сработало для меня.

PPS: получил ответ от другого вопроса

Ответ 10

Я заметил, что не упоминается использование временного файла как промежуточного. Следующие проблемы вокруг буферизации выводятся во временный файл и позволяют анализировать данные, поступающие из rsync, без подключения к pty. Я протестировал следующее в linux box, и выход rsync имеет тенденцию отличаться на разных платформах, поэтому регулярные выражения для анализа вывода могут отличаться:

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 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...

Ответ 11

Используйте | tee для перенаправления stdout в файл с именем out.txt при отображении stdout в режиме реального времени на терминале

import subprocess, time, os, sys

cmd = "rsync.exe -vaz -P source/ dest/ | tee out.txt"

p, line = True, 'start'

p = subprocess.Popen(cmd,
                 shell=True)

p.wait()

Вы можете получить stdout из файла out.txt после подпроцесса.

# Get stdout from file out.txt
f = open('out.txt')
out = f.read()
f.close()

Ответ 12

В зависимости от варианта использования вы также можете отключить буферизацию в самом подпроцессе.

Если подпроцесс будет процессом Python, вы можете сделать это до вызова:

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

Или, в качестве альтернативы, передайте это в аргументе env Popen.

В противном случае, если вы работаете в Linux/Unix, вы можете использовать инструмент stdbuf. Например, как:

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

Смотрите также здесь о stdbuf или других опциях.

Ответ 13

В Python 3 здесь представлено решение, которое выводит команду из командной строки и доставляет красиво декодированные строки в реальном времени по мере их поступления.

Получатель (receiver.py):

import subprocess
import sys

cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
for line in p.stdout:
    print("received: {}".format(line.rstrip().decode("utf-8")))

Пример простой программы, которая может генерировать вывод в реальном времени (dummy_out.py):

import time
import sys

for i in range(5):
    print("hello {}".format(i))
    sys.stdout.flush()  
    time.sleep(1)

Выход:

$python receiver.py python dummy_out.py
received: hello 0
received: hello 1
received: hello 2
received: hello 3
received: hello 4