Pexpect 笔记

By | 2021年12月31日

Pexpect 安装

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

Pexpect 安装

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

安装命令:

pip install pexpect

参数说明

maxread – 缓存设置

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

测试发现,pexpect.spawn 方法的参数 maxread 最大到4092(4K),在这个范围内的参数是有效的,因为 cli.buffer 值确实根据这个参数变化了。
但超过这个数字参数就不起作用了,cli.buffer 里始终是最新的 4092 个 byte 的数据。maxread 默认值应该是2000。
这个4092限制,似乎与 Linux 的 N_TTY_BUF_SIZE(文章里搜索 N_TTY_BUF_SIZE)的限制有关

searchwindowsize – 模式匹配阀值

默认值: None

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

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

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

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

dimensions – 指定终端列出、行数

执行 pexpect.spawn 方法时可以带 dimensions 参数,此参数解释如下:

The dimensions attribute specifies the size of the pseudo-terminal as seen by the subprocess, and is specified as a two-entry tuple (rows, columns). If this is unspecified, the defaults in ptyprocess will apply.

方法说明

setwinsize

This sets the terminal window size of the child tty. This will cause a SIGWINCH signal to be sent to the child. This does not change the physical window size. It changes the size reported to TTY-aware applications like vi or curses – applications that respond to the SIGWINCH signal.

客户设备那采集设备遇到过一个错误:

调小到 setwinsize(100,510) 后解决。

连续发送 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 的日志打印全部会话日志包括控制字符用于分析,设置方式如图:

pexpect 不直接执行 ssh, 而是执行 sh ssh,以防 EOF 报错

expect_exact 报 EOF 错较少见,这种错是因为设备主动与 pexpect 上的 ssh 客户端断开连接导致的,以至于拿不到输出流中数据:

这个错误是进入华为虚设备后调用 show config 拿虚设备配置时报的,同时伴随有以下提示:The max number of VTY users is 5, and the number of current VTY users on line is 0.Connection closed by foreign host.。手工通过 SSH 登录设备去切换到虚设备不会有这个提示出来。

解决办法:pexpect 不直接执行 ssh, 而是执行 sh ssh。这样 pexpect 就直接与 linux shell 的输出流打交道了,不会被远程设备断掉了。

核心代码 open_shell_ssh:

# 这是关键,cmdline 不再直接用 'ssh xxx' 了
cmdline = '/bin/bash'
cli = pexpect.spawn(cmdline, maxread=5000)
cli.sendline('stty raw')
time.sleep(1)
cmdline = 'ssh {0}@{1} -p {2}'.format(user, host, port)
cli.sendline(cmdline)
cli.expect_exact(prompts, searchwindowsize=100)

FAQ

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

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

参考

Pexpect 模块使用说明

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注