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


135

操作の進行状況インジケーターを表示するコマンドラインプログラム(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*)オプションが他にありますか?


1
sydout=PIPEサブプロセスが親プロセスをバイパスして直接コンソールに書き込むように、を省略してみましたか?
S.Lott、2009

5
問題は、出力を読みたいということです。コンソールに直接出力される場合、どうすればよいですか?また、ラップされたプログラムからの出力をユーザーに見せたくありません。私の出力だけです。
Chris Lieb、

では、なぜ「リアルタイム」ディスプレイなのでしょうか。ユースケースがわかりません。
S.Lott、2009

8
shell = Trueは使用しないでください。むやみにシェルを呼び出します。代わりにp = Popen(['svnadmin'、 'verify'、 '/ var / svn / repos / config']、stdout = PIPE、stderr = STDOUT)を使用してください
nosklo

2
@ S.Lott基本的に、svnadmin verifyは、検証されたすべてのリビジョンの出力行を出力します。過剰な出力を引き起こさない素敵な進捗インジケーターを作りたかったのです。たとえば、wgetのようなもの
Chris Lieb、

回答:


82

私はこれを試しました、そしてなぜかコードが

for line in p.stdout:
  ...

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

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

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


これは、古いPython IO実装の唯一の問題ではありません。これが、Py2.6とPy3kが完全に新しいIOライブラリで終わった理由です。
Tim Lin、

3
このコードは、サブプロセスが空の行を返すと壊れます。より良い解決策は、のwhile p.poll() is None代わりにを使用while Trueして削除することですif not line
exhuma 2009

6
@exhuma:正常に動作します。readlineは、空の行で「\ n」を返します。これは、trueと評価されません。パイプが閉じたときにのみ空の文字列を返します。これは、サブプロセスが終了したときに行われます。
アリスパーセル

1
@Dave将来の参考のために:utf-8の行をpy2 +でprint(line.decode('utf-8').rstrip())
Jonathan Komar 2016年

3
また、プロセスの出力をリアルタイムで読み取るには、バッファリングを望まないことをpythonに伝える必要があります。親愛なるPythonは私に直接出力を与えます。そしてここに方法があります:環境変数を設定する必要がありますPYTHONUNBUFFERED=1。これは、無限の出力に特に役立ちます
George Pligoropoulos


29

サブプロセスの出力を直接ストリームに送ることができます。簡略化した例:

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

これにより、事後のコンテンツも取得できます.communicate()か?または、コンテンツは親stderr / stdoutストリームに失われますか?
theferrit32

いいえ、communicate()返されたメソッドはありませんCompletedProcess。また、およびとcapture_output相互に排他的です。stdoutstderr
エイダンフェルドマン

20

あなたはこれを試すことができます:

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


はい、readline()を使用すると印刷が停止します(sys.stdout.flush()を呼び出しても)
Mark Ma

3
これは無期限にハングアップするはずですか?最初のサブプロセスが完了したときにループを編集するための定型コードも含めて、特定のソリューションが欲しいと思います。申し訳ありませんが、何度調べても、サブプロセスなどは、私が仕事に取り掛かることができないものです。
ThorSummoner 2014年

1
Pythonで ''をテストするのはなぜですか?
グレッグベル、

2
これは、長時間実行するジョブに最適なソリューションです。しかし、それはNoneではなく、!= Noneではありません。Noneと一緒に!=を使用しないでください。
Cari

これによりstderrも表示されますか?
Pieter Vogelaar

7

ストリーミングサブプロセスのstdinとstdoutとasyncioのPythonでのブログのポストケビン・マッカーシー 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())

これは、投稿されたコードにわずかな変更を加えれば機能します
Jeef

こんにちは@Jeef私が答えを更新できるように修正を指摘できますか?
Pablo

こんにちは、それは私にとってはうまくいきましたが、いくつかのエラーメッセージを取り除くために以下を追加する必要がimport nest_asyncio; nest_asyncio.apply()ありました:シェルコマンドを使用する、すなわちのprocess = await create_subprocess_shell(*command, stdout=PIPE, stderr=PIPE, shell=True)代わりにprocess = await create_subprocess_exec(...)。乾杯!
user319436

4

リアルタイム出力の問題の解決: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プログラム>>

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

それが役に立てば幸い。

〜sairam


1
これが実際に役立った唯一のことでした。flush(stdout)C ++では同じコード()を使用しました。ありがとう!
ゲルハルトハゲラー

私は、Pythonスクリプトがサブプロセスとして別のPythonスクリプトを呼び出すことで同じ問題を抱えていました。サブプロセスプリントでは、「フラッシュ」が必要でした(python 3ではprint( "hello"、flush = True))。また、多くの例がまだ(2020)python 2、これはpython 3なので、+ 1
smajtkst

3

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


3

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

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

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

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

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

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

またはその他のオプションについては、こちらもご覧くださいstdbuf

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


2

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

5
stdoutバッファーが空でなくてもループが終了する可能性はありますか?
jayjay 14

完了しても問題が解決しない適切な答えを探しました。私は追加することで解決策として、これを見つけif out=='': breakた後out = sub_process...
ソス

2

この「プラグアンドプレイ」機能はここにあります。魅力のように働いた!

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
の追加は、stderr=subprocess.STDOUTストリーミングデータのキャプチャに実際に役立ちます。私は賛成しています。
カーン

1
ここでの主な牛肉は受け入れられた答え
tripleee

2

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

Python 3.xでは、出力が文字列ではなくバイト配列であるため、プロセスがハングすることがあります。必ず文字列にデコードしてください。

Python 3.6以降ではencodingPopenコンストラクターのパラメーターを使用して実行できます。完全な例:

process = subprocess.Popen(
    'my_command',
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)

このコードは出力エラーにリダイレクト stderrstdout処理することに注意してください。


1

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


0

完全なソリューション:

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

1
呼び出しで使用universal_newlines=TrueしているのでPopen()、おそらくそれらの独自の処理を行う必要もありません。それがオプションの要点です。
martineau 2013

1
不必要に複雑に思われます。バッファリングの問題は解決しません。私の回答のリンクを参照してください。
jfs 2014年

これは、rsyncの進行状況をリアルタイム(--outbuf = L)で出力できる唯一の方法です!感謝
Mohammadhzp

0

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

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

(このソリューションは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

python 3.xまたはpthon 2.xを示唆する回答はほとんどありません。以下のコードは両方で機能します。

 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
    stdout = []
    while True:
        line = p.stdout.readline()
        if not isinstance(line, (str)):
            line = line.decode('utf-8')
        stdout.append(line)
        print (line)
        if (line == '' and p.poll() != None):
            break
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.