概要
subprocess.Popenで標準出力に出力される内容を追いかけて読んでいきます。この際、readline()で改行ごとに読むのではなく、read()を使ってひたすら(?)読んでみます。ストリーム周りではよく悩むのでメモ。
CentOS7上でPython 2.7.5で動作確認しました。
文字出力スクリプト
Popenで起動される側のスクリプトです
textout.py
#!/usr/bin/env python
import sys
import time
for i in range(0, 10):
sys.stdout.write("[Count {0:04d}]".format(i))
time.sleep(1)
1秒ごとに[Count 0000]
を出力する単純なスクリプトです。ただ、改行しないので、readline()では読めません。
実行スクリプト
上のスクリプトをPopenで呼び出し、標準出力を読んでいきます。
reader.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os, time, errno, fcntl
from subprocess import Popen, PIPE
# Popenでtextout.pyを実行する
# このときpythonに -u オプションを渡し、バッファリングしないようにする
p = Popen([sys.executable, "-u", "textout.py"], bufsize=0, stdout=PIPE)
# p.stdoutをノンブロッキングモードにする
flag = fcntl.fcntl(p.stdout.fileno(), fcntl.F_GETFL)
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK)
while True:
# 読み込みループ
try:
# p.stdoutを読み込む
# ノンブロッキングモードなので読める分だけ読む
buf = p.stdout.read()
if buf == "": break # p.stdoutがクローズされたら抜ける
sys.stdout.write(buf) # 読んだ内容を出力
sys.stdout.flush() # 改行がないのでflush()
except IOError, e:
# 読むべき内容がない場合はIOError(11,
# "Resource temporarily unavailable")が
# スローされるので待機
if e.errno == errno.EAGAIN:
time.sleep(0.1)
p.wait()
実行結果
$ ./reader.py
[Count 0000][Count 0001][Count 0002][Count 0003][Count 0004][Count 0005][Count 0006][Count 0007][Count 0008][Count 0009]
1秒ごとに[Count 0000]
が出力されます。
ポイント
- textout.pyを実行する際はバッファリングしない、またはsys.stdoutに書き込んだらflush()する
- reader.pyはread()でp.stdoutを読むので、ノンブロッキングモードで読む
- 出力待ちの時はerrno.EAGAINが送出されるので、例外処理して待機する
- textout.pyが終了するまで待つならこんな面倒なことをせずにread()一発(^^;