YOLO813

交互式操控Linux - expect测试

    最近有一个需求,需要在两台云服务器上进行通信,定时拉取文件,对我来说,有两种方式,一种是写一个python脚本,这肯定是没问题的,但是需要服务器安装了python,并配置了paramiko库(可以让Python代码直接使用SSH协议对远程服务器进行操作);另外一种方法就是直接在服务器上使用scp命令,输入密码后拉取,相对较简单。前面说过了需要定时拉取,那么总是人工去做也麻烦,我就在想是否可以写一个Linux脚本完成这种交互式输入密码,最后找到了EXPECT。


    先简单介绍下EXCEPT,以下内容翻译至EXPECT manual。凡是标注(!)均为我不了解的内容。

    EXCEPT-与交互式程序进行编程对话。Expect是一个通过脚本可以与其它交互式程序“对话”的程序。在脚本中,Expect知道可以从(其它交互式)程序中期待什么(输出)和正确的响应应该是什么。解释语言提供分支和高级控制结构来引导对话。此外,用户可以根据需要控制并直接进行交互,然后将控制权返回给脚本(张小飞注:可能类似于selenium的那种显/隐式等待)。Expect也可以直接在C或C ++中使用(即不使用Tcl)。

    名称“Expect”来自uucp(!),kermit(!)和其他调制解调器控制程序流行的send/expect序列的概念,但是,与uucp(!)不同,Expect是通用的,因此可以在考虑任何程序和任务的情况下将其作为用户级命令运行。Expect实际上可以同时与多个程序对话。

    例如,以下是Expect可以做的一些事情:

  •     玩游戏,例如rogue(注:Roguelike是欧美国家对一类游戏的统称,是角色扮演游戏(RPG)的一个子类(Roguelike-RPG)),如果没有出现最佳配置,不断重启它直到出现(您想要的配置),然后将控制权交给您(注:RPG每一次新开局游戏都会随机生成游戏场景)。
  •     运行fsck(file system check),并根据预定标准回答问题,回答“是”,“否”或将控制权还给您,取决于预先设定的规则。

    ......

Shell无法执行这些任务的原因有多种(注:不懂)。(尝试,您就可以看到。)使用Expect,一切皆有可能。

通常,Expect对于运行任何需要程序与用户之间进行交互的程序很有用。但是前提是(这些程序)的交互式具有可编程的特征。如果需要,Expect还可以向用户提供后退控制(而不会停止受控制的程序)。同样,用户可以随时将控制权返回给脚本。

    用法:Expect读取cmdfile以获取要执行的命令列表。在支持#!的系统上也可能隐式调用Expect。通过将脚本标记为可执行文件并在脚本的第一行进行标记:

#!/usr/local/bin/expect -f

当然,路径必须是Expect可执行文件的路径。/ usr / local / bin只是一个示例(注:例如我使用yum安装的Except,在/usr/bin中)。

    其它具体的使用用法互联网上很多教程,就不一一赘述了,这里着重强调下关于expect用法的timeout,因为我需要使用scp拷贝一个远程服务器目录下面的100多个文件,但是发现每次拷贝都只复制了几十个文件就停了,因此特此备注。

    在手册中expect的介绍(大约在258行),如果模式匹配到关键字超时,则在超时时执行相应的主体;如果不使用timeout超时关键字,则在超时时执行隐式null操作。默认超时时间为10秒,但可以通过命令“ set timeout 30”将其设置为例如30,无限超时可以用值-1表示。如果模式是关键字default,则在超时或文件结束时执行相应的主体。

    也就是说,如果没有设置timeout参数,则expect脚本会默认在10秒内关闭,这也就是为什么拉取所有文件时并不完整。


    简介完了,进入到实操步骤:

    目标:获取IP地址为158.247.215.218的云服务器A下的/srv/test目录下的所有文件,并将其上传至云服务B的/opt/hello目录下。

    A目录如下

    B服务器下的空目录


    1.查看是否安装了expect

rpm -qf `which expect`

    2.未安装,则安装expect

yum install -y expect

    3.首先在B服务器上使用scp命令检测一下是否能够拉取A服务器的文件(例如,账号,密码,端口等是否正确),第一次远程连接可能会出现如下图所示的提醒,继续连接即可。

    如果选择了yes,可以看到/root/.ssh目录下面生成了一个known_hosts文件,只要这个文件存在,下一次再进行远程连接就不用再次确认了。有兴趣的也可以打开这个文件看下。

cd  ~/.ssh/
ls
>known_hosts

    4.在第三步没有问题的情况下,我们可以开始写expect脚本了,如下

# 先看下expect的执行目录在哪
whereis expect
expect: /usr/bin/expect
# 创建expect脚本
touch transferFile.expect
chmod o+x transferFile.expect
# 进入脚本
vim transferFile.expect
# 具体脚本内容如下

#!/usr/bin/expect
#author:zhangxiaofei
# transfer file from remote folder to this server
set timeout 10
# remote host
set host "158.247.215.218"
set username "root"
set password "your_secret"
set ports "22"
set localFolder "/opt/hello"
set remoteFolder "/srv/test"
spawn scp -r -P $ports $username@$host:$remoteFolder $localFolder
expect {
        "*yes/no*" {send "yes\n"; exp_continue}
        "*password" {send "$password\n"}
}
expect eof
exit

    4.运行脚本。可以看到文件已经拉取到B服务器下了,这个程序多次重复运行没有关系,会自动覆盖相同的目录。

expect transferFile.expect


    expect注解:开头#!/usr/bin/expect定义可执行文件的位置,set用于在expect脚本中定义变量,我分别定义了超时、远程服务器IP、用户名等变量,关键的语句还是scp那一行代码,只不过在expect脚本中将其放在了spawn 后面,spawn用于启动一个进程或者执行一个命令;然后按照人工流程此时应该要求是否确认fingerprint(如果是第一次连接的话)或者是输入密码,所以在expect中定义了如果CMD中的回复中存在yes/no,那么我认为其是要求确认fingerprint的,如果回复中带有password一词,我就让程序输入我上面定义好的密码。

    关于exp_continue,手册中介绍是“命令exp_continue允许expect继续执行,而不是像往常那样返回。默认情况下,exp_continue重置超时计时器。-continue_timer标志可防止重新启动计时器。。

    最后可以看到,remoteFolder目录被完整的拷贝到localFolder目录下(即成为了其子文件夹)。

    

    关于known_hosts文件,以下内容摘录之网络:“ssh会把每个你访问过计算机的公钥(public key)都记录在known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告, 避免你受到DNS Hijack之类的攻击。”


参考资料:
#SSH原理与运用(一):远程登录
https://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html