LoginSignup
6
9

More than 5 years have passed since last update.

subprocess.Popenの出力をリアルタイムで読む

Posted at

概要

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()一発(^^;
6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9