Pexpect 笔记

By | 2021年12月31日

1 Pexpect 安装

1.1 Pip安装

pip 官网:https://pypi.org/project/pip
pip官方安装教程:https://pip.pypa.io/en/stable/installing

我的Centos7上自带 Python 2.7.5,那就在它下安装吧。安装命令:

// 1. 下载安装脚本 get-pip.py
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
// 2. 执行安装脚本
python get-pip.py

1.2 Pexpect 安装

pexpect 官网:https://pexpect.readthedocs.io/en/stable
pexpect 官方安装教程:https://pexpect.readthedocs.io/en/stable/install.html

安装命令:

pip install pexpect

参数说明

2.1 maxread – 缓存设置

默认值: 2000 (单位:字符)
指定一次性试着从命令输出中读多少数据。如果设置的数字比较大,那么从 TTY 中读取数据的次数就会少一些。
设置为 1 表示关闭读缓存。
设置更大的数值会提高读取大量数据的性能,但会浪费更多的内存。这个值的设置与 searchwindowsize 合作会提供更多功能。缓存的大小并不会影响获取的内容,也就是说如果一个命令输出超过2000个字符以后,先前缓存的字符不会丢失掉,而是放到其他地方去,当你用 self.before (这里 self 代表 spawn 的实例)还是可以取到完整的输出的。
spawn 方法最后一个参数就是 maxread。

2.2 searchwindowsize – 模式匹配阀值

默认值: None

searchwindowsize 参数是与 maxread 参数一起合作使用的,它的功能比较微妙,但可以显著减少缓存中有很多字符时的匹配时间。在 expect_exact 方法中,最后一个参数就是 searchwindowsize。

默认情况下, expect() 匹配指定的关键字都是这样:每次缓存中取得一个字符时就会对整个缓存中的所有内容匹配一次正则表达式,你可以想像如果程序的返回特别多的时候,性能会多么的低。

设置 searchwindowsize 的值表示一次性收到多少个字符之后才匹配一次表达式,比如现在有一条命令会出现大量的输出,但匹配关键字是标准的 FTP 提示符 ftp> ,显然要匹配的字符只有 5 个(包括空格),但是默认情况下每当 expect 获得一个新字符就从头匹配一次这几个字符,如果缓存中已经有了 1W 个字符,一次一次的从里面匹配是非常消耗资源的,这个时候就可以设置 searchwindowsize=10, 这样 expect 就只会从最新的(最后获取的) 10 个字符中匹配关键字了,如果设置的值比较合适的话会显著提升性能。不用担心缓存中的字符是否会被丢弃,不管有多少输出,只要不超时就总会得到所有字符的,这个参数的设置仅仅影响匹配的行为。

也就是说,设置 searchwindowsize 后,就从后面匹配了。前面已有的不会再去匹配。

3 连续发送 send/sendline 命令并在最后执行 expect_exact

工作中遇到一台帕拉迪堡垒机发送指令后返回的都是排版过的控制字符非常不爽,每次 send 命令后都尝试expect_exact 匹配最后的特殊控制符,虽然试成功了,但不是好办法,这些字符下次或设备作小调整后很容易出现变化。

最好的办法是一次性发送多个 send() 命令,再执行 expect_exact(),然后获取 before+after 值。当然不执行 expect_exact() 不影响命令的发送,但一般发送好了总归要验证下最后的结果吧(before+after ),因此必须先调用 expect_exact(),否则 before+after 变量里是不会有值的。

# 登录堡垒机后,先后发送"寻找IP"、"回车进入这台IP设备"、"选择SSH:22 root"登录设备三条命令,
# 最后再执行 expect_exact 匹配字符 '#', '$'(#-是管理员登录的提示符、$-是非管理员登录提示符)

# 发送 send 命令
time.sleep(1)
cmd.send('/i:10.10.101.102')

# 发送 send 命令
time.sleep(1)
cmd.send('\r')

# 发送 send 命令
time.sleep(1)
cmd.send('1\r')

# 前面的那几步 send() 中无法去用正则验证 before、after 中是否有  SSH:22 等,必须要执行完 expect_exact() 后 before、after 里才会有值,才能验证。
Common.expect_exact(cmd, ['#', '$'], timeout=15)
print '>>> 成功进入设备。'

# 由于已经执行 expect_exact,因此 before、after 里存储了上面所有 send() 后终端返回的数据了
tmpfile = open('/root/dev/python/OTP/pri.txt', "w")
tmpfile.write(cmd.before + cmd.after)
tmpfile.close()

如果遇到一些特殊设备,在 send 发送命令前,可能要借助 putty 的日志打印全部会话日志包括控制字符用于分析,设置方式如图:

4 FAQ

4.1 ssh报错:Unable to negotiate with x.x.x.x port xx: no matching cipher found. Their offer: …

解决方法是 sudo vi /etc/ssh/ssh_config,取消下面Cipher的两行注解,如下

发表评论

您的电子邮箱地址不会被公开。