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


202

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")と、プログラムはプロセスが終了するまで待機し、その後初めてプログラムの完全な出力を取得します。終了するまでに時間がかかるプロセスを実行している場合、これは迷惑です。

ループなどで終了する前にプロセス出力をポーリングすることで、プログラムに1行ずつプロセス出力を書き込ませることができますか?

** [編集]すみません、この質問を投稿する前によく検索できませんでした。スレッディングが実際の鍵です。それを行う方法を示す例をここに見つけました:** スレッドからPython Subprocess.Popen


サブプロセスではなくスレッドだと思う
Ant

9
いいえ、スレッドは必要ありません。プロセスの実行中にプロセスから読み取り/書き込みを取得できるため、パイピングのアイデア全体が機能します。
tokland 2013

回答:


264

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

24
私はこのコードを(実行にかなりの時間がかかるプログラムで)試してみましたが、実行が完了するのを待つのではなく、受信した行を出力することを確認できます。これは優れた答えです。
アンドリューマーティン

11
注:Python 3では、を使用できますfor line in popen.stdout: print(line.decode(), end='')。Pythonの2と3の両方をサポートするために、使用がリテラルバイト:b''それ以外lines_iteratorのPython 3の端部決して
JFSない

3
このアプローチの問題は、プロセスがstdoutに何も書き込まずに少し停止すると、読み取る入力がなくなることです。プロセスが終了したかどうかを確認するループが必要になります。Python 2.7でsubprocess32を使用してこれを試しました
Har

7
うまくいくはずです。それを磨くために、あなたが追加することができますbufsize=1(これは、Python 2のパフォーマンスを向上させること)を、近いpopen.stdoutパイプ明示的に(それの世話をするためにガベージコレクションを待たずに)、および昇給subprocess.CalledProcessError(のようなcheck_call()check_output()やります)。print声明は、Python 2と3に異なっている:あなたはなsoftspaceハック使用することができますprint line,(注:カンマを)あなたのコードのようにすべての改行を倍増避けるために行い、合格universal_newlines=Trueテキストの代わりに、bytes-取得するには、Pythonの3に関連した答えを
jfs

6
@binzhangこれはエラーではなく、標準出力はPythonスクリプトでバッファリングされます(多くのUnixツールでも同様)。お試しくださいexecute(["python", "-u", "child_thread.py"])。さらに詳しい情報:stackoverflow.com/questions/14258500/...
tokland

84

[OK]を私はこの質問からスニペットを使って、(スレッドを使用すると良いだろう、なぜ任意の提案が高く評価されている)スレッドせずにそれを解決するために管理し、それを実行している間、サブプロセスの標準出力インターセプト

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)

3
私は変更しなければならなかった(ifischerさんとtoklandのコードは非常にうまく機能マージprint line,するsys.stdout.write(nextline); sys.stdout.flush()。そうでなければ、それはすべての2行を印刷しその後、再び、これはそうかもしれない何か他のものが起こっていた、IPythonのノートブック・インターフェースを使用している-に関係なく、明示的に呼び出すflush()作品を。
eacousineau 2012年

3
ミスターあなたは私の命の恩人です!! この種のものがライブラリ自体に組み込まれていないことは非常に奇妙です。cliappを書いた場合、ループ内で処理されているものすべてを即座に表示したいのです。s'rsly ..
holms

3
このソリューションは出力とエラーの両方を常に出力するように変更できますか?ループに切り替えstderr=subprocess.STDOUTてからループ内からstderr=subprocess.PIPE呼び出すprocess.stderr.readline()と、subprocessモジュールのドキュメントで警告されている非常にデッドロックが発生する可能性があります。
davidrmcharles 2013

7
@DavidCharles私があなたが探しているのは、stdout=subprocess.PIPE,stderr=subprocess.STDOUTこれがstderrをキャプチャすることだと思います、そして私はそれをstdinもキャプチャすると信じています(ただし、テストしていません)。
アンドリューマーティン

終了コードを待ってくれてありがとう。それを解決する方法を知りませんでした
Vitaly Isaev '19年

68

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()からストリーミング入力を読み取る」も参照してください。


3
この解決策は私にとってうまくいきました。上記の受け入れられた解決策は私のために空白行を印刷し続けました。
コードネーム、2015年

3
印刷をすぐに取得するには、sys.stdout.flush()を追加する必要がありました。
コードネーム2015年

3
@Codename:sys.stdout.flush()親では必要ありません-stdout はファイル/パイプにリダイレクトされない場合はラインバッファリングされるため、印刷lineによってバッファが自動的にフラッシュされます。sys.stdout.flush()子も必要ありません-u。代わりにコマンドラインオプションを渡します。
jfs 2015年

1
@Codename:使用する場合>はを実行しpython -u your-script.py > some-fileます。注意:-u上記のオプション(を使用する必要はありませんsys.stdout.flush())。
jfs 2015年

1
@mvidelgauzを呼び出す必要はありません— ブロックのp.wait()終了時に呼び出されwithます。を使用しp.returncodeます。
jfs 2017年

8

出力を印刷したいだけの場合、実際にこれを行う簡単な方法があります。

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)

ここでは、サブプロセスを独自のstdoutにポイントし、既存の成功または例外APIを使用しています。


1
このソリューションは、Python 3.6の@toklandのソリューションよりもシンプルでクリーンです。shell = True引数は必要ないことに気づきました。
グッドウィル

グッドキャッチ、グッドウィル。削除shell=True
アンドリューリング

非常に直感的で、小さなコードで完璧に動作します。多分あなたもサブプロセスstderrをsys.stderrにリダイレクトする必要がありますか?
マヌ

あなたが確かにできるManu。質問の試みはstderrをstdoutにリダイレクトすることだったので、ここでは行いませんでした。
アンドリューリング

sys.stdoutとsubprocess.STDOUTの違いを説明できますか?
Ron Serruya

7

@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)

3
コードを簡略化できますiter()end='\r\n'不要です。Pythonはデフォルトでユニバーサル改行モードを使用します。つまり、印刷中に'\n'変換さ'\r\n'れます。'latin'おそらく間違ったエンコーディングですuniversal_newlines=True。Python3でテキスト出力を取得するために使用できます(ロケールの優先エンコーディングを使用してデコードされます)。で停止しないでください.poll()。未読のデータがバッファリングされている可能性があります。Pythonスクリプトがコンソールで実行されている場合、その出力はラインバッファリングされます。-uオプションを使用してラインバッファリングを強制できます- flush=Trueここでは必要ありません。
jfs 2015

4

誰かが両方から読みたい場合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()


1
私はpython2に対してほぼ同じようなものを使用しなければなりませんでした。このようなものはpython2で提供されているはずですが、このようなものはまったく問題ありません。
スチュアートアクソン

3

Pythonスクリプトからstdoutを取得するためにこの質問への回答を試みる人にとって、Pythonはそのstdoutをバッファーするため、stdoutを表示するのに時間がかかる場合があることに注意してください。

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

sys.stdout.flush()

1
しかし、PythonをPythonのサブプロセスとして実行することは、そもそもクレイジーです。あなたのスクリプトは単にimport他のスクリプトでなければなりません。見multiprocessingたりthreading、あなたはパラレル実行が必要な場合。
Tripleee

3
@triplee PythonのサブプロセスとしてPythonを実行することが適切であるシナリオがいくつかあります。毎日、連続して実行したい多数のpythonバッチスクリプトがあります。これらは、実行を開始するマスターPythonスクリプトによって調整され、子スクリプトが失敗した場合にメールで通知されます。各スクリプトは互いにサンドボックス化されています-名前の競合はありません。私は並列化していないので、マルチプロセッシングとスレッド化は関係ありません。
user1379351 2018

また、メインのpythonプログラムが実行されているものとは異なるpython実行可能ファイルを使用して、他のpythonプログラムを開始することもできます。たとえば、subprocess.run("/path/to/python/executable", "pythonProgramToRun.py")
Kyle Bridenstine

3

Python> = 3.5ではsubprocess.run、私のために作品を使用しています:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)

(実行中に出力を取得してもなしで機能しますshell=Truehttps://docs.python.org/3/library/subprocess.html#subprocess.run


2
これは「実行中」ではありません。subprocess.run()サブプロセスが完成し稼働しているときのコールにのみ返されます。
tripleee

1
「実行中」でないことを説明できますか?のようなもの>>> import subprocess; subprocess.run('top')も「実行中」と出力するようです(そしてtopが終了しない)。多分私はいくつかの微妙な違いを把握していませんか?
user7017793

たとえば、出力をPythonにリダイレクトして戻すstdout=subprocess.PIPEと、top終了後にしか読み取ることができません。Pythonプログラムは、サブプロセスの実行中にブロックされます。
tripleee

1
そうですね。生成された出力を確認するrunだけの場合でも、この方法は機能します。あなたが非同期でPythonの出力で何かをしたいのであれば、それはうまくいきません。
user7017793

3

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

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

3
以下のために何を指定していないstdoutし、stderr少ないコードで同じことを行います。私は明示的が暗黙的よりも優れている
tripleee

1

この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.5s間隔からの出力のみがあり、その間に何もないことが明確にわかります。

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

0

これは少なくともPython3.4で機能します

import subprocess

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

1
これには、プロセスの実行が完了するまでループでブロックされるという問題があります。
tripleee

0

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

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

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

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

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メッセージが後で一度にすべて表示されるのがわかります。


-2

Python 3.6では、これを使用しました:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)

1
これは、この特定の質問に対する回答ではありません。出力を取得する前にサブプロセスが完了するのを待つことは、OPが具体的かつ正確に回避しようとしていることです。古いレガシー関数にsubprocess.call()は、新しい関数によって修正されるいぼがいくつかあります。Python 3.6では、通常subprocess.run()これに使用します。便宜上、古いラッパー関数subprocess.check_output()も引き続き使用できます。これは、プロセスからの実際の出力を返します(このコードは終了コードのみを返しますが、代わりに未定義のものを出力します)。
tripleee
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.