Я хочу subprocess.Popen()
запустить rsync.exe в Windows и распечатать стандартный вывод на Python.
Мой код работает, но не отслеживает прогресс, пока не будет выполнена передача файла! Я хочу распечатать прогресс для каждого файла в реальном времени.
Теперь я использую Python 3.1, поскольку я слышал, что он должен лучше обрабатывать ввод-вывод.
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()
python
subprocess
stdout
Джон А
источник
источник
Ответы:
Некоторые практические правила для
subprocess
.shell=True
. Он без нужды вызывает дополнительный процесс оболочки для вызова вашей программы.sys.argv
в Python список, и поэтомуargv
в С. Таким образом , вы передаете список , чтобы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
сам буферизует вывод, поэтому вам не повезло.Решением было бы подключиться напрямую к a
pty
, используя что-то вродеpexpect
.источник
shell=False
это правильно, когда вы строите командную строку, особенно из введенных пользователем данных. Но, тем не менее,shell=True
это также полезно, когда вы получаете всю командную строку из надежного источника (например, жестко запрограммированного в сценарии).shell=True
. Подумайте об этом - вы вызываете другой процесс в своей ОС, включающий выделение памяти, использование диска, планирование процессора, просто чтобы разбить строку ! И к одному вы присоединились !! Вы можете разделить на Python, но в любом случае проще писать каждый параметр отдельно. Кроме того , используя список означает , что вы не должны экранировать специальные символы оболочки: пространства,;
,>
,<
,&
.. Ваши параметры могут содержать эти символы , и вы не должны беспокоиться! Наshell=True
самом деле, я не вижу причин для использования , если только вы не запускаете команду только для оболочки.csv
модуля. Но в качестве примера ваш конвейер в python будет выглядеть следующим образом:p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print result
Обратите внимание, что теперь, когда оболочка не задействована, вы можете работать с длинными именами файлов и специальными символами оболочки без необходимости экранирования. Кроме того, это намного быстрее, так как на один процесс меньше.for line in iter(p.stdout.readline, b'')
вместоfor line in p.stdout
Python 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())
источник
preexec_fn=os.setpgrp
чтобы программа выжила в родительском скрипте 2. вы пропустили чтение из конвейера процесса 3. процесс выдает много данных, заполняя канал 4. вы застряли на несколько часов , пытаясь выяснить, почему программа, которую вы запускаете, завершает работу через произвольный промежуток времени . Ответ от @nosklo мне очень помог.В Linux у меня была такая же проблема с избавлением от буферизации. Наконец, я использовал "stdbuf -o0" (или unbuffer from expect), чтобы избавиться от буферизации PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout
Затем я мог бы использовать select.select на stdout.
См. Также /unix/25372/
источник
stdbuf -o0
оказался действительно полезным с кучей тестов pytest / pytest-bdd, которые я написал, которые порождают приложение C ++ и проверяют, что оно генерирует определенные операторы журнала. Безstdbuf -o0
них этим тестам потребовалось 7 секунд для получения (буферизованного) вывода из программы C ++. Теперь они запускаются практически мгновенно!pytest
, я не мог получить его результат.stdbuf
Имеет ли это.В зависимости от варианта использования вы также можете отключить буферизацию в самом подпроцессе.
Если подпроцессом будет процесс Python, вы можете сделать это до вызова:
os.environ["PYTHONUNBUFFERED"] = "1"
Или, в качестве альтернативы, передайте это в
env
аргументеPopen
.В противном случае, если вы используете Linux / Unix, вы можете использовать этот
stdbuf
инструмент. Например:cmd = ["stdbuf", "-oL"] + cmd
См. Также здесь about
stdbuf
или другие варианты.источник
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)
будет блокироваться до тех пор, пока дочерний процесс не закроет свой стандартный вывод или не завершится.источник
inchar
никогда неNone
используетсяif not inchar:
вместо этого (read()
возвращает пустую строку в EOF). кстати, это еще хужеfor line in p.stdout
, не печатает даже полные строки в реальном времени в Python 2 (for line in
вместо этого можно использовать iter (p.stdout.readline, ') `).for line in p.stdout:
работает на Python 3. Убедитесь, что вы понимаете разницу между''
(строка Unicode) иb''
(байтами). См. Python: чтение потокового ввода из subprocess.communicate ()Ваша проблема:
for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush()
сам итератор имеет дополнительную буферизацию.
Попробуйте сделать так:
while True: line = p.stdout.readline() if not line: break print line
источник
Вы не можете заставить stdout печатать без буферизации в канал (если вы не можете переписать программу, которая печатает на stdout), поэтому вот мое решение:
Перенаправьте stdout на sterr, который не буферизуется.
'<cmd> 1>&2'
должен это сделать. Откройте процесс следующим образом:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
вы не можете отличить от stdout или stderr, но вы получите весь вывод сразу.
Надеюсь, это поможет любому, кто решит эту проблему.
источник
1>&2
просто изменяет файлы, на которые указывают дескрипторы файлов, перед запуском программы. Сама программа не может различить перенаправление stdout на stderr (1>&2
) или наоборот (2>&1
), поэтому это не повлияет на поведение программы при буферизации. В любом случае1>&2
синтаксис интерпретируется оболочкой.subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
потерпит неудачу, потому что вы не указалиshell=True
.Измените стандартный вывод процесса 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)
источник
Чтобы избежать кеширования вывода, вы можете попробовать pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None) while True: try: child.expect('\n') print(child.before) except pexpect.EOF: break
PS : Я знаю, что этот вопрос довольно старый, но он все еще предлагает решение, которое сработало для меня.
PPS : получил этот ответ из другого вопроса
источник
p = subprocess.Popen(command, bufsize=0, universal_newlines=True)
Я пишу графический интерфейс для rsync на Python и имею те же проблемы. Эта проблема беспокоила меня несколько дней, пока я не нашел ее в pyDoc.
Кажется, что rsync будет выводить '\ r', когда выполняется перевод.
источник
Я заметил, что нет упоминания об использовании временного файла в качестве промежуточного. Следующее позволяет обойти проблемы с буферизацией путем вывода во временный файл и позволяет анализировать данные, поступающие из 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...
источник
while not p.poll()
приводит к бесконечному циклу, если подпроцесс успешно завершается с 0, используйтеp.poll() is None
вместо этогоopen(file_name)
может выйти из строяcommand_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv
и вызываю:popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
и теперь я могу читать без какой-либо буферизацииесли вы запустите что-то подобное в потоке и сохраните свойство ffmpeg_time в свойстве метода, чтобы вы могли получить к нему доступ, он будет работать очень хорошо, я получаю такие выходные данные: вывод будет таким, как если бы вы использовали потоки в tkinter
input = 'path/input_file.mp4' output = 'path/input_file.mp4' command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\"" process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True) for line in self.process.stdout: reg = re.search('\d\d:\d\d:\d\d', line) ffmpeg_time = reg.group(0) if reg else '' print(ffmpeg_time)
источник
В 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
источник