从expect到pexpect的演变

     在日常的系统管理中,经常会遇到要与系统进行交互的过程。如果是少部分机器还行,但是如果是很多机器呢?所以不得不开发自己的脚本,使其能够代替人工自动的在一批机器上执行相同的
的操作。有人可能会说,现在有很多的自动化工具,像fabric,ansible,saltstack等,但是这些工具的前提都是要在打通底层ssh密钥通信的情况下,而且对于一些重量级的自动化工具还需要花点时间去部署和维护。如果此时你不想轻易的动线上的服务器,那么expect这种可以通过脚本进行交互的工具就越发重要。
     这里说明一点,其实任何知识的知识面都很大,如果你想都学深,挖其根,个人感觉是不可能的。不管是什么工作,工作中最重要的是第一时间使用所学知识解决问题,是知识围着工作转,
不是工作围着知识转。深度与广度是你空闲时间的积累,而工作简单明了,就是你所学的知识要能解决工作中的问题,就这么简单。所以工作不是让你去学习的,而是让你去干事的。。时间怎么安排自己看着办,学到什么程度自己去把握。我也不可能给你讲的很深,因为我的水平也很有限。哈哈,你是不是想说,最后一句话才是重点啊。。
     下面将通过expect->python->pexpect来演示脚本交互的功能,喜欢用哪种你就用哪种,不过如果你是运维人员,还是建议掌握下python哈。。。
 
一:expect
1.基本使用
 对于expect的使用,这里不介绍什么原理不原理的,关于理论介绍请百度。这里通过一个小可见大的例子,介绍expect基本组成和使用
案例一:
#!/usr/bin/expect    
set ipaddr "10.0.10.14"  
set passwd "123456"
set timeout -1
spawn ssh
root@$ipaddr
expect {
        "yes/no" {
                send "yes\r"
                expect "password:"
                send "$passwd\r"
                }
        "password:" { send "$passwd\r" }
}
expect "]#"
send "touch /home/a.txt\r"
send "echo 'hello world' > /home/a.txt\r"
#interact
send "exit\r"
expect eof
exit

分析:
        
#!/usr/bin/expect
//这是expect脚本开头的第一句话,有点类似于shell脚本的开头,也就是表示将使用什么程序来解释脚本,如果你不加这语句,则你必须使用expect -f expect_file的形式来执行

set:该指令在expect脚本中,是用来设置变量的,格式为:set variable_name variable_value  你可以设置任何你想要的变量,这里要说明一下的是timeout这个变量,这个变量应该说是
 expect中内置的变量,表示的是超时时间,expect在执行时会期望出现相应的字符串信息,如果没有出现,则会一直等待,当等待超时时就会退出。在复制文件时要特别的注意,因此在需要
 长时间执行某个命令时,会设置timeout为-1,表示永不超时
spawn:该指令是expect中的一个特色,用来在本地发起某个shell命令,通常都是远程登录对方服务器的指令。一旦发起本地指令后,接下来所有的脚本内容都是跟远端进行交互的操作。由重要的两块内容expect 和send组成,直到退出。

expect 和send:这两个块是expect脚本的核心,因为这是实现交互过程的重要环节。expect通常是用来捕获对方服务器最后显示的相关字符串,这里可以是一个正则表达式,一旦匹配了内容,
将立即执行紧跟的send指令,发送相关的信息到对方服务器,这样就完成了一次交互,多次的交互只是这两个块组合的累加。但是要注意一旦expect要捕获的信息没有发生,则会一直等待,直到超时。expect通常是一个或者是一个条件判断,就像我上面的第一个expect,当连接一个服务器的时候,第一次会出现yes/no的信息,但当下次连接时会直接要求输入密码,所以这里需要一个判断。send可以一连串的指令,通常没有限制。

interact:这个是一个交互的指令,当使用了这个指令后,在执行上面的expect,send指令之后,会停留在远端服务器的终端上,这个时候就可以手动操作了。使用这个命令,下面的退出操作就不需要了。

expect eof: 这是退出expect执行环境的固定格式,当上面的内容执行结束后,会返回一个eof的信息,expect捕获到这个信息,则退出。

上面这个脚本看起来,好像简单,但是已经包含了expect的精华,使用类似这样的脚本已经可以完成一系列的工作内容。对于复杂的场合,适当的配置for,if...else while语句以及参数的获取
即可。


2.expect语句的使用
 为了解决大部分的问题,还是要研究一下expect的语句的使用,使得能够执行复杂的任务。。
 
for语句:
expect的for语句有两种写法:
第一种:
foreach i {
 ip1
 ip2 } {
......
}

这种形式通常用在批量执行服务器操作的时候。。。

第二种:
for {设置初始值}{判断}{增量}{
.....
}
example:
for {set i 0}{$i<=10}{incr 1}{
....
}
表示设置i的初始值为0,当i的值小于等于10时就执行括号中的内容,每执行一次就i自增1。。

while语句:
 while语句的格式比较简单
while {条件判断} {
....}

example:
while {$i < 10}{
command
incr i
}

if...else...语句:
if {条件判断} {
....
}else {
....
}

三个语句其实都不是很难,难的是你expect和send语句块的组合。其次要注意的是在expect中{}的使用

3.expect脚本读取文件中的内容
 写脚本的同志可能经常遇到脚本和数据需要分离的情况,此时怎么让脚本读取数据文本中的内容来使用就是一个需要解决的问题。因此有必要了解一下expect怎么来读取文件的内容。
案例:
#!/usr/bin/expect
set number 0
set fd [open "/home/workspace/portfile.txt" r]
while { [gets $fd line] >= 0} {
incr number
}
puts "Number of line:$number"
close $fd

以上就是expect获取文件的一个过程。open用来打开一个文集,并使用set将此文件赋值给fd。最后还要记得关闭文件close 。这一点跟python很像

这里重要的是gets这个指令,这个指令获得$fd文本的每一行内容,并将其赋值给line,从这里可以看出expect如何操作文本的内容
puts:这个指令是输出指令,输出信息到中断上,这个就不多说了。。


4.expect中的参数使用
 expect默认情况下参数是被存储在一个argv的数组中,序号从0开始。但是取得数组中的每个数值却比较特殊
 
[lindex $argv 0] :这表示取得数组argv中的第一个参数,这些特别的是使用lindex来取得其值,通常会跟变量赋值一起使用
example:
set ip [lindex $argv 0]


关于expect就聊到这里,相信掌握了上面的内容,工作中已经不存在问题了。。。

二:python中嵌套expect实现expect的功能
 对于熟悉python的同胞来说,可能更倾向于使用python来完成任务。当然现在使用python有专门的模块pexpect,但是这里还是了解一下使用python嵌套expect来实现expect的过程。
看一个案例:
#!/usr/bin/env python
#encoding: utf-8
import os

class remoteCopy(object):
    def __init__(self,host,user,password):
        self.host = host
        self.user = user
        self.password = password
    def copy(self,local_path,remote_path):
        scp_put = r'''
   set timeout -1
            spawn scp %s
%s@%s:%s
            expect {
                     "yes/no" {
                                send "yes\r"
                                expect "password:"
                                send "%s\r"
                                }
                     "password:" { send "%s\r" }
                     }
            expect eof
            exit
        ''' % (local_path,self.user,self.host,remote_path,self.password,self.password)
        os.system("echo '%s' > /home/scp_cmd" % scp_put)
        os.system("expect -f /home/scp_cmd")
        os.system("rm -rf /home/scp_cmd")
if __name__=="__main__":
    cp = remoteCopy("10.0.10.13","root","123456")
    cp.copy('/home/a.txt','/home')

以上这个python脚本就是嵌套了expect的编码,使用python来执行这个expect脚本,并进行一些其他的处理,实现了远程复制的功能。。。。

三:expect的python版:pexpect
 如今,python中已经提供过了实现类似expect功能的模块pexpect,不用在嵌套expect编码,对于那些使用python而不用学习expect的人也提供了解决方案。
一个简单的例子:
#!/usr/bin/env python
#encoding: utf-8
import pexpect

if __name__=="__main__":
    host = "10.0.10.13"
    user = "root"
    password = "123456"


    child = pexpect.spawn('ssh %s@%s' % (user,host))
    child.expect("password:")
    child.sendline("%s" % (password))
    child.expect("]#")
    child.sendline('ls -al')
    child.sendline('ifconfig eth0')
    child.sendline("echo 'this is a pexpect test' > /home/b.txt")
    child.expect("#")
    child.sendline('exit')

 

    看到没,pexpect极大简化了在python中嵌套expect的工作。而且使用起来特别简单。。关于pexpect模块的介绍,请看下回分解。。。。

 

 

 

结束!!!

    笨蛋的技术------不怕你不会!!!