本作品采用
知识共享署名-非商业性使用-相同方式共享 3.0 Unported许可协议
进行许可。允许非商业转载,但应注明作者及出处。
作者:xialulee
最初发布于:2011年1月28日, http://blog.sina.com.cn/xialulee
一个朋友愁眉苦脸的陪我喝茶。他抱怨道:有个已经远走高飞的家伙
留下了一个exe,这个exe里包含了某种算法,我们经常用它来处理数据,可是程序的作者没有留下源码,也没有考虑过可扩展性,比如做成dll之类的。因此这个exe只能以交互的方式使用,每次人工往里输入数据,很辛苦。
我说:不能在Python中利用subprocess来调用这个exe,实现自动运转吗?
他说:没有看过程序的源码,但是我猜作者一定是printf之后没有fflush(stdout),因此使用subprocess打开进程之后,从进程的stdin输入一行数据,没法从进程的stdout取得输出。阻塞了。
我说:不能一次将所有要输入的数据全部输入,然后一次性将结果全部读出吗?使用subprocess的communicate可以做到,是比较推荐的方法。
他说:可惜的是办不到。因为这个程序里用到了某种迭代算法,每次输入数据后,程序进行一次迭代,然后返回迭代的结果,我们将返回的结果进行一些运算,再将数据输入给程序,进行下一次迭代。也就是说第n次的输入与第n-1次的输出有关,所以无法一次性将所有数据输入,然后一次性取出全部结果。
我说:可以使用pty吗?
他说:很遗憾,是Windows的程序。
我说:如果有源码就好了啊,在每一个printf后面加上fflush(stdout)就好了。
他说:如果有源码,什么都好办。
我说:让我们试验试验吧,说不定能够找到解决办法。
于是我拿出笔记本,写了一个简单的C程序(increase.c):
使用MinGW的gcc编译:
xialulee@xialulee-pc3 ~/lab
$ gcc increase.c -o increase.exe
就可以用了。
这个试验程序很简单,我们输入1,它就输出2,输入2,输出3,即将输入加1。现在我们试试在Python中使用subprocess和increase.exe进行双向通信:
[e:xialulee/lab]|7> import subprocess as sp
[e:xialulee/lab]|8> p = sp.Popen(['increase'], stdin=sp.PIPE, stdout=sp.PIPE)
[e:xialulee/lab]|9> p.stdin.write('1\n')
[e:xialulee/lab]|10> p.stdout.readline()
<10> '2\r\n'
[e:xialulee/lab]|11> p.stdin.write('2\n')
[e:xialulee/lab]|12> p.stdout.readline()
<12> '3\r\n'
很正常。
现在修改一下increase.c,将fflush(stdout)注释掉,即
重新用gcc编译一遍。再用subprocess试试:
[e:xialulee/lab]|14> p = sp.Popen(['increase'], stdin=sp.PIPE, stdout=sp.PIPE)
[e:xialulee/lab]|15> p.stdin.write('1\n')
[e:xialulee/lab]|16> p.stdout.readline()
p.stdout.readline()没有返回。
他说:是吧。
我说:想起来了。PP3E上说过这个问题,说一个程序如果不是以交互的方式运行的话,那么stdout这些都是完全缓冲的,即使设置bufsize也没用,可行的办法是使用pty或者在源码中使用fflush强制flush stdout。
他说:可是无论是使用pty还是修改源码对于我来说都是无法实现的。
我说:我突然想到了一个邪恶的主意。我们可以把这个Windows的程序放到Linux中,使用Wine来运行,这样不就可以了吗?
由于现在身边只有这个笔记本,于是打开虚拟机,运行Ubuntu。将increase.exe拷贝到共享目录,由于Wine已经装好了,所以应该直接就可以运行:
xialulee@xialulee-pc3:/mnt/rootmachine$ ./increase.exe
fixme:msvcrt:_setmbcp trail bytes data not available for DBCS codepage 0 - assuming all bytes
1
2
100
101
-1
虽然输出了一行诡异的错误信息,但程序的运行是正常的。
他说:你的这个exe可以在Ubuntu下用Wine运行。我的那个也应该可以,因为只是实现了算法,并没有使用什么特殊的系统功能。那么现在你可以展示一下如何使用Python的pty来搞定它了。
我说:比较抱歉的是……我不会在Python中用pty……
他说:……
我说:等等,好像gawk也可用pty,而且很简单。
打开
GAWK: Effective AWK Programming
,找到如下的一句话:
Beginning with gawk 3.1.2, you may use Pseudo-ttys (ptys) for two-way communication instead of pipes, if your system supports them.
而使用方法则极为简单,只需:
command = "sort -nr"
PROCINFO[command, "pty"] = 1
就可以了。
我说:试试用gawk吧,如果gawk能够实现双向通信,Python也一定可以的。
于是写了如下的一段代码(gawkpty),这段gawk代码会启动increase.exe,并将1和100两个数字传递给它,如果运行无误的话,我们会得到2和101的输出:
运行:
xialulee@xialulee-pc3:/mnt/rootmachine$ vim gawkpty
xialulee@xialulee-pc3:/mnt/rootmachine$ ./gawkpty
2
101
果然正常运行了。
他说:
不对!有个大问题!
作者:xialulee
最初发布于:2011年1月28日, http://blog.sina.com.cn/xialulee
#include <stdio.h>
int main(void){
int i;
while (1){
scanf("%d", &i);
if (i == -1)
break;
printf("%d\n", i+1);
fflush(stdout);
}
}
使用MinGW的gcc编译:
xialulee@xialulee-pc3 ~/lab
$ gcc increase.c -o increase.exe
就可以用了。
[e:xialulee/lab]|7> import subprocess as sp
[e:xialulee/lab]|8> p = sp.Popen(['increase'], stdin=sp.PIPE, stdout=sp.PIPE)
[e:xialulee/lab]|9> p.stdin.write('1\n')
[e:xialulee/lab]|10> p.stdout.readline()
[e:xialulee/lab]|11> p.stdin.write('2\n')
[e:xialulee/lab]|12> p.stdout.readline()
很正常。
#include <stdio.h>
int main(void){
int i;
while (1){
scanf("%d", &i);
if (i == -1)
break;
printf("%d\n", i+1);
//fflush(stdout);
}
}
重新用gcc编译一遍。再用subprocess试试:
[e:xialulee/lab]|14> p = sp.Popen(['increase'], stdin=sp.PIPE, stdout=sp.PIPE)
[e:xialulee/lab]|15> p.stdin.write('1\n')
[e:xialulee/lab]|16> p.stdout.readline()
p.stdout.readline()没有返回。
xialulee@xialulee-pc3:/mnt/rootmachine$ ./increase.exe
fixme:msvcrt:_setmbcp trail bytes data not available for DBCS codepage 0 - assuming all bytes
1
2
100
101
-1
虽然输出了一行诡异的错误信息,但程序的运行是正常的。
Beginning with gawk 3.1.2, you may use Pseudo-ttys (ptys) for two-way communication instead of pipes, if your system supports them.
而使用方法则极为简单,只需:
command = "sort -nr"
PROCINFO[command, "pty"] = 1
就可以了。
#!/usr/bin/gawk -f
# 2011.01.28 PM 04:28
# xialulee
BEGIN{
INC = "./increase.exe 2>/dev/null"
print 1 |& INC
INC |& getline
print
print 100 |& INC
INC |& getline
print
print -1 |& INC
close(INC)
}
运行:
xialulee@xialulee-pc3:/mnt/rootmachine$ vim gawkpty
xialulee@xialulee-pc3:/mnt/rootmachine$ ./gawkpty
2
101
果然正常运行了。