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からの戻り値が必要であり、stdout
とstderr
の両方を解析して使用します。
では、Pythonで、出力が大きくなる外部アプリを実行するための同等のメソッドは何ですか?
2つのファイルへの読み取りをブロックしています。 2番目が始まる前に最初のものが完了する必要があります。アプリケーションがstderr
に大量に書き込み、stdout
に何も書き込まない場合、プロセスはstdout
のデータが来ないのを待って待機しますが、 re runningは、stderr
に書き込んだものが読み取られるのを待ってそこに座っています(stdout
を待っているので、決して読み取られません)。
これを修正する方法はいくつかあります。
最も簡単なのは、stderr
を傍受しないことです。 stderr=None
を残します。エラーはstderr
に直接出力されます。それらを傍受して、独自のメッセージの一部として表示することはできません。コマンドラインツールの場合、これは多くの場合問題ありません。他のアプリの場合、問題になる可能性があります。
もう1つの簡単なアプローチは、stderr
をstdout
にリダイレクトすることです。そのため、受信ファイルは1つだけです:set stderr=STDOUT
。これは、通常の出力とエラー出力を区別できないことを意味します。これは、アプリケーションが出力を書き込む方法に応じて、受け入れられる場合と受け入れられない場合があります。
これを処理する完全で複雑な方法はselect
( http://docs.python.org/library/select.html )です。これにより、非ブロッキング方式で読み取ることができます。データがstdout
またはstderr
のいずれかに表示されるたびにデータを取得します。本当に必要な場合にのみお勧めします。これはおそらくWindowsでは機能しません。
stdout
を使用して非常に大きな出力(つまり、メガバイト数)でstderr
とselect
を個別に読み取る:
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)
グレンメイナードは、デッドロックについての彼のコメントに正しいです。ただし、この問題を解決する最善の方法は、stdout用とstderr用の2つのスレッドを作成することです。これらのスレッドは、使い果たされるまでそれぞれのストリームを読み取り、出力で必要なことをすべて実行します。
一時ファイルの使用の提案は、出力のサイズなど、およびサブプロセスの出力が生成されるときに処理する必要があるかどうかによって、機能する場合と機能しない場合があります。
Heikki Toivonenが示唆しているように、communicate
メソッドを確認する必要があります。ただし、これはサブプロセスのstdout/stderrをメモリにバッファリングし、communicate
呼び出しから返されるものを取得します。これは一部のシナリオには理想的ではありません。しかし、通信方法のソースは一見の価値があります。
もう1つの例は、私が管理しているパッケージ python-gnupg にあります。ここで、gpg
実行可能ファイルはsubprocess
を介して生成され、手間のかかる作業を行います。Pythonラッパーはスレッドを生成してgpgのstdoutとstderrを読み取り、データがgpgによって生成されるときにそれらを消費します。そこでソースを確認することでもいくつかのアイデアを得ることができる場合があります。gpgによって生成されたデータから両方のstdoutへ一般的な場合、stderrは非常に大きくなる可能性があります。
大量の出力主観的であるため、推奨を行うのは少し難しいです。出力の量が本当に大きい場合は、とにかく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
私も同じ問題を抱えていました。大きな出力を処理する必要がある場合、別の良いオプションは、stdoutとstderrのファイルを使用し、パラメーターごとにそれらのファイルを渡すことです。
Pythonのtempfileモジュールを確認してください: https://docs.python.org/2/library/tempfile.html 。
このようなものがうまくいくかもしれません
out = tempfile.NamedTemporaryFile(delete=False)
次に、次のようにします。
Popen(... stdout=out,...)
その後、ファイルを読み取り、後で消去できます。
あなたはコミュニケーションを試みて、それがあなたの問題を解決するかどうか見ることができます。そうでない場合は、出力を一時ファイルにリダイレクトします。
これは、通常の出力とエラー出力の両方をキャプチャする単純なアプローチです。すべて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)