Pythonで子プロセスの出力を読む

子プロセスが下のような「何かを出力して終了する」というものなら何も考えずに子プロセスの標準出力から読んでしまってかまわない。

# print.py
print "Hello!"
# readline.py
import sys
from subprocess import Popen, PIPE
p = Popen(sys.argv[1:], stdout=PIPE)
print "data:", p.stdout.readline()
$ python readline.py python print.py 
data: Hello!

しかし、子プロセスが下のような終了しないものの場合、この方法では読むことができないことがある。

print "Hello!"
from time import sleep
while True:
    sleep(1)
$ python readline.py python print.py 
data: (表示されないまま待機される)

これは、子プロセスの標準出力がflushされないせいである。子プロセスのコードいじれる場合は下のようにflushを付け加えると読めるようになる。

print "Hello!"

import sys
sys.stdout.flush()

from time import sleep
while True:
    sleep(1)

しかし、すべての出力にflushをつけて回るのは大変。ptyを使ってターミナルの振りをするとこういうものも読むことができる。

import sys
import pty
import os

def reader(fileno):
    return os.read(fileno, 1)

pty.spawn(sys.argv[1:], reader)

ただし、この場合os.readで読んだものは子プロセスが出力した文字列自体ではなく、ターミナルに送られた制御コードである。例えば出力されたものが改行(\n, \x0A)であっても、ターミナルが受け取るのはCR+LF(\r\n, \x0D\x0A)である。

        • -

光成さんから子プロセス側がPythonで書かれている場合にはpython -u print.pyでバッファリングしないモードにできるという話を聞きました。確かに上のブロックされる例の場合はpython readline.py python -u print.py とやると問題なく動きますね。Rubyにも同じようなオプションがあるのかな。

        • -

id:kazuhooku さんからの指摘で:

  • ruby だと STDOUT.sync = true
  • perl だと $| = 1 または $fh.autoflush(1)