web-dev-qa-db-ja.com

サブプロセスコマンドからのライブ出力

私はpythonスクリプトを流体力学コードのドライバとして使用しています。シミュレーションを実行するときは、subprocess.Popenを使用してコードを実行し、stdoutとstderrからの出力をsubprocess.PIPEに収集します。その後、出力情報を印刷(およびログファイルに保存)して、すべてのエラー問題は、コードがどのように進歩しているのか私にはわかりません。コマンドラインから直接実行した場合は、その反復の時点、時点、次のタイムステップなどについて出力されます。

ロギングとエラーチェックのために)出力を保存し、さらにライブストリーミングの出力を作成する方法はありますか?

私のコードの関連セクション:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

もともと私はrun_commandからteeまでをコピーしてコピーが直接ログファイルに行き、ストリームがまだ直接端末に出力されるようにしていました - しかしそのようにしてエラーを(私の知識に)保存することはできません。


編集する

一時的な解決策:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, Shell=True )
while not ret_val.poll():
    log_file.flush()

次に、別の端末でtail -f log.txt(s.t. log_file = 'log.txt')を実行します。

148
DilithiumMatrix

これを行うには、readまたはreadline関数からイテレータを作成する方法があります。

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

または

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

readerおよびwriterファイルを作成することもできます。 writerPopenに渡し、readerから読み取ります。

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

このようにして、標準出力と同様にtest.logにデータを書き込むことができます。

ファイルアプローチの唯一の利点は、コードがブロックされないことです。そのため、その間に必要なことは何でも実行でき、readerから必要なときにいつでもブロックすることなく読み取ることができます。 PIPEを使用すると、readおよびreadline関数は、それぞれ1文字がパイプに書き込まれるか1行がパイプに書き込まれるまでブロックされます。

136
Viktor Kerkez

エグゼクティブサマリー(または "tl; dr"バージョン):subprocess.PIPEが1つしかない場合は簡単ですが、それ以外の場合は困難です。

subprocess.Popenがどのように機能するかについて少し説明する時があるかもしれません。

(警告:これはPython 2.x用ですが、3.xは似ていますが、Windows版についてはかなりあいまいです。POSIXのものははるかに良く理解できます。)

Popen関数は、ゼロから3つのI/Oストリームをやや同時に処理する必要があります。これらは、通常どおりstdinstdout、およびstderrと表示されます。

あなたが提供することができます:

  • Noneは、ストリームをリダイレクトしたくないことを示します。代わりに通常通りこれらを継承します。少なくともPOSIXシステムでは、これはPythonのsys.stdoutを使うことを意味するのではなく、単にPythonのactual stdoutを使います。最後にデモを見てください。
  • int値これは(少なくともPOSIXでは)「生の」ファイル記述子です。 (補足:PIPESTDOUTは、実際は内部的にはintsですが、 "不可能な"記述子である-1と-2です。)
  • ストリーム - 実際には、filenoメソッドを持つ任意のオブジェクト。 Popenは、そのストリームの記述子をstream.fileno()を使用して検索し、次にintの値と同じように処理を進めます。
  • subprocess.PIPEは、Pythonがパイプを作成する必要があることを示します。
  • subprocess.STDOUTstderrのみ):stdoutと同じ記述子を使うようにPythonに指示します。これは、Noneに(non -stdout)値を指定した場合にのみ意味があります。それでも、stdout=subprocess.PIPEを設定した場合は必要になります。 (それ以外の場合は、stdoutに指定したものと同じ引数、たとえばPopen(..., stdout=stream, stderr=stream)を指定することができます。)

最も簡単なケース(パイプなし)

何もリダイレクトしない場合(3つすべてをデフォルトのNoneの値のままにするか、明示的にNoneを指定する)、Pipeは非常に簡単です。サブプロセスをスピンオフして実行させるだけです。あるいは、PIPE以外の_、またはint、またはストリームのfileno()にリダイレクトした場合でも、OSがすべての作業を実行するので簡単です。 Pythonはサブプロセスをスピンオフして、その標準入力、標準出力、標準エラー出力を提供されたファイル記述子に接続するだけです。

それでもやさしいケース:1本のパイプ

1つのストリームだけをリダイレクトしても、Pipeにはまだかなり簡単なものがあります。一度に1つのストリームを選んで見ましょう。

stdinを指定したいが、stdoutstderrをリダイレクトしないようにするか、ファイル記述子に移動します。親プロセスとして、あなたのPythonプログラムは単純にwrite()を使ってデータをパイプに送ります。あなたはこれを自分で行うことができます、例えば:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

または、stdinデータをproc.communicate()に渡すこともできます。そして、それは上記のstdin.writeを行います。戻ってくる出力はないのでcommunicate()には他に1つの本当の仕事しかありません:それはまたあなたのためにパイプを閉じます。 (proc.communicate()を呼び出さないのであれば、パイプを閉じるためにproc.stdin.close()を呼び出さなければならないので、サブプロセスはそれ以上データがないことを認識しています。)

stdoutをキャプチャしたいが、stdinstderrはそのままにしておくとします。繰り返しますが、これは簡単です。出力がなくなるまでproc.stdout.read()(または同等のもの)を呼び出すだけです。 proc.stdout()は通常のPythonのI/Oストリームなので、次のように通常のコンストラクトをすべて使用できます。

for line in proc.stdout:

また、proc.communicate()を使用することもできます。これは単にread()を実行するだけです。

stderrだけをキャプチャしたい場合は、stdoutと同じように機能します。

物事が難しくなる前にもう1つトリックがあります。 stdoutをキャプチャし、さらにstderrをキャプチャしたいがstdoutと同じパイプ上で

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

この場合、subprocess "cheats"!まあ、それはそれをしなければならない、それでそれは本当に詐欺的ではありません:それはその親(Python)プロセスにフィードバックする(単一の)パイプ記述子に向けられたそのstdoutとstderrの両方でサブプロセスを開始します。親側にも、出力を読み取るための単一のパイプディスクリプタしかありません。 "stderr"の出力はすべてproc.stdoutに表示されます。proc.communicate()を呼び出すと、stderrの結果(Tupleの2番目の値)は文字列ではなくNoneになります。

ハードケース:2本以上のパイプ

問題はすべて、少なくとも2本のパイプを使いたいときに起こります。実際、subprocessコード自体にこのビットがあります。

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

しかし、残念ながら、ここでは少なくとも2つ、場合によっては3つの異なるパイプを作成しました。したがって、count(None)は1または0のいずれかを返します。

Windowsでは、これはthreading.Threadを使用してself.stdoutおよびself.stderrの結果を累積し、親スレッドにself.stdin入力データを配信させます(そしてパイプを閉じます)。

POSIXでは、利用可能であればpollを、そうでなければselectを使って出力を蓄積し、標準入力を渡します。これはすべて(単一の)親プロセス/スレッドで実行されます。

デッドロックを回避するために、スレッドまたはポーリング/選択がここで必要です。たとえば、3つのストリームすべてを3つの別々のパイプにリダイレクトしたとします。さらに、書き込み処理が中断されるまでの間にパイプに格納できるデータ量にはわずかな制限があり、読み取り処理がもう一方の端からパイプを「片付ける」のを待つとします。説明のために、この小さな制限を1バイトに設定しましょう。 (これは実際には物事がどのように機能するかを示していますが、制限が1バイトよりはるかに大きいことを除いて)。

'go\n'からproc.stdinのように、親(Python)プロセスが数バイトを書き込もうとすると、最初のバイトが入り、次に2番目のバイトがPythonプロセスを中断し、サブプロセスが最初のバイトを読み取るのを待ってパイプを空にします。

一方、サブプロセスが「こんにちは、パニックにならないでください」という親しみやすいものを印刷することにしたとします。挨拶。 Hはその標準出力パイプに入りますが、eはそれを中断させ、親がそのHを読み取って標準出力パイプを空にするのを待ちます。

これで私たちは動けなくなりました。Pythonプロセスは "go"と言って終了するのを待って眠っています、そしてサブプロセスも "Hello!Don't Panic!"と言って終了するのを待って眠っています。

subprocess.Popenコードはthreading-or-select/pollに関するこの問題を回避します。バイトがパイプを通過できるとき、それらは行きます。それができない場合、スレッド(プロセス全体ではない)だけがスリープする必要があります。あるいは、select/pollの場合、Pythonプロセスは同時に "can write"または "data available"を待って、プロセスのstdinに書き込みます。スペースがあるときだけ、そしてデータが準備ができているときだけそのstdoutやstderrを読みます。 proc.communicate()コード(実際には毛深いケースが扱われる場合は_communicate)は、すべての標準入力データ(もしあれば)が送信され、すべての標準出力または標準エラーデータ、あるいはその両方が累積されると戻ります。

2つの異なるパイプでstdoutstderrの両方を(stdinリダイレクトに関係なく)読み取る場合は、デッドロックも回避する必要があります。ここでのデッドロックのシナリオは異なります - stderrからデータを取得している間にサブプロセスがstdoutに長い時間書き込みを行った場合、またはその逆の場合ですが、それでも問題はありません。


デモ

リダイレクトされていないPython subprocessesがsys.stdoutではなく基礎となる標準出力に書き込むことを実証することを約束しました。だから、ここでいくつかのコードです:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

実行時:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

StringIOオブジェクトにはfilenoがないため、stdout=sys.stdoutを追加した場合、最初のルーチンは失敗します。 stdout=sys.stdoutsys.stdoutにリダイレクトされているので、os.devnullを追加した場合、2番目はhelloを省略します。

(Pythonのfile-descriptor-1をリダイレクトすると、サブプロセスwillがそのリダイレクトに従います。open(os.devnull, 'w')呼び出しは、fileno()が2より大きいストリームを生成します。)

77
torek

Readline()でiterコンストラクトを使用する代わりに、標準出力の読み取りにデフォルトのファイル反復子を使用することもできます。

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)
18
Jughead

もしあなたがサードパーティのライブラリを使うことができるなら、あなたは sarge のようなものを使うことができるかもしれません(開示:私はそのメンテナです)。このライブラリはサブプロセスからの出力ストリームへのノンブロッキングアクセスを可能にします - それはsubprocessモジュールの上に層にされます。

10
Vinay Sajip

良いが「ヘビー級」の解決策はTwistedを使うことです - 下を見てください。

あなたがこれらの線に沿って何か標準出力だけで生きることをいとわないのであればうまくいくはずです:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(read()を使用すると有用ではない「ファイル」全体を読み取ろうとしますが、ここで実際に使用できるのは、現在パイプ内にあるすべてのデータを読み取るものです)

次のように、スレッドを使ってこれに取り組むこともできます。

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, Shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

これで、2つのスレッドを持つことで標準エラー出力を追加する可能性があります。

ただし、サブプロセスドキュメントではこれらのファイルを直接使用することは推奨されず、communicate()主に上記の問題ではないと思われるデッドロックが関係します)の使用を推奨します。仕事まで(また参照してください: http://www.python.org/dev/peps/pep-3145/ )そして我々は他の何かを見る必要があります。

より複雑な解決策は、次に示すように Twisted を使用することです。 https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

これを Twisted で実行する方法は、reactor.spawnprocess()を使用してプロセスを作成し、次に出力を非同期的に処理するProcessProtocolを提供することです。 TwistedのサンプルPythonコードはこちらです。 https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

2
Guy Sirton

私が試した上記の解決策はすべて、stderrとstdoutの出力の分離(複数のパイプ)に失敗するか、OSのパイプバッファがいっぱいになると永久にブロックされます。サブプロセスのpoll()マニュアル私が見つけた唯一の信頼できる方法はselectを通してでした、しかしこれはposixだけの解決策です:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()
1
sivann

ラインバッファ出力がうまくいくように見えますが、その場合は次のようなものが適しています。 (警告:テストされていません。)これはサブプロセスの標準出力をリアルタイムで表示するだけです。リアルタイムでstderrとstdoutの両方を使いたい場合は、selectを使ってもっと複雑なことをする必要があります。

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
1
Alp

解決策1:stdout AND stderrをリアルタイムで同時に記録

stdout AND stderrの両方を同時にrealtimeに記録する簡単なソリューション。

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, pipe_name):

    while p.poll() is None:
        line = getattr(p, pipe_name).readline()
        log_file.write(line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, 'stdout')
        r2 = pool.submit(log_popen_pipe, p, 'stderr')
        r1.result()
        r2.result()

解決策2:stdoutstderrを1行ずつ、同時にリアルタイムで返すイテレータを作成します

ここでは、リアルタイムで同時に両方のパイプ(stdout/stderr)を反復処理できる関数read_popen_pipes()を作成します。

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

1
Rotareti

上記すべてに基づいて、私はわずかに修正されたバージョン(python3)を提案します。

  • readlineを呼び出すwhileループ(iterの解決策は私にとっては永久にブロックされているようです - Python 3、Windows 7)
  • 構造化されているため、ポーリングが-Noneを返さない場合は、読み取りデータの処理を複製する必要はありません。
  • stderrがstdoutにパイプ接続されているため、両方の出力が読み取られます。
  • Cmdの終了値を取得するためのコードを追加しました。

コード:

import subprocess
proc = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here
0
mrtnlrsn

なぜstdoutを直接sys.stdoutに設定しないのですか?また、ログにも出力する必要がある場合は、単純にfのwriteメソッドをオーバーライドできます。

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)
0
xaav

これらすべての答えに加えて、1つの簡単な方法も次のようになります。

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

読み取り可能な限り読み取り可能なストリームをループ処理し、空の結果が得られた場合は停止します。

ここで重要なのは、出力がある限りreadline()は(最後に\nを含む)行を返すことです。それが本当に最後であれば空です。

これが誰かに役立つことを願っています。

0
kabirbaidhya

Pythonicソリューションのどれも私のために働きませんでした。 proc.stdout.read()やそれに類するものが永遠にブロックされる可能性があることがわかりました。

したがって、私は次のようにteeを使用します。

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', Shell=True, check=True, executable='/bin/bash')

あなたが既にShell=Trueを使っているならば、この解決法は便利です。

${PIPESTATUS}はコマンドチェーン全体の成功ステータスをキャプチャします(Bashでのみ利用可能)。 && exit ${PIPESTATUS}を省略した場合、teeが失敗することはないので、これは常にゼロを返します。

"パイプバッファ"が一杯になるまで待ち時間が長すぎるのではなく、unbufferが各行を直ちに端末に表示するのに必要かもしれません。しかし、アンバッファはassert(SIG Abort)の終了ステータスを飲み込みます...

2>&1もstderrorをファイルに記録します。

0
Mike76

これが私のプロジェクトのひとつで使っているクラスです。サブプロセスの出力をログにリダイレクトします。最初は単純にwrite-methodを上書きしようとしましたが、サブプロセスが決してそれを呼び出すことはないのでそれは動作しません(リダイレクトはファイルディスクリプタレベルで起こります)。サブプロセスモジュールで行われているのと同じように、私は自分のパイプを使っています。これには、すべてのロギング/印刷ロジックをアダプターにカプセル化するという利点があり、ロガーのインスタンスをPopenに渡すことができます。subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

ロギングを必要とせず、単にprint()を使いたいのであれば、明らかにコードの大部分を削除してクラスを短くすることができます。 __enter__および__exit__メソッドでそれを拡張し、__exit__内でfinishedを呼び出すことで、コンテキストとして簡単に使用できるようにすることもできます。

0
t.animal

これまでの回答と似ていますが、Python 3を使用しているウィンドウでは、次の解決法が役に立ちました。( getting-realtime-output-using-python ) :

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, Shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()
0
scottysbasement

subprocess.communicateメソッドは少し紛らわしいと思います。実際にはsubprocess.Popenで指定したstdoutstderrを埋めます。

ただし、subprocess.PIPEstdoutおよびstderrパラメータに指定できるsubprocess.Popenから読み取ると、最終的にはOSのパイプバッファがいっぱいになり、アプリケーションがデッドロックされます(特に複数のプロセスがある場合)。 subprocessを使用する必要がある/ threads。

私が提案した解決策は、stdoutstderrをファイルと一緒に提供し、デッドロックのPIPEからではなくファイルの内容を読むことです。これらのファイルはtempfile.NamedTemporaryFile()にすることができます - それらがsubprocess.communicateによって書き込まれている間に読み込みのためにアクセスすることもできます。

以下は使用例です

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

そして、これは使用する準備ができているというソースコードです。

あなたがpython 2を使っているのであれば、まずpypiのsubprocess32パッケージの最新版をインストールしてください。


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



0
Victor Klapholz