subprocess.communicate()からストリーミング入力を読み取ります


83

私はPythonを使用subprocess.communicate()して、約1分間実行されるプロセスからstdoutを読み取ります。

そのプロセスの各行をstdoutストリーミング形式で印刷して、生成された出力を確認しながら、続行する前に終了するプロセスをブロックするにはどうすればよいですか?

subprocess.communicate() 一度にすべての出力を提供するように見えます。


回答:


44

JFセバスティアンの方法(下記)の方が良いと思いますのでご注意ください。


これは簡単な例です(エラーのチェックなし):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

場合lsは、すべてのデータを読んだ前に、両端が速すぎて、その後、whileループは終了します。

この方法で残りをstdoutでキャッチできます。

output = proc.communicate()[0]
print output,

1
このスキームは、Pythonドキュメントが参照するバッファブロッキングの問題の犠牲になりますか?
ハインリッヒシュメッターリング2010

@Heinrich、バッファブロッキングの問題は私がよく理解していることではありません。この問題は、whileループ内のstdout(およびstderr?)から読み取らない場合にのみ発生すると(グーグルで調べただけで)信じています。したがって、上記のコードは問題ないと思いますが、はっきりとは言えません。
unutbu 2010

1
これは実際にはブロッキングの問題に悩まされています。数年前、procが終了したとしても、readlineが改行を取得するまで、readlineがブロックするという問題に終わりはありませんでした。解決策は覚えていませんが、ワーカースレッドで読み取りを実行し、ループwhile proc.poll() is None: time.sleep(0)するか、そのための何かと関係があると思います。基本的に、出力改行がプロセスが最後に実行することを確認する必要があります(インタープリターに再度ループする時間を与えることができないため)、または「凝った」何かを行う必要があります。
dash-tom-bang

@Heinrich:アレックスマルテッリはここにデッドロックを回避する方法について書いている:stackoverflow.com/questions/1445627/...
unutbu

6
バッファブロッキングは、時々聞こえるよりも単純です。子が終了するのを待っている親ブロック+親が読み取って通信パイプ内のスペースを解放するのを待っている子ブロック=デッドロック。とても簡単です。パイプが小さいほど、発生する可能性が高くなります。
MarcH 2013年

160

サブプロセスがstdoutバッファをフラッシュするとすぐに、サブプロセスの出力を1行ずつ取得するには:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()Python 2の先読みバグを回避するために行が書き込まれるとすぐに行を読み取るために使用されます。

サブプロセスのstdoutが非対話モードでラインバッファリングの代わりにブロックバッファリングを使用する場合(これにより、子のバッファがいっぱいになるか、子によって明示的にフラッシュされるまで出力が遅延します)、を使用してバッファなしの出力を強制することができます pexpectptyモジュールまたはunbufferstdbufscriptユーティリティは、参照Qを:なぜちょうど(のpopen())のパイプを使用していませんか?


Python3のコードは次のとおりです。

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

注:サブプロセスのバイト文字列をそのまま出力するPython2とは異なります。Python 3はテキストモードを使用します(cmdの出力はlocale.getpreferredencoding(False)エンコーディングを使用してデコードされます)。


b ''はどういう意味ですか?
アーロン

4
b''bytesPython 2.7とPython 3のリテラルは
JFS

2
@JinghaoShi:サブプロセスにbufsize=1も(を使用して)書き込むと、違いが生じる可能性があります。p.stdinたとえば、pexpect子プロセス自体にバッファリングの問題がないと仮定すると、対話型(のような)交換を行う際のデッドロックを回避するのに役立ちます。読んでいるだけの場合、私が言ったように、違いはパフォーマンスだけです。そうでない場合は、それを示す最小限の完全なコード例を提供できますか?
jfs 2014

1
@ealeon:はい。stderrをstdoutにマージない限り(に渡すstderr=subprocess.STDOUTことによってPopen()stdout / stderrを同時に読み取ることができる手法が必要です。そこにリンクされているスレッド化または非同期ソリューションも参照してください。
jfs 2016年

2
@saulspatzstdout=PIPEが出力をキャプチャしない場合(画面に引き続き表示されます)、プログラムは代わりにstderrまたは直接端末に出力する場合があります。stdout&stderrをマージするには、パスしますstderr=subprocess.STDOUT(以前のコメントを参照)。ttyに直接出力された出力をキャプチャするには、pexpect、ptyソリューションを使用できます。。ここだ、より複雑なコード例
jfs 2017

6

ストリーミング方式でプロセスから出力を収集する最も簡単な方法は次のようなものだと思います。

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

readline()またはread()プロセスが終了した後に機能のみ、EOFに空の文字列を返すべきである-読むために何が(存在しない場合、それ以外の場合はブロックされますreadline()ので、空の行に、それは「\ n」を返し、改行を含みます)。これによりcommunicate()、ループ後に厄介な最終呼び出しを行う必要がなくなります。

非常に長い行のファイルではread()、最大メモリ使用量を減らすために望ましい場合があります-渡される数は任意ですが、それを除外すると、パイプ出力全体を一度に読み取ることになり、おそらく望ましくありません。


4
data = proc.stdout.read()すべてのデータが読み取られるまでブロックします。あなたはそれをos.read(fd, maxsize)(データが利用可能になるとすぐに)早く戻ることができると混同しているかもしれません。
jfs 2013

あなたは正しいです、私は間違っていました。ただし、適切なバイト数が引数として渡されたread()場合は正常にreadline()機能し、最大行長が適切である限り同様に正常に機能します。それに応じて私の答えを更新しました。
d Coetzee 2013


3

単に出力をリアルタイムで通過させようとしている場合、これよりも単純にするのは困難です。

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

subprocess.check_call()のドキュメントを参照してください。

出力を処理する必要がある場合は、必ずループしてください。ただし、そうでない場合は、単純にしてください。

編集: JFセバスチャンは、stdoutおよびstderrパラメーターのデフォルトがsys.stdoutおよびsys.stderrに渡されること、およびsys.stdoutおよびsys.stderrが置き換えられた場合(たとえば、テスト)。


実際のfileno()を持たないファイルのようなオブジェクトに置き換えられた場合、sys.stdoutまたはsys.stderr置き換えられた場合は機能しません。sys.stdoutsys.stderrが置き換えられない場合は、さらに簡単ですsubprocess.check_call(args)
jfs 2015

ありがとう!sys.stdout / stderrを置き換えることの気まぐれに気づきましたが、引数を省略すると、stdoutとstderrが適切な場所に渡されることにどういうわけか気づきませんでした。私が好きcall()以上check_call()、私が欲しい場合を除きCalledProcessError
ネイト

python -mthis「エラーは決して黙って通過するべきではありません。明示的に沈黙させない限り。」そのため、サンプルコードはよりも優先check_call()する必要がありcall()ます。
jfs 2015

ふふ。私が最終的に作成call()するプログラムの多くは、ひどいため、エラー以外の状態でゼロ以外のエラーコードを返します。したがって、この場合、ゼロ以外のエラーコードは実際にはエラーではありません。
ネイト

はい。grepエラーがない場合でもゼロ以外の終了ステータスを返す可能性があるようなプログラムがあります。これらは例外です。デフォルトでは、ゼロ終了ステータスは成功を示します。
jfs 2015

1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

1
人々の理解を深めるために、ソリューションが何をするのかを説明することは常に良いことです
DaFois 2017年

2
shlex.split(myCommand)代わりに使用することを検討する必要がありmyCommand.split()ます。引用符で囲まれた引数のスペースも尊重します。
UtahJarhead 2018

0

いくつかの小さな変更を加えた別のpython3ソリューションの追加:

  1. シェルプロセスの終了コードをキャッチできます(with構成の使用中に終了コードを取得できませんでした)
  2. また、stderrをリアルタイムでパイプします
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.