与子进程多次通信(多次调用 stdout)
我在[这个讨论串][1]上看到一个和我问题类似的提问。
我想给我的子进程发送一个命令,解析它的响应,然后再发送另一个命令。如果为了这个目的不得不启动一个新的子进程,那就太可惜了,尤其是如果第二个子进程需要执行和第一个子进程很多相同的任务(比如说,ssh连接,打开mysql)。
我试过以下代码:
subprocess1.stdin.write([my commands])
subprocess1.stdin.flush()
subprocess1.stout.read()
但是因为没有给read()
一个明确的字节参数,程序在执行这条指令时就卡住了。我也无法给read()
提供参数,因为我不知道流中有多少字节可用。
我在使用WinXP,Py2.7.1版本。
编辑
要感谢@regularfry,他给了我实现我真正意图的最佳解决方案(可以看看他回复中的评论,里面提到通过SSH隧道实现我的目标)。他的回答得到了很多赞。不过,为了方便之后来这里寻找标题问题答案的朋友,我接受了@Mike Penningtion的回答。
3 个回答
这个方法是可行的(我试过),但需要一些时间,并且它使用的是Unix特有的调用。你需要放弃subprocess模块,自己根据fork/exec和os.pipe()来实现类似的功能。
在用os.pipe()创建了子进程的输入输出文件描述符后,使用fcntl.fcntl函数将这些描述符设置为非阻塞模式(也就是O_NONBLOCK选项常量)。
接下来,使用select.select函数来检查或等待你的文件描述符是否可用。为了避免死锁,你需要使用select()来确保写入不会被阻塞,就像读取一样。即使如此,在读写时你也要考虑到OSError异常,并在遇到EAGAIN错误时重试。(即使在读写之前使用select,非阻塞模式下仍然可能出现EAGAIN;这是一个常见的内核错误,修复起来很困难。)
如果你愿意在Twisted框架上实现,他们据说已经为你解决了这个问题;你只需要写一个Process子类。不过我自己还没有尝试过。
你可以选择以下几种方式:
- 使用一种基于行的协议(这样就可以用readline()而不是read()),并确保发送的每一行都是有效的信息;
- 使用read(1)和一个解析器来判断你是否读取了一条完整的信息;或者
- 将消息对象进行序列化(也就是把它们转换成可以存储的格式)后从子进程发送,然后在父进程中反序列化(恢复成原来的格式)。这样就能解决消息长度的问题。
@JellicleCat,我在跟进之前的评论。我相信wexpect是sage的一部分……据我所知,它并没有单独打包,但你可以在这里下载wexpect。
老实说,如果你想通过程序来控制ssh会话,建议使用paramiko。它可以单独安装,打包得很好,而且在Windows上也能直接安装。
编辑
下面是一个paramiko的示例脚本,用来切换到一个目录,执行一个ls
命令并退出……同时捕获所有结果……
import sys
sys.stderr = open('/dev/null') # Silence silly warnings from paramiko
import paramiko as pm
sys.stderr = sys.__stderr__
import os
class AllowAllKeys(pm.MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key):
return
HOST = '127.0.0.1'
USER = ''
PASSWORD = ''
client = pm.SSHClient()
client.load_system_host_keys()
client.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
client.set_missing_host_key_policy(AllowAllKeys())
client.connect(HOST, username=USER, password=PASSWORD)
channel = client.invoke_shell()
stdin = channel.makefile('wb')
stdout = channel.makefile('rb')
stdin.write('''
cd tmp
ls
exit
''')
print stdout.read()
stdout.close()
stdin.close()
client.close()