ловить стандартный вывод в реальном времени из подпроцесса - PullRequest
69 голосов
/ 22 октября 2009

Я хочу subprocess.Popen() rsync.exe в Windows и распечатать стандартный вывод на 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()

Ответы [ 12 ]

87 голосов
/ 22 октября 2009

Некоторые правила для 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 буферизует свой вывод, когда обнаружит, что он подключен к каналу, а не к терминалу. Это поведение по умолчанию - при подключении к каналу программы должны явно сбрасывать стандартный вывод для результатов в реальном времени, в противном случае стандартная библиотека C будет буферизоваться.

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

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

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

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

Выполнение этого подпроцесса должно дать вам «Hello» и подождать 10 секунд, прежде чем дать «World». Если это происходит с приведенным выше кодом Python, а не с rsync, это означает, что rsync само буферизует вывод, так что вам не повезло.

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

32 голосов
/ 03 марта 2016

Я знаю, что это старая тема, но сейчас есть решение. Вызовите 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())
11 голосов
/ 06 ноября 2016

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

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

Я мог бы затем использовать select.select на стандартный вывод.

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

8 голосов
/ 12 февраля 2013
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 прерывается, когда дочерний процесс закрывает свой стандартный вывод или завершает работу. read()/read(-1) будет блокироваться, пока дочерний процесс не закроет свой стандартный вывод или не завершится.

7 голосов
/ 02 мая 2013

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

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

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

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

while True:
  line = p.stdout.readline()
  if not line:
     break
  print line
5 голосов
/ 10 августа 2012

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

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

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

2 голосов
/ 11 сентября 2017
    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)

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

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

Кажется, что rsync выведет '\ r', когда будет выполняться перевод.

2 голосов
/ 14 августа 2016

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

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

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

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

2 голосов
/ 22 октября 2009

Измените стандартный вывод из процесса 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)
1 голос
/ 17 октября 2018

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...