質問

コマンドラインプログラム(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*ではない)他のオプションがありますか?

役に立ちましたか?

解決

これを試しましたが、何らかの理由でコードの実行中

for line in p.stdout:
  ...

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

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

しません。明らかにこれは既知のバグです: http://bugs.python.org/issue3907 (問題は現在<!> quot; Closed <!> quot; 2018年8月29日現在)

他のヒント

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

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

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を使用すると、入力メッセージが出力されない場合があります。インライン入力が必要なコマンドで試してみてください。

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

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

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

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

<!> lt; <!> lt; Cプログラム<!> gt; <!> gt;

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

<!> lt; <!> lt; Pythonプログラム<!> gt; <!> gt;

#!/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)

<!> lt; <!> lt; OUTPUT <!> gt; <!> gt; 印刷:カウント1 印刷:カウント2 印刷:カウント3

お役に立てば幸いです。

〜sairam

サブプロセスの出力の各バイトに対してイテレータを使用できます。これにより、サブプロセスからのインライン更新(「\ 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.")

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

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

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

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

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

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

<=>またはその他のオプションについては、こちらも参照してください。

(同じ回答については、こちらも参照してください。)

ストリーミング Kevin McCarthy による、Pythonのasyncioを使用したサブプロセスstdinおよびstdoutのブログ投稿 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 __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

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

このソリューションを使用して、サブプロセスでリアルタイム出力を取得しました。このループは、プロセスが完了するとすぐに停止し、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()

この<!> quot; plug-and-play <!> quot;が見つかりました。関数こちら。魔法のように働いた!

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)

完全なソリューション:

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

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

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

(このソリューションは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()
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top