2009-10-22 2 views
58

Я хочу, чтобы subprocess.Popen() rsync.exe в Windows и распечатать stdout в Python.catching stdout в режиме реального времени из подпроцесса

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

Использование 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() 
+2

Дубликат: http://stackoverflow.com/questions/1085071/real-time-intercepting-of-stdout-from-another-process-in-python, http://stackoverflow.com/questions/ 874815/how-do-i-get-real-time-information-back-from-a-subprocess-popen-in-python-2-5, http://stackoverflow.com/questions/527197/intercepting-stdout- a-sub-process-while-it-running –

+1

(Исходя из google?) все PIPE будут заторможены, когда один из буферов PIPE будет заполнен и не будет прочитан. например stdout, когда stderr заполнен. Никогда не пропускайте PIPE, которую вы не собираетесь читать. –

+0

Может кто-нибудь объяснить, почему вы не можете просто установить stdout в sys.stdout вместо subprocess.PIPE? – Mike

ответ

2

Измените стандартный вывод из процесса 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) 
+3

Буферизация происходит на стороне rsync, изменение атрибута bufsize на стороне python не поможет. – nosklo

+12

Для любого другого пользователя, ответ nosklo полностью неверен: отображение прогресса rsync не буферизировано; реальная проблема заключается в том, что подпроцесс возвращает объект файла, а интерфейс итератора файла имеет плохо документированный внутренний буфер, даже с bufsize = 0, что требует повторного вызова readline(), если вам нужны результаты до заполнения буфера. –

71

Некоторые эмпирические правила для 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.

+6

'shell = False' правильная вещь, когда вы создаете командную строку, особенно из введенных пользователем данных. Но, тем не менее, 'shell = True' тоже полезен, когда вы получаете всю командную строку из надежного источника (например, жестко закодированного в скрипте). –

+9

@Denis Otkidach: Я не думаю, что это требует использования 'shell = True'. Подумайте об этом - вы вызываете другой процесс в своей ОС, включая распределение памяти, использование диска, планирование процессора, только для ** разделения строки **! И тот, с кем ты присоединился! Вы можете разделить на python, но проще писать каждый параметр отдельно. Кроме того, использование списка означает, что вам не нужно избегать специальных символов оболочки: пробелы, ';', '>', '<', '&' .. Ваши параметры могут содержать эти символы, и вам не нужно беспокоиться ! Я не вижу причины использовать 'shell = True', действительно, если вы не используете команду только для оболочки. – nosklo

+0

nosklo, что должно быть: р = subprocess.Popen (CMD, STDOUT = subprocess.PIPE, STDERR = subprocess.STDOUT) –

0

Я заметил, что не упоминается использование временного файла в качестве промежуточного. Следующие проблемы вокруг буферизации выводятся во временный файл и позволяют анализировать данные, поступающие из rsync, без подключения к pty.Я проверил следующее на коробке Linux, а выход 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'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

это не в режиме реального времени. Файл не решает проблему буферизации на стороне rsync. – jfs

+0

tempfile.TemporaryFile может удалить себя для более простой очистки в случае исключений – jfs

+0

не перенаправить stderr = STDOUT это скрыть ошибки в этом случае – jfs

7

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

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

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

+3

Вы пробовали? Потому что это не работает. Если stdout буферизуется в этом процессе, он не будет перенаправлен на stderr так же, как он не перенаправлен на PIPE или файл. –

+3

Это просто неправильно. Буферизация stdout происходит внутри самой программы. Синтаксис оболочки '1> & 2' просто изменяет файлы, на которые указывает файловые дескрипторы перед запуском программы. Сама программа не может различать перенаправление stdout на stderr ('1> & 2') или наоборот (' 2> & 1'), поэтому это не повлияет на поведение буферизации программы. И в любом случае '1 Синтаксис> & 2' интерпретируется оболочкой. 'subprocess.Popen (' 1> & 2', stderr = subprocess.PIPE)' будет терпеть неудачу, потому что вы не указали 'shell = True'. –

+0

В случае, если люди будут читать это: я пытался использовать stderr вместо stdout, он показывает то же самое поведение. – martinthenext

7
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 

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

+1

'inchar' никогда' '' '' '' if not inchar: 'вместо (' read() 'возвращает пустую строку в EOF). btw, хуже. 'for line in p.stdout' не печатает даже полные строки в реальном времени в Python 2 (' для строки в 'iter (p.stdout.readline, '')' может использоваться вместо). – jfs

+1

Я тестировал это с помощью python 3.4 на osx, и он не работает. – qed

+1

@qed: 'для строки в p.stdout:' работает на Python 3. Не забудьте понять разницу между '' ''(строка Unicode) и' b''' (байты). См. [Python: чтение потокового ввода из subprocess.communicate()] (http://stackoverflow.com/a/17698359/4279) – jfs

5

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

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

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

Try делать так:

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

Я знаю, что это старая тема, но есть решение теперь. Вызовите 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()) 
+1

Это работает и должно быть одобрено для сохранения будущих читателей от прокрутки по всему диалоговому окну. – VectorVictor

+0

@ VectorVictor Это не объясняет, что происходит, и почему это происходит. Это может быть, что ваша программа не работает, пока: 1. Вы добавляете 'preexec_fn = os.setpgrp', чтобы сделать программу выжить его родительский сценарий 2. Вы не читать из трубы текущего процесса 3. выходы процесса много данных, заполнение трубы 4. Вы застряли в течение нескольких часов, пытаясь понять, почему программа, с которой вы работаете, завершает работу * после некоторого случайного количества времени *. Ответ от @nosklo мне очень помог. – danuker

1

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

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

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

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

8

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

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

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

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

+1

Для тех, кто пытается захватить исходный код кода C из Python, я могу подтвердить, что это решение было единственным, что работало для меня. Чтобы быть ясным, я говорю о добавлении 'stdbuf', '-o0' в мой существующий список команд в Popen. – Reckless

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

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

If universal_newlines is True, the file objects stdout and stderr are opened as text files in universal newlines mode. Lines may be terminated by any of '\n', the Unix end-of-line convention, '\r', the old Macintosh convention or '\r\n', the Windows convention. All of these external representations are seen as '\n' by the Python program.

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

0

Использование | tee перенаправить стандартный вывод в файл с именем 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() 

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

# Get stdout from file out.txt 
f = open('out.txt') 
out = f.read() 
f.close() 
Смежные вопросы