web-dev-qa-db-ja.com

大出力のプロセスにsubprocess.Popenを使用する

Python外部アプリを実行するコードがあります。アプリの出力が少ない場合は正常に動作しますが、出力が多い場合はハングします。コードは次のようになります。

_p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errcode = p.wait()
retval = p.stdout.read()
errmess = p.stderr.read()
if errcode:
    log.error('cmd failed <%s>: %s' % (errcode,errmess))
_

潜在的な問題を示しているように見えるコメントがドキュメントにあります。待機中です:

警告:子プロセスがstdoutまたはstderrパイプに十分な出力を生成し、OSパイプバッファーがより多くのデータを受け入れるのを待機するのをブロックすると、デッドロックが発生します。これを回避するには、communicate()を使用します。

コミュニケーション中ですが、私は見ます:

注読み取ったデータはメモリにバッファリングされるため、データサイズが大きい場合や無制限の場合は、この方法を使用しないでください。

したがって、大量のデータがある場合、これらのいずれかを使用する必要があるかどうかはわかりません。その場合、どの方法を使用すべきかを示していません。

Execからの戻り値が必要であり、stdoutstderrの両方を解析して使用します。

では、Pythonで、出力が大きくなる外部アプリを実行するための同等のメソッドは何ですか?

31
Tim

2つのファイルへの読み取りをブロックしています。 2番目が始まる前に最初のものが完了する必要があります。アプリケーションがstderrに大量に書き込み、stdoutに何も書き込まない場合、プロセスはstdoutのデータが来ないのを待って待機しますが、 re runningは、stderrに書き込んだものが読み取られるのを待ってそこに座っています(stdoutを待っているので、決して読み取られません)。

これを修正する方法はいくつかあります。

最も簡単なのは、stderrを傍受しないことです。 stderr=Noneを残します。エラーはstderrに直接出力されます。それらを傍受して、独自のメッセージの一部として表示することはできません。コマンドラインツールの場合、これは多くの場合問題ありません。他のアプリの場合、問題になる可能性があります。

もう1つの簡単なアプローチは、stderrstdoutにリダイレクトすることです。そのため、受信ファイルは1つだけです:set stderr=STDOUT。これは、通常の出力とエラー出力を区別できないことを意味します。これは、アプリケーションが出力を書き込む方法に応じて、受け入れられる場合と受け入れられない場合があります。

これを処理する完全で複雑な方法はselecthttp://docs.python.org/library/select.html )です。これにより、非ブロッキング方式で読み取ることができます。データがstdoutまたはstderrのいずれかに表示されるたびにデータを取得します。本当に必要な場合にのみお勧めします。これはおそらくWindowsでは機能しません。

17
Glenn Maynard

stdoutを使用して非常に大きな出力(つまり、メガバイト数)でstderrselectを個別に読み取る:

import subprocess, select

proc = subprocess.Popen(cmd, bufsize=8192, Shell=False, \
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)

with open(outpath, "wb") as outf:
    dataend = False
    while (proc.returncode is None) or (not dataend):
        proc.poll()
        dataend = False

        ready = select.select([proc.stdout, proc.stderr], [], [], 1.0)

        if proc.stderr in ready[0]:
            data = proc.stderr.read(1024)
            if len(data) > 0:
                handle_stderr_data(data)

        if proc.stdout in ready[0]:
            data = proc.stdout.read(1024)
            if len(data) == 0: # Read of zero bytes means EOF
                dataend = True
            else:
                outf.write(data)
8
vz0

グレンメイナードは、デッドロックについての彼のコメントに正しいです。ただし、この問題を解決する最善の方法は、stdout用とstderr用の2つのスレッドを作成することです。これらのスレッドは、使い果たされるまでそれぞれのストリームを読み取り、出力で必要なことをすべて実行します。

一時ファイルの使用の提案は、出力のサイズなど、およびサブプロセスの出力が生成されるときに処理する必要があるかどうかによって、機能する場合と機能しない場合があります。

Heikki Toivonenが示唆しているように、communicateメソッドを確認する必要があります。ただし、これはサブプロセスのstdout/stderrをメモリにバッファリングし、communicate呼び出しから返されるものを取得します。これは一部のシナリオには理想的ではありません。しかし、通信方法のソースは一見の価値があります。

もう1つの例は、私が管理しているパッケージ python-gnupg にあります。ここで、gpg実行可能ファイルはsubprocessを介して生成され、手間のかかる作業を行います。Pythonラッパーはスレッドを生成してgpgのstdoutとstderrを読み取り、データがgpgによって生成されるときにそれらを消費します。そこでソースを確認することでもいくつかのアイデアを得ることができる場合があります。gpgによって生成されたデータから両方のstdoutへ一般的な場合、stderrは非常に大きくなる可能性があります。

6
Vinay Sajip

大量の出力主観的であるため、推奨を行うのは少し難しいです。出力の量が本当に大きい場合は、とにかく1回のread()呼び出しですべてを取得したくない可能性があります。出力をファイルに書き込んでから、次のようにデータを段階的にプルすることをお勧めします。

f=file('data.out','w')
p = subprocess.Popen(cmd, Shell=True, stdout=f, stderr=subprocess.PIPE)
errcode = p.wait()
f.close()
if errcode:
    errmess = p.stderr.read()
    log.error('cmd failed <%s>: %s' % (errcode,errmess))
for line in file('data.out'):
    #do something
6
Mark Roddy

私も同じ問題を抱えていました。大きな出力を処理する必要がある場合、別の良いオプションは、stdoutとstderrのファイルを使用し、パラメーターごとにそれらのファイルを渡すことです。

Pythonのtempfileモジュールを確認してください: https://docs.python.org/2/library/tempfile.html

このようなものがうまくいくかもしれません

out = tempfile.NamedTemporaryFile(delete=False)

次に、次のようにします。

Popen(... stdout=out,...)

その後、ファイルを読み取り、後で消去できます。

3
Mariano Anaya

あなたはコミュニケーションを試みて、それがあなたの問題を解決するかどうか見ることができます。そうでない場合は、出力を一時ファイルにリダイレクトします。

2
Heikki Toivonen

これは、通常の出力とエラー出力の両方をキャプチャする単純なアプローチです。すべてPython内にあるため、stdoutの制限は適用されません。

com_str = 'uname -a'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, Shell=True)
(output, error) = command.communicate()
print output

Linux 3.11.0-20-generic SMP Fri May 2 21:32:55 UTC 2014 

そして

com_str = 'id'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, Shell=True)
(output, error) = command.communicate()
print output

uid=1000(myname) gid=1000(mygrp) groups=1000(cell),0(root)
0
SDsolar