web-dev-qa-db-ja.com

サブプロセスの標準出力を1行ずつ読み込む

私の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()

私が本当に望んでいる振る舞いは、フィルタスクリプトが各行をサブプロセスから受け取ったとおりに表示することです。 tee のようなものですが、Pythonコードを使用してください。

何が足りないの?これでも可能ですか?


更新:

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()
199
deft_code

私が最後に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)。

160
Rômulo Ceccon

パーティーには少々時間がかかりましたが、ここで最も単純な解決策だと私が思っているものが見えないことに驚きました。

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
35
jbg

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

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

になる

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

私はpythonの中からpythonを呼び出すときにこれを必要としていました。

16
Steve Carter

これらの追加パラメータをsubprocess.Popenに渡します。

bufsize=1, universal_newlines=True

それからあなたはあなたの例のように繰り返すことができます。 (Python 3.5でテスト済み)

13
user1747134

次のRômuloの答えの修正は、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
1
mdh

ループのない行を読むこともできます。 python3.6で動作します。

import os
import subprocess

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

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

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
shakram02

リアルタイムで行ごとにstdoutstderrの両方を同時に反復できる関数

stdoutstderrの両方の出力ストリームを同時に取得する必要がある場合は、次の関数を使用できます。

この関数は、キューを使用して両方のPopenパイプを単一の反復子にマージします。

ここで、関数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()
                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
0
Rotareti