行ごとにサブプロセスstdoutを読み取る


235

私のpythonスクリプトはサブプロセスを使用して、非常にうるさいlinuxユーティリティを呼び出します。すべての出力をログファイルに保存し、その一部をユーザーに表示します。次の方法でうまくいくと思いましたが、ユーティリティが大量の出力を生成するまで、出力はアプリケーションに表示されません。

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

私が本当に望んでいる動作は、フィルタースクリプトがサブプロセスから受け取った各行を出力することです。Sortaは何をするのかteeが好きですが、Pythonコードを使用しています。

何が欠けていますか?これは可能ですか?


更新:

a sys.stdout.flush()がfake_utility.pyに追加された場合、コードはPython 3.1で望ましい動作をします。Python 2.6を使用しています。を使用proc.stdout.xreadlines()するとpy3kと同じように機能すると思いますが、そうではありません。


アップデート2:

これが最小限の作業コードです。

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
(注:末尾のカンマ)のprint line,代わりに使用できますprint line.rstrip()
JFS


2
Update 2では、Python 3.0以降では機能するが古い印刷ステートメントを使用するため、Python 3.0以降では機能しません。
ルーキー

ここにリストされている答えはどれもうまくいきませんでしたが、stackoverflow.com / questions / 5411780 / …うまくいきました。
2018

回答:


179

私が最後にPythonで作業してから久しぶりですが、問題は、ステートメントfor line in proc.stdout全体で入力を読み取る前にそれを繰り返すステートメントにあると思います。解決策はreadline()代わりに使用することです:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

もちろん、サブプロセスのバッファリングを処理する必要があります。

注:ドキュメントによると、イテレータを使用したソリューションは、readline()先読みバッファを除いて、を使用することと同等でなければなりませんが、(またはこれが原因で)提案された変更により、異なる結果が得られました(Windows XP上のPython 2.5)。


11
以下のためのfile.readline()for line in file参照bugs.python.org/issue3907 ;:(使用、それはのpython3に働く短期でio.open()のPython 2.6+上)
JFS

5
PEP 8の「プログラミングの推奨事項」(python.org/dev/peps/pep-0008)によるEOFのよりpythonicテストは、'if not line:'になります。
Jason Mock、

14
@naxa:パイプ用:for line in iter(proc.stdout.readline, ''):
jfs

3
@ Jan-PhilipGehrcke:はい。1. for line in proc.stdoutPython 3で使用できます(先読みバグはありません)2. '' != b''Python 3で-コードを盲目的にコピーアンドペーストしないでください-機能と仕組みを考えてください。
jfs

2
@JFSebastian:確かに、iter(f.readline, b'')解決策はかなり明白です(誰かが興味を持っている場合は、Python 2でも機能します)。私のコメントの要点は、あなたの解決策を非難することではありません(そのように表示された場合は申し訳ありませんが、今も読みました!)。 3つの問題が発生すると例外が発生しますが、ここでは正常に動作するループが無限に変わり、ガベージコレクションは新しく作成されたオブジェクトのフラッドとの戦いに苦労し、長い期間と大きな振幅でメモリ使用量の変動を引き起こします)。
Jan-Philip Gehrcke博士、2015

45

パーティーに少し遅れましたが、私がここで最も簡単な解決策であると思うものを見ていないことに驚きました:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(これにはPython 3が必要です。)


25
私はこの答えを使いたいのですが、AttributeError: 'file' object has no attribute 'readable' py2.7
ダンGarthwaite

3
python 3で動作します
matanster

明らかにこのコードは、py3 / py3の互換性とValueErrorを取得する実際のリスクのために有効ではありません:閉じられたファイルでのI / O操作
ソリン

3
@sorinは、これらのいずれも「無効」にしません。Python 2をサポートする必要があるライブラリを作成している場合は、このコードを使用しないでください。しかし、多くの人々は、10年前よりも最近リリースされたソフトウェアを使用できるという贅沢を持っています。閉じたファイルを読み取ろうとすると、使用するかどうかに関係なく、その例外が発生しますTextIOWrapper。単に例外を処理できます。
jbg

1
あなたはおそらくパーティーに遅れますが、答えは最新バージョンのPythonで最新です、ty
Dusan Gligoric

20

実際、イテレータを整理した場合、バッファリングが問題になる可能性があります。出力をバッファリングしないように、サブプロセスのpythonに指示することができます。

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

なる

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

python内からpythonを呼び出すときにこれが必要です。


14

これらの追加のパラメーターをに渡したい場合subprocess.Popen

bufsize=1, universal_newlines=True

その後、例のように反復できます。(Python 3.5でテスト済み)


2
@nicoulaj subprocess32パッケージを使用すれば動作するはずです。
Quantum7 2017

4

両方の反復処理できる機能stdoutstderr、リアルタイムで、同時にラインずつ

ケースでは、両方の出力ストリームを取得する必要があるstdoutstderr同時に、あなたは以下の機能を使用することができます。

この関数は、キューを使用して両方のPopenパイプを1つのイテレーターにマージします。

ここで関数を作成しますread_popen_pipes()

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()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() 使用中で:

import subprocess as sp


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

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

ループなしで行を読み取ることもできます。python3.6で動作します。

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
または、文字列に変換するには:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

私はこれをpython3で試してみましたが、うまくいきました、ソース

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

次のロムロの回答の修正は、Python 2および3(2.7.12および3.6.1)で私に有効です。

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

これがサブプロセスモジュールに追加されたときのDunnoですが、Python 3では次のようにして問題ありませんproc.stdout.splitlines()

for line in proc.stdout.splitlines():
   print "stdout:", line
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.