web-dev-qa-db-ja.com

stdoutプロセスをリアルタイムで読み取る

このスニペットについて考えてみましょう。

_from subprocess import Popen, PIPE, CalledProcessError


def execute(cmd):
    with Popen(cmd, Shell=True, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
        for line in p.stdout:
            print(line, end='')

    if p.returncode != 0:
        raise CalledProcessError(p.returncode, p.args)

base_cmd = [
    "cmd", "/c", "d:\\virtual_envs\\py362_32\\Scripts\\activate",
    "&&"
]
cmd1 = " ".join(base_cmd + ['python -c "import sys; print(sys.version)"'])
cmd2 = " ".join(base_cmd + ["python -m http.server"])
_

execute(cmd1)を実行すると、出力は問題なく出力されます。

ただし、代わりにexecute(cmd2)を実行すると、何も出力されません。その理由と、http.serverの出力をリアルタイムで確認できるように修正するにはどうすればよいですか。

また、_for line in p.stdout_は内部でどのように評価されますか? stdout eofか何かに到達するまで、それはある種の無限ループですか?

このトピックは、ここSOで数回取り上げられていますが、Windowsソリューションはまだ見つかりません。上記のスニペットは実際にはこれからのコードです answer そして試してみてくださいvirtualenvからhttp.serverを実行する(win7ではpython3.6.2-32ビット)

17
BPL

実行中のサブプロセスから継続的に読み取りたい場合は、thatプロセスの出力をバッファなしにする必要があります。サブプロセスはPythonプログラムです。これは、-uをインタープリターに渡すことで実行できます。

python -u -m http.server

これは、Windowsボックスでの外観です。

enter image description here

5
fpbhb

このコードでは、バッファリングのためにリアルタイム出力を表示できません。

_for line in p.stdout:
    print(line, end='')
_

しかし、p.stdout.readline()を使用すると、機能するはずです。

_while True:
  line = p.stdout.readline()
  if not line: break
  print(line, end='')
_

詳細については、対応する pythonバグディスカッション を参照してください

UPD:ここでは、stackoverflowでほぼ同じ さまざまなソリューションの問題 を見つけることができます。

5

主な問題はhttp.serverどういうわけか出力をstderrに記録しています。ここでは、asyncioまたはstdoutからデータを読み取るstderrの例を示します。

私の最初の試みは、Python 3.4以降に存在するNiceAPIであるasyncioを使用することでした。後で、より簡単な解決策を見つけたので、選択できます。両方のemが機能するはずです。

ソリューションとしての非同期

バックグラウンドで、asyncioは [〜#〜] iocp [〜#〜] -WindowsAPIを使用して同期を行っています。

# inspired by https://pymotw.com/3/asyncio/subprocesses.html

import asyncio
import sys
import time

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)

async def run_webserver():
    buffer = bytearray()

    # start the webserver without buffering (-u) and stderr and stdin as the arguments
    print('launching process')
    proc = await asyncio.create_subprocess_exec(
        sys.executable, '-u', '-mhttp.server',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    print('process started {}'.format(proc.pid))
    while 1:
        # wait either for stderr or stdout and loop over the results
        for line in asyncio.as_completed([proc.stderr.readline(), proc.stdout.readline()]):
            print('read {!r}'.format(await line))

event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(run_df())
finally:
    event_loop.close()

stdoutからのリダイレクト

あなたの例に基づくと、これは本当に簡単な解決策です。 stderrをstdoutにリダイレクトするだけで、stdoutのみが読み取られます。

from subprocess import Popen, PIPE, CalledProcessError, run, STDOUT import os

def execute(cmd):
    with Popen(cmd, stdout=PIPE, stderr=STDOUT, bufsize=1) as p:
        while 1:
            print('waiting for a line')
            print(p.stdout.readline())

cmd2 = ["python", "-u", "-m", "http.server"]

execute(cmd2)
3
uphill

OSレベルでバッファなしの動作を実装できます。

Linuxでは、既存のコマンドラインをstdbufでラップできます。

stdbuf -i0 -o0 -e0 YOURCOMMAND

または、Windowsでは、既存のコマンドラインをwinptyでラップできます。

winpty.exe -Xallow-non-tty -Xplain YOURCOMMAND

私はこのためのOSに中立なツールを知りません。

3
Chris Johnson

P.stdoutの行は内部でどのように評価されますか? stdout eofか何かに到達するまで、それはある種の無限ループですか?

_p.stdout_はバッファ(ブロッキング)です。 emptyバッファーから読み取る場合、そのバッファーに何かが書き込まれるまでブロックされます。何かが入ったら、データを取得して内部を実行します。

_tail -f_がLinuxでどのように機能するかを考えてみてください。ファイルに何かが書き込まれるまで待機し、書き込まれると、新しいデータが画面にエコーされます。データがない場合はどうなりますか? 待機します。したがって、プログラムがこの行に到達すると、データを待機して処理します。

コードは機能しますが、モデルとして実行しない場合は、何らかの形でこれに関連している必要があります。 _http.server_モジュールはおそらく出力をバッファリングします。 _-u_パラメーターをPythonに追加して、プロセスをバッファーなしで実行してみてください。

-u:バッファリングされていないバイナリstdoutおよびstderr;また、PYTHONUNBUFFERED = x'-u 'に関連する内部バッファリングの詳細については、manページを参照してください。

また、ループをfor line in iter(lambda: p.stdout.read(1), ''):に変更してみてください。これは、処理前に一度に_1_バイトを読み取るためです。


更新:完全なループコードは

_for line in iter(lambda: p.stdout.read(1), ''):
    sys.stdout.write(line)
    sys.stdout.flush()
_

また、コマンドを文字列として渡します。各要素を独自のスロットに入れて、リストとして渡してみてください。

_cmd = ['python', '-m', 'http.server', ..]
_
3
Chen A.