web-dev-qa-db-ja.com

サブプロセスを使用してリアルタイム出力を取得する

操作のニース進行状況インジケーターを表示するコマンドラインプログラム(svnadmin verify)のラッパースクリプトを記述しようとしています。これには、ラップされたプログラムからの出力の各行が出力されるとすぐに見ることができる必要があります。

subprocess.Popenを使用してプログラムを実行し、stdout=PIPEを使用してから、各行を読み込んで、それに応じて処理するだけだと考えました。ただし、次のコードを実行すると、出力がどこかにバッファリングされているように見え、1行目から332行目、次に333から439行目(出力の最後の行)の2つのチャンクに表示されます

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, Shell = True)
for line in p.stdout:
    print line.replace('\n', '')

サブプロセスのドキュメントを少し見て、bufsizeパラメーターがPopenになっていることを発見したので、bufsizeを1(各行のバッファー)と0(バッファーなし)に設定しようとしましたが、値はありません線の配達方法を変えたようです。

この時点でストローを把握し始めていたので、次の出力ループを作成しました。

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

しかし、同じ結果を得ました。

サブプロセスを使用して実行されたプログラムの「リアルタイム」プログラム出力を取得することは可能ですか? Pythonにはexec*ではなく、前方互換性がある)他のオプションがありますか?

122
Chris Lieb

私はこれを試しましたが、何らかの理由でコードが

for line in p.stdout:
  ...

積極的にバッファ、バリアント

while True:
  line = p.stdout.readline()
  if not line: break
  ...

ではない。明らかにこれは既知のバグです: http://bugs.python.org/issue3907 (2018年8月29日現在、この問題は「クローズ」です)

77
Dave
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()
36
Corey Goldberg

これを試すことができます:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Readの代わりにreadlineを使用すると、入力メッセージが出力されない場合があります。インライン入力が必要なコマンドで試してみてください。

18
Nadia Alramli

サブプロセスの出力を直接ストリームに送信できます。簡単な例:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
18
Aidan Feldman

ユースケースによっては、サブプロセス自体のバッファリングを無効にすることもできます。

サブプロセスがPythonプロセスである場合、呼び出しの前にこれを行うことができます。

os.environ["PYTHONUNBUFFERED"] = "1"

または、これをenv引数でPopenに渡します。

それ以外の場合、Linux/Unixを使用している場合は、stdbufツールを使用できます。例えば。好む:

cmd = ["stdbuf", "-oL"] + cmd

here about stdbufまたはその他のオプションも参照してください。

(同じ回答については here も参照してください。)

3
Albert

リアルタイム出力の問題が解決されました:cプログラムからリアルタイム出力をキャプチャしているときに、Pythonで同様の問題が発生しました。 「fflush(stdout);」を追加しました私のCコードで。それは私のために働いた。ここにコードの抜粋があります

<< Cプログラム>>

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

<< Python Program >>

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

<<出力>>印刷:カウント1印刷:カウント2印刷:カウント3

それが役に立てば幸い。

〜サイラム

3
sairam

私はしばらく前に同じ問題に遭遇しました。私の解決策は、サブプロセスの実行が完了していなくてもすぐに戻るreadメソッドの反復処理を終了することでした。

3
Eli Courtwright

PythonのasyncioでのサブプロセスstdinおよびstdoutのストリーミングKevin McCarthy によるブログ投稿では、asyncioでそれを行う方法を示しています。

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __== "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
3
Pablo

サブプロセスの出力の各バイトに対してイテレータを使用できます。これにより、サブプロセスからのインライン更新(「\ r」で終わる行が前の出力行を上書きする)が可能になります。

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")
2
rhyno183

このソリューションを使用して、サブプロセスでリアルタイムの出力を取得しました。このループは、プロセスが完了するとすぐに停止し、breakステートメントまたは無限ループの可能性がなくなります。

sub_process = subprocess.Popen(my_command, close_fds=True, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()
2
Jason Hedlund

この「プラグアンドプレイ」機能を見つけました here 。魅力のように働いた!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)
1
Deena

非ブロッキングリードラインでpexpect [ http://www.noah.org/wiki/Pexpect ]を使用すると、この問題は解決します。これは、パイプがバッファリングされているため、アプリの出力がパイプによってバッファリングされているため、バッファがいっぱいになるかプロセスが終了するまでその出力に到達できません。

1
Gabe

これは私がいつもこれに使用する基本的なスケルトンです。これにより、タイムアウトの実装が容易になり、避けられないハングプロセスに対処できます。

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           Shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()
0
Badslacks

(このソリューションはPython 2.7.15)でテスト済みです)
各行の読み取り/書き込み後にsys.stdout.flush()を実行するだけです。

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
0
dan

完全なソリューション:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()
0
Andres Restrepo