pexpect与'链式'函数调用的问题
下面这个类是用来操作类似思科设备的接口,目的是执行命令和更新配置。
目前,我可以创建这个类的实例,调用 ssh_to_aos_expsh
函数,并得到有效的输出(比如,当命令是 'show running-config' 时可以获取配置)。但是,当我调用 ssh_to_aos_config
函数(这个函数又会调用 ssh_to_aos_expsh
)时,我遇到了一个 pexpect 超时错误。
我对比了 _ssh_connect
、ssh_to_aos_expsh
和 ssh_to_aos_config
中返回的 pexpect 对象(也就是 'child'),发现它们的内存地址是一样的,所以我不明白为什么我不能继续用 pexpect 操作这个对象。
我不是最厉害的 Python 编程者,所以可能在尝试在函数之间传递 pexpect 对象时犯了一些不小心的错误。如果是这样的话,希望有人能指出我的错误。
#!/usr/bin/env python
import os
import traceback
import pexpect
class SSHTool():
def __init__(self):
self.aos_user = 'some_user'
self.aos_passwd = 'some_passwd'
self.aos_init_prompt = 'accelerator>'
self.aos_enable_prompt = 'accelerator#'
self.aos_lnxsh_prompt = 'ACC#'
self.linux_passwd = 'linux_passwd'
self.root_prompt = ''
def _timeout_error(self, child):
print 'SSH could not login. Timeout error.'
print child.before, child.after
return None
def _password_error(self, child):
print 'SSH could not login. Password error.'
print child.before, child.after
return None
def _ssh_connect(self, user, address, passwd):
self.root_prompt = "root@%s's password: " % address
ssh_newkey = "Are you sure you want to continue connecting"
child = pexpect.spawn('ssh -l %s %s' % (user, address))
i = child.expect([pexpect.TIMEOUT, \
ssh_newkey, \
'Password: ', \
self.root_prompt])
if i == 0: # Timeout
return self._timeout_error(child)
elif i == 1: # SSH does not have the public key. Just accept it.
child.sendline ('yes')
i = child.expect([pexpect.TIMEOUT, \
'Password: ', \
self.root_prompt])
if i == 0: # Timeout
return self._timeout_error(child)
else:
child.sendline(passwd)
return child
elif i == 2 or i == 3:
child.sendline(passwd)
return child
else:
return self._password_error(child)
def ssh_to_aos_expsh(self, ip_address, command = ''):
child = self._ssh_connect(self.aos_user, \
ip_address, \
self.aos_passwd)
i = child.expect([pexpect.TIMEOUT, \
self.aos_init_prompt])
if i == 0:
return self._timeout_error(child)
child.sendline('enable')
i = child.expect([pexpect.TIMEOUT, \
self.aos_enable_prompt])
if i == 0:
return self._timeout_error(child)
if command:
child.sendline(command)
i = child.expect([pexpect.TIMEOUT, \
self.aos_enable_prompt])
if i == 0:
return self._timeout_error(child)
else:
return child.before
else:
return child
def ssh_to_aos_config(self, ip_address, command):
child = self.ssh_to_aos_expsh(ip_address)
i = child.expect([pexpect.TIMEOUT, \
self.aos_enable_prompt])
if i == 0:
return self._timeout_error(child)
child.sendline('config')
i = child.expect([pexpect.TIMEOUT, \
self.aos_config_prompt])
if i == 0:
return self._timeout_error(child)
child.sendline(command)
i = child.expect([pexpect.TIMEOUT, \
self.aos_config_prompt])
if i == 0:
return self._timeout_error(child)
else:
return child.before
3 个回答
我猜测超时的原因是因为 ssh_to_aos_config()
没有收到它所期待的所有输入:调用 ssh_to_aos_expsh()
可能是正常的,但后续调用 expect
就可能不行了。
所以一个问题是:超时到底发生在 哪里?你可以通过抛出一个异常,而不是返回 self._timeout_error(child) 来追踪这个问题。你找到的地方会指向一个 pexpect 从未收到的输入(因此导致超时),然后你可以在那儿更新你的代码。
如果你遇到超时问题,那是因为你没有收到你期待的字符串。可能你收到了错误信息,或者你期待的提示信息不对。
可以开启日志记录来查看整个交互过程。在 pexpect 2.3 中,可以通过给 child.logfile 属性赋值一个文件对象来实现,这样你就能清楚地看到发生了什么。对于早期版本的文档,可以查一下,因为我觉得这个方法可能有所不同。
我注意到你代码中的几个问题:
1) root_prompt 是一个空字符串。这会导致它总是立即匹配,即使客户端没有返回任何内容。这可能是你问题的原因——ssh 连接函数认为它看到了提示并成功登录,而客户端仍在等待其他输入。
2) 你的代码中有语法错误——在 ssh_connect 中,你有这样的代码:
if i == 0: # Timeout
return self._timeout_error(child)
else:
child.sendline(passwd)
return child
elif i == 2 or i == 3:
child.sendline(passwd)
return child
else:
return self._password_error(child)
elif 和 if 语句没有对应上,所以据我所知,这段代码是无法编译的。我猜这是复制粘贴时的错误,因为你说你一直在运行这段代码。
结果发现有两个问题,这两个问题一旦知道了原因就很容易解决。首先,__init__
方法里没有 self.aos_config_prompt
这个东西——当我把异常处理的代码注释掉时,pexpect的错误信息很清楚地指出了这一点。其次,给定一个看起来像 'accelerator(config)#' 的 self.aos_config_prompt
,pexpect会把它转化成正则表达式的匹配代码,这样它只会匹配包含括号内内容的提示符。只需在字符串中对括号进行转义,匹配就能正常工作了。