from subprocess import Popen, PIPE
p = Popen('less', stdin=PIPE, stdout=PIPE)
p.communicate('Line number %d.\n' % x)
communicate函数返回一个二元组(stdoutdata, stderrdata),包含了子进程的标准输出和标出错误的输出数据。然而,由于Popen对象的communicate函数会阻塞父进程,同时还会关闭管道,因此每个Popen对象只能调用一次communicate函数,如果有多个请求必须重新生成Popen对象(重新初始化子进程),不能满足我们的需求。
因此,我们只有往Popen对象的stdin和stdout对象里写入和读取数据才能实现我们的需求。然而,不幸的是subprocess模块默认情况下只运行在子进程结束的时候读取一次标准输出。Both subprocess and os.popen* only allow input and output one time, and the output to be read only when the process terminates.
进过一番研究之后我发现通过fcntl模块的fcntl函数可以把子进程的标准输出改为非阻塞的方式,从而达到我们的目的。这样困扰我许久的问题终于得到了完美解决。代码如下:
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # author:
4 from subprocess import Popen, PIPE
5 import select
6 import fcntl, os
7 import time
8
9 class Server(object):
10 def __init__(self, args, server_env = None):
11 if server_env:
12 self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=server_env)
13 else:
14 self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
15 flags = fcntl.fcntl(self.process.stdout, fcntl.F_GETFL)
16 fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
17
18 def send(self, data, tail = '\n'):
19 self.process.stdin.write(data + tail)
20 self.process.stdin.flush()
21
22 def recv(self, t=.1, stderr=0):
23 r = ''
24 pr = self.process.stdout
25 if stderr:
26 pr = self.process.stdout
27 while True:
28 if not select.select([pr], [], [], 0)[0]:
29 time.sleep(t)
30 continue
31 r = pr.read()
32 return r.rstrip()
33 return r.rstrip()
34
35 if __name__ == "__main__":
36 ServerArgs = ['/path/to/server', '/path/to/args']
37 server = Server(ServerArgs)
38 test_data = '拿铁', '咖啡'
39 for x in test_data:
40 server.send(x)
41 print x, server.recv()
另外,调用一些外部程序时,可能需要指定相应的环境变量,方式如下:
my_env = os.environ
my_env["LD_LIBRARY_PATH"] = "/path/to/lib"
server = server.Server(cmd, my_env)