一、基本介绍
安装
pip3 install pexpect
在Pexpect中有两种重要的方法– expect()
和 send()
(或 sendline()
类似于send() 方法+ \n
,就是可以换行)。
该 expect()
方法等待子应用程序返回给定的字符串。
您指定的字符串是一个正则表达式,因此您可以匹配复杂的模式。
该 send()
方法将字符串写入子应用程序。从子应用程序的角度来看,就像有人在终端上键入文本一样。
每次调用 expect()
后, 子应用程序的 before
和 after
属性将被设置为可以被打印的文本。该 before
属性将包含子应用程序返回的所有文本,直到匹配上期望的字符串模式为止。该 after
字符串将包含与预期模式匹配的文本。match
属性设置为 re match对象。
执行一个创建密钥对的命令
[test@VM-0-11-centos ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
使用模块 pexpect
尝试捕获到响应的字符串
In [1]: import pexpect
In [2]: child = pexpect.spawn("ssh-keygen")
In [3]: child.expect("/root/.ssh/id")
Out[3]: 0
In [4]: child.before
Out[4]: b'Generating public/private rsa key pair.\r\nEnter file in which to save the key ('
In [5]: child.after
Out[5]: b'/root/.ssh/id'
实例演示
先删除或者移动已经有的 密钥对
下面的指令是先测试是否存在 .ssh
目路,如果存在则改名,防止已经建立的信任关系失效。
test -d ~/.ssh && mv ~/.ssh ~/myssh
In [1]: import pexpect
In [2]: cmd = "ssh-keygen"
In [3]: child = pexpect.spawn(cmd) // 执行 shell 命令,生成子程序
// 捕获子程序输出的字符串, 因为 ) 会被 python 识别解释,所以这里使用了 [)]
In [4]: child.expect(".ssh/id_rsa[)]")
Out[4]: 0
// 根据子程序的需要,发送进一步的指令
// 这里是直接输入一个回车键,相当于按下了回车键
In [5]: child.sendline()
Out[5]: 1
// 继续捕获
In [6]: child.expect("no passphrase.:")
Out[6]: 0
// 这里是打印出了匹配模式之前的输出内容,不是必须的操作
// 只是进行简单的验证子程序输出的内容而已
In [7]: child.before
Out[7]: b": \r\nCreated directory '/home/test/.ssh'.\r\nEnter passphrase (empty for "
// 这里是输出的字符串中,匹配到的那部分字符串
In [8]: child.after
Out[9]: b'no passphrase):'
In [9]: child.sendline()
Out[9]: 1
In [10]: child.expect("again:")
Out[10]: 0
In [11]: print(child.before.decode()+child.after.decode())
Enter same passphrase again:
In [12]: child.sendline()
Out[12]: 1
In [13]: child.close()
In [14]: !ls .ssh
id_rsa id_rsa.pub
In [15]:
额外的小方法
对于创建 ssh 的密钥对,其实可以使用如下的 shell 命令非交互式的创建自己的密钥对, 上面的例子只是为了演示 pexpect 而已。
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N ""
- -t 指定密钥加密类型
- -f 指定私钥文件的输出路径
- -N 新的密钥的加密密码,
""
标识空(或者说是不对私钥进行加密)
二、应对一条命令多种输出内容的情况
在某些情况下,我们执行的命令,有可能会出现多种情况的返回结果。
比如,在要求输入密码的时候,可能会输入正确的密码,返回的是正常的进一步的命令提示符,也有可能输入了错误的密码,此时返回的是要求再次输入密码的提示内容。
比如刚才上面创建密钥对的命令,假如密钥对已经存在,中间会多提示一条是否要进行覆盖现有的密钥对,并且期望输入的是 y
或者 n
。
测试情况如下
[test@VM-0-11-centos ~]$ ls .ssh/
id_rsa id_rsa.pub
[test@VM-0-11-centos ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/test/.ssh/id_rsa):
/home/test/.ssh/id_rsa already exists.
Overwrite (y/n)? n
[test@VM-0-11-centos ~]$
当输入 n
并回车之后, 表明你不覆盖现有的密钥对,程序也就不会继续创建新的密钥对了。
假如输入 y
并回车,程序会继续创建新的密钥对,并覆盖原来的。
[test@VM-0-11-centos ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/test/.ssh/id_rsa):
/home/test/.ssh/id_rsa already exists.
Overwrite (y/n)? y
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_rsa.
Your public key has been saved in /home/test/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:OMu/CTZd4kwnC+uD1/2S8Nqry6iUG/112Z1Vu452EhI test@VM-0-11-centos
The key's randomart image is:
+---[RSA 2048]----+
| |
| |
| .|
| . E o|
| + S o . ..|
| + X.* . + .+|
| +.O.=+..+ oo.|
| ..=oB.+=. oo. |
| ooo.X=o+o.o. |
+----[SHA256]-----+
[test@VM-0-11-centos ~]$
应对上面的情况,pexpect 模块的 expect
方法可以接受一个列表类型的参数。这个列表中包含了多个正则表达式,每个正则表达式代表了可能会出现的字符串结果。比如:
i = child.expect["Overwrite", "no passphrase"]
i
的值是捕获列表的索引号, 用于判断实际捕获到了列表中的哪个字符串,比如:
匹配到 "Overwrite"
,i
的值就是 0,
匹配到 "no passphrase"
, i
的值就是 1, 依次类推。
所以代码优化后应该像下面的样子:
In [15]: import pexpect
In [16]: cmd = "ssh-keygen"
In [17]: child = pexpect.spawn(cmd)
In [18]: child.expect(".ssh/id_rsa[)]:")
Out[18]: 0
In [20]: child.sendline()
Out[7]: 1
In [8]: i = child.expect(["Overwrite", "no passphrase"])
In [10]: if i == 0:
...: child.sendline("y")
...: child.expect("empty for no passphrase")
...: child.sendline()
...: elif i == 1:
...: child.expect("empty for no passphrase")
...: child.sendline()
...:
In [11]: child.expect("again:")
Out[11]: 0
In [12]: child.sendline()
Out[12]: 1
In [13]: child.close()
In [14]: !ls ~/.ssh
id_rsa id_rsa.pub
三、小练习
练习写一个 MySQL初始化后,修改默认密码的脚本
脚本要实现的功能:
- 过滤出默认密码
- 修改默认密码