pexpect与'链式'函数调用的问题

0 投票
3 回答
2780 浏览
提问于 2025-04-15 15:58

下面这个类是用来操作类似思科设备的接口,目的是执行命令和更新配置。

目前,我可以创建这个类的实例,调用 ssh_to_aos_expsh 函数,并得到有效的输出(比如,当命令是 'show running-config' 时可以获取配置)。但是,当我调用 ssh_to_aos_config 函数(这个函数又会调用 ssh_to_aos_expsh)时,我遇到了一个 pexpect 超时错误。

我对比了 _ssh_connectssh_to_aos_expshssh_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 个回答

0

我猜测超时的原因是因为 ssh_to_aos_config() 没有收到它所期待的所有输入:调用 ssh_to_aos_expsh() 可能是正常的,但后续调用 expect 就可能不行了。

所以一个问题是:超时到底发生在 哪里?你可以通过抛出一个异常,而不是返回 self._timeout_error(child) 来追踪这个问题。你找到的地方会指向一个 pexpect 从未收到的输入(因此导致超时),然后你可以在那儿更新你的代码。

0

如果你遇到超时问题,那是因为你没有收到你期待的字符串。可能你收到了错误信息,或者你期待的提示信息不对。

可以开启日志记录来查看整个交互过程。在 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 语句没有对应上,所以据我所知,这段代码是无法编译的。我猜这是复制粘贴时的错误,因为你说你一直在运行这段代码。

1

结果发现有两个问题,这两个问题一旦知道了原因就很容易解决。首先,__init__ 方法里没有 self.aos_config_prompt 这个东西——当我把异常处理的代码注释掉时,pexpect的错误信息很清楚地指出了这一点。其次,给定一个看起来像 'accelerator(config)#' 的 self.aos_config_prompt,pexpect会把它转化成正则表达式的匹配代码,这样它只会匹配包含括号内内容的提示符。只需在字符串中对括号进行转义,匹配就能正常工作了。

撰写回答