Edited at

subprocess.Popenで実行したコマンドの出力を徐々に表示する

More than 3 years have passed since last update.

subprocessモジュールを使って外部コマンドを実行する際には、communicateメソッドをよく使用していた。


subprocess.Popen.communicate()

import subprocess

(stdoutdata, stderrdata) = subprocess.Popen(['some', 'command'], stdout=subprocess.PIPE).communicate()
print(stdoutdata)


しかしビルドスクリプトなど、結果を非同期に出力したい場合はcommunicateメソッドでは実現できない。ライブラリリファレンスにあるとおり、communicateメソッドはプロセス終了まで待ち、その後に標準出力のデータを返すからだ。

この要求を実現したい場合、ioモジュールを使って子プロセスの出力に対応したストリームを作成する必要がある。

(ref) http://docs.python.jp/2/library/subprocess.html#subprocess.Popen.stdout

(ref) https://gist.github.com/mattbornski/3299031


子プロセスの出力に対応したストリーム

def exec_process(commands):

process = subprocess.Popen(commands, stdout=subprocess.PIPE, bufsize=-1)
with io.open(process.stdout.fileno(), closefd=False) as stream:
[print(line.rstrip('\n')) for line in stream]

# プロセス終了まで待ち、結果を判定する
process.wait()
if process.returncode != 0:
print('Build process aborts.')
sys.exit(1)


以下、何点かハマった点のメモ

1.io.openでストリームを生成する際には、ファイルパスまたはファイル記述子((file object).fileno()で取得可能)を与える必要がある。subprocess.Popen.stdoutのパス名は分からないため、ファイル記述子を与える必要がある。ファイルオブジェクト(=subprocess.Popen.stdout)をそのまま与えるのではないことに注意する。

(ref) http://docs.python.jp/2/library/io.html#io.open

# process.stdoutをio.openに直接与えると実行時例外で終了する。

def exec_process(commands):
process = subprocess.Popen(commands, stdout=subprocess.PIPE, bufsize=-1)
with io.open(process.stdout, closefd=False) as stream:
[print(line.rstrip('\n')) for line in stream]

2.io.openでストリームを生成する際、closefd=Falseを指定する必要がある。デフォルトではclosefd=Trueであり、その状態だと以下のエラーが発生する。

close failed in file object destructor:

IOError: [Errno 9] Bad file descriptor

streamが閉じられるタイミング(=今回の場合、with文を抜ける時)で、子プロセスの掴んでいるファイルオブジェクト(=subprocess.Popen.stdout)を閉じようとすることが原因だと考えられる。