web-dev-qa-db-ja.com

プロセスの実行中にサブプロセスの出力を常に印刷する

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

153
ifischer

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="")
215
tokland

この質問のスニペットを使用して、スレッドなしで解決できました(スレッドを使用する方が良い理由はありがたいです) 実行中のサブプロセスの標準出力のインターセプト

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)
77
ifischer

Python 3でstdoutバッファーがフラッシュされるとすぐにサブプロセスの出力を行ごとに印刷するには:

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() からストリーミング入力を読み取ります。

51
jfs

@tokland

コードを試して、3.4用に修正しました。windowsdir.cmdは、cmd-fileとして保存された単純なdirコマンドです

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)
5
user3759376

Pythonスクリプトからstdoutを取得するためにこの質問への回答を試みる人は、Pythonがstdoutをバッファリングすることに注意してください。

これは、ターゲットスクリプトの各stdout書き込みの後に次を追加することで修正できます。

sys.stdout.flush()
3
user1379351

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

3
user7017793

元の質問に答えるための最良の方法は、IMOがサブプロセスstdoutをプログラムのstdoutに直接リダイレクトすることです(オプションで、以下の例のように、stderrにも同じことができます)。

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
2
Alleo

誰かがスレッドを使用してstdoutstderrの両方から同時に読み取りたい場合、これが私が思いついたものです:

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()というプロセスを強制終了することに注意してください。

2
Will

この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 __== "__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 __== "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

出力:2.5秒間隔から何も出力されていないことが明確にわかります。

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
1

これは少なくともPython3.4で動作します

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())
0
arod

ここでの答えは、私のニーズのすべてに対応していません。

  1. Stdoutのスレッドはありません(キューなどもありません)
  2. 進行中の他のことを確認する必要があるため、ノンブロッキング
  3. 複数のことを行う必要があるときにPIPEを使用します。出力をストリームし、ログファイルに書き込み、出力の文字列コピーを返します。

少しの背景:ThreadPoolExecutorを使用してスレッドのプールを管理し、それぞれがサブプロセスを起動して並行処理を実行しています。 (Python2.7では、これは新しい3.xでも機能するはずです)。私はできるだけ多くを他のものに使用できるように、出力収集のためだけにスレッドを使用したくありません(20個のプロセスのプールは実行するために40個のスレッドを使用します;プロセススレッド用に1個、標準出力用に1個...そして、もしあなたがstderrが欲しいなら、私は推測します)

私は多くの例外などを取り除いていますので、これはbasedが本番で動作するコードに基づいています。うまくいけば、コピーアンドペーストでそれを台無しにしないでください。また、フィードバックも大歓迎です!

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メッセージが後で一度に表示されることです。

0
Rafe