Постоянно печатать вывод подпроцесса во время работы процесса
Для запуска программ из моих Python-скриптов я использую следующий метод:
def execute(command):
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
Поэтому, когда я запускаю такой процесс, как Process.execute("mvn clean install")
Моя программа ждет, пока процесс не завершится, и только после этого я получаю полный вывод моей программы. Это раздражает, если я запускаю процесс, который занимает некоторое время, чтобы закончить.
Могу ли я позволить моей программе записывать вывод процесса построчно, опрашивая вывод процесса до его завершения в цикле или что-то еще?
** [редактировать] Извините, я не очень хорошо искать, прежде чем опубликовать этот вопрос. Потоки на самом деле ключ. Здесь нашел пример, который показывает, как это сделать: ** Python Subprocess.Popen from the thread
17 ответов
Вы можете использовать iter для обработки строк, как только команда выведет их: lines = iter(fd.readline, "")
, Вот полный пример, показывающий типичный вариант использования (спасибо @jfs за помощь):
from __future__ import print_function # Only Python 2.x
import subprocess
def execute(cmd):
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
for stdout_line in iter(popen.stdout.readline, ""):
yield stdout_line
popen.stdout.close()
return_code = popen.wait()
if return_code:
raise subprocess.CalledProcessError(return_code, cmd)
# Example
for path in execute(["locate", "a"]):
print(path, end="")
Чтобы выводить вывод подпроцесса построчно, как только его буфер stdout будет очищен в Python 3:
from subprocess import Popen, PIPE, CalledProcessError
with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
for line in p.stdout:
print(line, end='') # process line here
if p.returncode != 0:
raise CalledProcessError(p.returncode, p.args)
Обратите внимание: вам не нужно p.poll()
- цикл заканчивается, когда достигается eof. И тебе не нужно iter(p.stdout.readline, '')
- ошибка опережающего чтения исправлена в Python 3.
Смотрите также, Python: чтение потокового ввода от subprocess.communicate ().
Хорошо, мне удалось решить это без потоков (любые предложения, почему использование потоков было бы лучше), с помощью фрагмента из этого вопроса Перехват вывода stdout подпроцесса во время его работы
def execute(command):
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Poll process for new output until finished
while True:
nextline = process.stdout.readline()
if nextline == '' and process.poll() is not None:
break
sys.stdout.write(nextline)
sys.stdout.flush()
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
На самом деле есть действительно простой способ сделать это, если вы просто хотите распечатать результат:
import subprocess
import sys
def execute(command):
subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)
Здесь мы просто указываем подпроцесс на наш собственный стандартный вывод и используем существующий api успешного выполнения или исключения.
В Python >= 3.5 используя subprocess.run
работает для меня:
import subprocess
cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)
(получение вывода во время выполнения также работает без shell=True
) https://docs.python.org/3/library/subprocess.html
@tokland
попробовал ваш код и исправил его для 3.4 и windows dir.cmd- простая команда dir, сохраненная как cmd-файл
import subprocess
c = "dir.cmd"
def execute(command):
popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
lines_iterator = iter(popen.stdout.readline, b"")
while popen.poll() is None:
for line in lines_iterator:
nline = line.rstrip()
print(nline.decode("latin"), end = "\r\n",flush =True) # yield line
execute(c)
Чтобы ответить на первоначальный вопрос, лучшим способом IMO является просто перенаправление подпроцесса stdout
непосредственно к вашей программе stdout
(опционально, то же самое можно сделать для stderr
как в примере ниже)
p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
Для тех, кто пытается получить ответы на этот вопрос, чтобы получить стандартный вывод из скрипта Python, обратите внимание, что Python буферизует свой стандартный вывод, и поэтому для его просмотра может потребоваться некоторое время.
Это можно исправить, добавив следующее после каждой записи stdout в целевой скрипт:
sys.stdout.flush()
В случае, если кто-то хочет прочитать с обоих stdout
а также stderr
в то же время используя потоки, вот что я придумал:
import threading
import subprocess
import Queue
class AsyncLineReader(threading.Thread):
def __init__(self, fd, outputQueue):
threading.Thread.__init__(self)
assert isinstance(outputQueue, Queue.Queue)
assert callable(fd.readline)
self.fd = fd
self.outputQueue = outputQueue
def run(self):
map(self.outputQueue.put, iter(self.fd.readline, ''))
def eof(self):
return not self.is_alive() and self.outputQueue.empty()
@classmethod
def getForFd(cls, fd, start=True):
queue = Queue.Queue()
reader = cls(fd, queue)
if start:
reader.start()
return reader, queue
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)
# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
# Process all available lines from the stdout Queue.
while not stdoutQueue.empty():
line = stdoutQueue.get()
print 'Received stdout: ' + repr(line)
# Do stuff with stdout line.
# Process all available lines from the stderr Queue.
while not stderrQueue.empty():
line = stderrQueue.get()
print 'Received stderr: ' + repr(line)
# Do stuff with stderr line.
# Sleep for a short time to avoid excessive CPU use while waiting for data.
sleep(0.05)
print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()
# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()
print "Waiting for process to exit..."
returnCode = process.wait()
if returnCode != 0:
raise subprocess.CalledProcessError(returnCode, command)
Я просто хотел поделиться этим, так как я закончил на этом вопросе, пытаясь сделать что-то подобное, но ни один из ответов не решил мою проблему. Надеюсь, это кому-нибудь поможет!
Обратите внимание, что в моем случае использования внешний процесс убивает процесс, который мы Popen()
,
Основываясь на превосходном ответе @jfs , вот полный рабочий пример, с которым вы можете поиграть. Требуется Python 3.7 или новее.
sub.py
import time
for i in range(10):
print(i, flush=True)
time.sleep(1)
main.py
from subprocess import PIPE, Popen
import sys
with Popen([sys.executable, 'sub.py'], bufsize=1, stdout=PIPE, text=True) as sub:
for line in sub.stdout:
print(line, end='')
Обратите внимание на flush
аргумент, используемый в дочернем скрипте.
Ни один из ответов здесь не отвечает всем моим потребностям.
- Нет потоков для stdout (нет очередей и т. Д.)
- Неблокирующая, так как мне нужно проверить, что происходит дальше
- Используйте PIPE так, как мне нужно, чтобы сделать несколько вещей, например, вывод потока, запись в файл журнала и возврат строковой копии вывода.
Немного предыстории: я использую ThreadPoolExecutor для управления пулом потоков, каждый из которых запускает подпроцесс и выполняет их параллелизм. (В Python2.7, но это должно работать и в более новых 3.x). Я не хочу использовать потоки только для сбора выходных данных, так как хочу, чтобы как можно больше было доступно для других целей (пул из 20 процессов использовал бы только 40 потоков для запуска; 1 для потока процесса и 1 для stdout... и еще если хочешь стдерр наверное)
Я отбрасываю множество исключений и тому подобное здесь, так что это основано на коде, который работает в производстве. Надеюсь, я не испортил это в копии и вставке. Также, отзывы очень приветствуются!
import time
import fcntl
import subprocess
import time
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
"""A little inline function to handle the stdout business. """
# fcntl makes readline non-blocking so it raises an IOError when empty
try:
for s in iter(proc_stream.readline, ''): # replace '' with b'' for Python 3
my_buffer.append(s)
if echo_streams:
sys.stdout.write(s)
if log_file:
log_file.write(s)
except IOError:
pass
# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
handle_stdout(proc_stdout, stdout_parts)
# ...Check for other things here...
# For example, check a multiprocessor.Value('b') to proc.kill()
time.sleep(0.01)
# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)
stdout_str = "".join(stdout_parts) # Just to demo
Я уверен, что здесь добавляются накладные расходы, но в моем случае это не проблема. Функционально он делает то, что мне нужно. Единственное, что я не решил, так это то, почему это прекрасно работает для сообщений журнала, но я вижу некоторые print
сообщения появляются позже и все сразу.
Это работает по крайней мере в Python3.4
import subprocess
process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
print(line.decode().strip())
Этот PoC постоянно читает выходные данные процесса и может быть доступен при необходимости. Сохраняется только последний результат, все остальные выходные данные отбрасываются, что препятствует росту памяти PIPE:
import subprocess
import time
import threading
import Queue
class FlushPipe(object):
def __init__(self):
self.command = ['python', './print_date.py']
self.process = None
self.process_output = Queue.LifoQueue(0)
self.capture_output = threading.Thread(target=self.output_reader)
def output_reader(self):
for line in iter(self.process.stdout.readline, b''):
self.process_output.put_nowait(line)
def start_process(self):
self.process = subprocess.Popen(self.command,
stdout=subprocess.PIPE)
self.capture_output.start()
def get_output_for_processing(self):
line = self.process_output.get()
print ">>>" + line
if __name__ == "__main__":
flush_pipe = FlushPipe()
flush_pipe.start_process()
now = time.time()
while time.time() - now < 10:
flush_pipe.get_output_for_processing()
time.sleep(2.5)
flush_pipe.capture_output.join(timeout=0.001)
flush_pipe.process.kill()
print_date.py
#!/usr/bin/env python
import time
if __name__ == "__main__":
while True:
print str(time.time())
time.sleep(0.01)
Вывод: вы можете ясно видеть, что между интервалом ~2,5 с ничего нет.
>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
import time
import sys
import subprocess
import threading
import queue
cmd='esptool.py --chip esp8266 write_flash -z 0x1000 /home/pi/zero2/fw/base/boot_40m.bin'
cmd2='esptool.py --chip esp32 -b 115200 write_flash -z 0x1000 /home/pi/zero2/fw/test.bin'
cmd3='esptool.py --chip esp32 -b 115200 erase_flash'
class ExecutorFlushSTDOUT(object):
def __init__(self,timeout=15):
self.process = None
self.process_output = queue.Queue(0)
self.capture_output = threading.Thread(target=self.output_reader)
self.timeout=timeout
self.result=False
self.validator=None
def output_reader(self):
start=time.time()
while self.process.poll() is None and (time.time() - start) < self.timeout:
try:
if not self.process_output.full():
line=self.process.stdout.readline()
if line:
line=line.decode().rstrip("\n")
start=time.time()
self.process_output.put(line)
if self.validator:
if self.validator in line: print("Valid");self.result=True
except:pass
self.process.kill()
return
def start_process(self,cmd_list,callback=None,validator=None,timeout=None):
if timeout: self.timeout=timeout
self.validator=validator
self.process = subprocess.Popen(cmd_list,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
self.capture_output.start()
line=None
self.result=False
while self.process.poll() is None:
try:
if not self.process_output.empty():
line = self.process_output.get()
if line:
if callback:callback(line)
#print(line)
line=None
except:pass
error = self.process.returncode
if error:
print("Error Found",str(error))
raise RuntimeError(error)
return self.result
execute = ExecutorFlushSTDOUT()
def liveOUTPUT(line):
print("liveOUTPUT",line)
try:
if "Writing" in line:
line=''.join([n for n in line.split(' ')[3] if n.isdigit()])
print("percent={}".format(line))
except Exception as e:
pass
result=execute.start_process(cmd2,callback=liveOUTPUT,validator="Hash of data verified.")
print("Finish",result)
Использовать-u
Вариант Python сsubprocess.Popen()
если вы хотите печатать из стандартного вывода во время выполнения процесса. (shell=True
необходимо, несмотря на риски...)
Лучше простое, чем сложное.
os
библиотека имеет встроенный модуль
system
вы должны выполнить свой код и увидеть результат.
import os
os.system("python --version")
# Python 3.8.6
# 0
В Python 3.6 я использовал это:
import subprocess
cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)