Постоянно печатать вывод подпроцесса во время выполнения процесса
для запуска программ из моих 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 Подпроцесс.Попень из ниточки
9 ответов:
можно использовать 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 подпроцесса во время его выполнения
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)
чтобы напечатать вывод подпроцесса построчно, как только его буфер 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: чтение потокового ввода из подпроцесса.общаться().
@tokland
попробовал ваш код и исправил его для 3.4 и windows dir.cmd-это простая команда dir, сохраненная как cmd-file
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)
для тех, кто пытается ответить на этот вопрос, чтобы получить stdout из скрипта Python обратите внимание, что Python буферизует свой stdout, и поэтому может потребоваться некоторое время, чтобы увидеть stdout.
Это можно исправить, добавив следующее после каждой записи stdout в целевой скрипт:
sys.stdout.flush()
этот PoC постоянно считывает выходные данные из процесса и может быть доступен при необходимости. Сохраняется только последний результат, все остальные выходные данные отбрасываются, поэтому труба не растет из памяти:
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 s ничего между ними.
>>>1520535158.51 >>>1520535161.01 >>>1520535163.51 >>>1520535166.01
в 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#subprocess.run
в случае если кто-то хочет читать как
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()
.