这是一个块缓冲问题。
接下来是我的答案的扩展版本,针对
Python: read streaming input from subprocess.communicate()问题。
直接在C程序中修复stdout缓冲
如果一个基于
stdio
的程序在终端交互式地运行,则通常是行缓冲的;当它们的
stdout
被重定向到一个管道时则是块缓冲的。在后一种情况下,除非缓冲区溢出或者刷新,否则你不会看到新的行。
为了避免每次
printf()
调用后都要调用
fflush()
,你可以在C程序中在最开始就调用以下方法来强制输出行缓冲:
setvbuf(stdout, (char *) NULL, _IOLBF, 0); /* make line buffered stdout */
一旦打印出换行符,缓冲区就会被刷新。
或者在不修改C程序源代码的情况下进行修复
有一个名为stdbuf
的实用程序,可以让您更改缓冲类型而无需修改源代码,例如:
from subprocess import Popen, PIPE
process = Popen(["stdbuf", "-oL", "./main"], stdout=PIPE, bufsize=1)
for line in iter(process.stdout.readline, b''):
print line,
process.communicate()
还有其他实用程序可用,请参见关闭管道缓冲。
或使用伪终端
为了欺骗子进程以认为自己正在交互式运行,您可以使用pexpect
模块或其类似模块。有关使用pexpect
和pty
模块的代码示例,请参见Python subprocess readlines() hangs。这里是那里提供的pty
示例的变体(它应该在Linux上工作):
import os
import pty
import sys
from select import select
from subprocess import Popen, STDOUT
master_fd, slave_fd = pty.openpty()
process = Popen("./main", stdin=slave_fd, stdout=slave_fd, stderr=STDOUT,
bufsize=0, close_fds=True)
timeout = .1
with os.fdopen(master_fd, 'r+b', 0) as master:
input_fds = [master, sys.stdin]
while True:
fds = select(input_fds, [], [], timeout)[0]
if master in fds:
data = os.read(master_fd, 512)
if not data:
input_fds.remove(master)
else:
os.write(sys.stdout.fileno(), data)
if sys.stdin in fds:
data = os.read(sys.stdin.fileno(), 512)
if not data:
input_fds.remove(sys.stdin)
else:
master.write(data)
if not fds:
if process.poll() is not None:
assert not select([master], [], [], 0)[0]
os.close(slave_fd)
break
rc = process.wait()
print("subprocess exited with status %d" % rc)
或者使用pexpect
通过pty
pexpect
将pty
处理封装成更高级别的接口:
#!/usr/bin/env python
import pexpect
child = pexpect.spawn("/.main")
for line in child:
print line,
child.close()
Q: 为什么不直接使用管道(popen())?解释了伪终端的用处。
while True
。一旦for
循环结束,你将不再从process.stdout
中获取任何内容。 - jfs