【问题标题】:Communicating multiple times w/ subprocess (multiple calls to stdout)与子进程进行多次通信(多次调用标准输出)
【发布时间】:2011-05-02 19:22:04
【问题描述】:

在 [this thread][1] 上有一个类似的问题。

我想向我的子进程发送一个命令,解释响应,然后发送另一个命令。不得不启动一个新的子进程来完成这件事似乎很可惜,特别是如果 subprocess2 必须执行许多与 subprocess1 相同的任务(例如 ssh、打开 mysql)。

我尝试了以下方法:

subprocess1.stdin.write([my commands])
subprocess1.stdin.flush()
subprocess1.stout.read()

但是如果没有明确的字节参数到read(),程序会在执行该指令时卡住,我无法为read() 提供参数,因为我无法猜测流中有多少字节可用。

我正在运行 WinXP,Py2.7.1

编辑

感谢@regularfry 为我的真实意图提供了最佳解决方案(请阅读他的回复中的 cmets,因为它们与通过 SSH 隧道实现我的目标有关)。 (他/她的答案已被投赞成票。)但是,为了任何以后来回答标题问题的观众的利益,我接受了@Mike Penningtion 的答案。

【问题讨论】:

  • 你考虑过使用Pexpect吗?
  • 我不熟悉它,但我现在会研究它。 (如果您想留下建议作为问题的答案,它可能正是我正在寻找的答案。)
  • 对我来说似乎是不行的。我在 Windows 上。
  • 我想我需要wexpect 而不是pexpect。但我找不到难以捉摸的wexpect 供下载。任何提示在哪里看?

标签: python stream subprocess pipe


【解决方案1】:

您的选择是:

  1. 使用面向行的协议(并使用 readline() 而不是 read()),并确保发送的每个可能行都是有效消息;
  2. 使用 read(1) 和解析器来告诉您何时阅读了完整的消息;或
  3. 将消息对象从子进程中提取到流中,然后在父进程中取消提取它们。这将为您处理消息长度问题。

【讨论】:

  • 我期待 MySQL 通过 ssh 响应,所以我看不出前两个选择如何能拯救我(响应往往会运行几行,并且没有我知道的分隔符)。你能从流中提供一些关于酸洗的见解吗?我想我需要在 ssh 的远程端编写一个 python 脚本来执行酸洗,而我的本地脚本会去酸洗?
  • 哦,我明白了。我曾假设这两个进程都是 Python。如果你想看看泡菜的作用,请看这里:docs.python.org/library/pickle.html#example
  • 另外,您是否正在尝试做 python 数据库适配器无法做的事情?直接调用命令行客户端似乎有点不寻常。
  • 感谢您的提问。我的应用程序的目的是在工作中访问 MySQL 数据库,进行一些查询,将结果写入本地文件。我不能只让 Python 进行远程 MySQL 连接,因为服务器限制 MySQL 访问服务器 IP。 IT 告诉我,我可以访问它的唯一方法是通过 SSH 隧道进入。
  • 查看 SSH 端口转发。您可以使用单个 ssh 命令为 MySQL 连接设置加密代理,这意味着您可以避免解析 mysql 命令行输出。我认为这是做你正在尝试的事情的正确方法,因为你可以使用现有的 python MySQL 绑定。这里有关于腻子的说明(如果你正在使用的话):cs.uu.nl/technical/services/ssh/putty/puttyfw.html
【解决方案2】:

@JellicleCat,我正在跟进 cmets。我相信wexpect是sage的一部分... AFAIK,它没有单独打包,但是你可以下载wexpect here

老实说,如果您要推动程序化 ssh 会话,请使用 paramiko。它支持独立安装,具有良好的包装,应该在 Windows 上原生安装。

编辑

将示例 paramiko 脚本 cd 到目录,执行 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()

【讨论】:

  • 请求进一步说明:我已经安装了 paramiko,但我还没有成功连接。我创建了一个 SSHClient 对象,然后调用 connect,提供了我对于普通 ssh 命令的登录信息,但我得到以下信息:gaierror: [Errno 11001] getaddrinfo failed。我想知道是否有必要通过公钥获取访问权限,但是我的工作服务器没有设置.ssh 目录,所以我不知道有什么地方可以放置我的公钥。
  • @JellicleCat,你试过用脚本登录localhost吗?如果没有看到一些代码,很难说错误可能是什么
【解决方案3】:

这种方法可行(我已经这样做了),但需要一些时间,并且它使用 Unix 特定的调用。您将不得不放弃 subprocess 模块并基于 fork/exec 和 os.pipe() 滚动您自己的等效模块。

在使用 os.pipe() 创建子进程后,使用 fcntl.fcntl 函数将子进程的标准输入/标准输出文件描述符(读取和写入)置于非阻塞模式(O_NONBLOCK 选项常量)。

使用 select.select 函数轮询或等待文件描述符的可用性。为了避免死锁,您需要使用 select() 来确保写入不会阻塞,就像读取一样。即便如此,您必须在读写时考虑 OSError 异常,并在遇到 EAGAIN 错误时重试。 (即使在读/写之前使用 select,EAGAIN 也可能出现在非阻塞模式下;这是一个常见的内核错误,已被证明难以修复。)

如果你愿意在 Twisted 框架上实现,他们应该已经为你解决了这个问题;您所要做的就是编写一个 Process 子类。但我自己还没有尝试过。

【讨论】:

    猜你喜欢
    • 2011-03-28
    • 2017-02-15
    • 1970-01-01
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-26
    • 1970-01-01
    相关资源
    最近更新 更多