一、expect 简介

expect 是一个自动交互功能的工具,可以实现自动登录,不必手动输入密码(Password)、确认(Yes)等交互操作。

expect 是开了一个子进程,通过spawn来执行shell脚本,except监测到脚本的返回结果,并发送交互输入内容(send)

 

二、except 安装

1. Ubuntu 安装

sudo apt-get -y install tcl expect

2. CentOS 安装

yum -y install expect

3. MacOS 安装

brew install expect

安装完成后的目录为

# which expect
/usr/bin/expect

 

三、except 使用

1. expect 使用示例

#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12

set timeout 30
set passwd "mimvp.com"

spawn ssh -l mimvp 123.57.78.100
expect "password:"
send "$passwd\n"
interact

说明:

#!/usr/bin/expect  表示使用expect的shell交互模式,由expect来解释执行的而不是由bash,所以代码的语法和shell脚本也是不一样的

set timeout 30  表示对变量timeout赋值,为30秒。如果执行的shell命令耗时长可以设置超时时间长一些,默认timeout为10秒

set passwd "mimvp.com"  表示对变量passwd赋值,密码供后面的引用。特殊字符需要转义,例如 "\$mimvp.com"

spawn ssh -l mimvp 123.57.78.100  表示在expect下执行shell脚本,例如本例的 ssh 远程登录服务器

expect "password:"  表示对执行shell脚本的返回字符串进行判断,是否包含"password:"字段,一般可写做 "*assword:"

send "$passwd\n"  表示如果expect监测到了包含的字符串,将输入send中的内容,\n相当于回车,有的也写做 \r

interact  表示留在开的子进程内,可以继续输入,否则将退出子进程回到shell中。例如 ssh登录到某台服务器上,只有加了interact才可以留在登录后的机器上进行操作

 

2. expect 命令行参数

[lindex $argv n]获得参数下标为 index = n 的参数(index从0开始计算)

$argc为命令行参数的个数

[lrange $argv 0 0]表示第一个参数

[lrange $argv 0 3]表示第1到第3个参数

用法示例:

#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12

set timeout 30
set passwd "mimvp.com"

set ipaddr [lrange $argv 0 0]
#set ipaddr [lindex $argv 0]

spawn ssh -l root $ipaddr
expect "password:"
send "$passwd\n"
interact


## ./conn_server.sh 123.57.78.100

上例里,执行语句为

./conn_server.sh 123.57.78.100

其中:

./conn_server.sh  是脚本文件名

123.57.78.100  是服务器IP地址,set ipaddr [lrange $argv 0 0] 获取第一个参数IP地址,并赋值给 ipaddr

spawn ssh -l root $ipaddr  表示使用IP地址,进行ssh登录

例如:scp_service.sh文件,可以 ./scp_service.sh -rm 来执行,这时 -rm 是赋值的第一个参数

set option  [lindex $argv 0](获得第一个参数 -rm 存到变量option中,参数是的index是从0开始计算的)

 

3. expect 判断语句

expect支持if语句 if...elif...else...

if { 条件1 } {

     条件1执行语句

} elif { 条件2 } {

     条件2执行语句

} else {

     其他情况执行语句

}

用法示例:

#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12

set timeout 30
set passwd "mimvp.com"

if { [llength $argv] < 2 } {
    puts "Usage:"
    puts "$argv0 username ipaddr"
    exit 1
}

#set username [lindex $argv 0]
#set ipaddr [lindex $argv 1]

set username [lrange $argv 0 0]
set ipaddr [lrange $argv 1 1]
spawn ssh -l $username $ipaddr
expect "password:"
send "$passwd\n"
interact


$ ./conn_server.sh mimvp		## error
Usage:
./conn_server.sh username ipaddr


$ ./conn_server.sh mimvp 123.57.78.100		## success

说明:

1. if的条件用{}来包含条件

2. if 和 后面的{}必须有空格隔开

3. 两个花括号之间必须有空格隔开,比如if {} {},否则会报错 expect:extra characters after close-brace

3. 使用{来衔接下一行,所以if的条件后需要加左花括号{

4. else不能单独放一行,所以else要跟在}后面

 

except 实现 scp 拷贝

#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12

set timeout 30
set passwd "mimvp.com"


if { [llength $argv] < 2 } {
    puts "Usage:"
    puts "$argv0 local_file remote_path"
    exit 1
}

set local_file [lindex $argv 0]
set remote_file [lindex $argv 1]

set passwd_error 0

spawn scp $local_file $remote_file

expect {
    "*assword:*" {
        if { $passwd_error == 1 } {
            puts "passwd is error"
            exit 2
        }
        set timeout 100
        set passwd_error 1
        send "$passwd\n"
        exp_continue
    }
    "*es/no)?*" {
        send "yes\n"
        exp_continue
    }
    timeout {
        puts "connect is timeout"
        exit 3
    }
}

 

4. expect {} 多行期望

有时执行shell后预期结果是不固定的,有可能是询问是yes/no,有可能是去输入密码,所以可以用expect{}(比如sudo命令,第一次使用sudo时需要输入密码,但是它有5分钟的有效时间,5分钟内是不需要再去输入的)

花括号内放多行语句,从上至下匹配,匹配到哪个expect,则执行哪句。

这里如果匹配到第一行会执行第一行;然后第一行的执行结果如果匹配到第二行也会执行第三行;

如果某一行没有匹配到会向下寻找匹配到的那一行进行执行)

注意:多行的expect的{后不要跟语句,否则读不到这条,需要换行后去写具体的期望值和操作。

#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12

expect {
	"*assword" {send "mimvp.com\n"}
	"*es/no)?*" {send "yes\n"}
	"exit" {send "exit\n"}
	exp_continue
}

说明:exp_continue表示继续执行下面的expect。

 

四、shell中调用expect实现自动登录

通过在shell脚本中,执行expect脚本的方式来实现的。

当然可以将shell中定义的一些变量传递给expect脚本作为参数输入。

示例1:shell 调用 expect 脚本,间接调用

#!/bin/bash
#
# mimvp.com
# 2016.10.12

script_tmp="/Users/homer/script_tmp"
if [ ! -d ${script_tmp} ]; then
    echo "${script_tmp} is not found"
    mkdir -p ${script_tmp}
    cd ${script_tmp}
else
    cd ${script_tmp}
fi

## expect file
#./scp_remote_server.sh
/Users/homer/script/scp_remote_server.sh

 

示例2:shell 嵌套 expect 脚本,直接调用(推荐

#!/bin/bash
#
# mimvp.com
# 2016.10.12

username='mimvp-guest'
passwd='mimvp.com'
passwd2='\$mimvp.com'

/usr/bin/expect <<-EOF
set timeout 30
spawn /usr/bin/htpasswd -c /etc/squid/passwd  $username 
## first password
expect {
    "*es/no:" { send "yes\n"; exp_continue }
    "*assword:" { send "$passwd\n; exp_continue" }
}

## second password to confirm
expect "*assword:"
send "$passwd\n"



spawn ssh mimvp@123.57.78.100
expect {
    "*es/no" { send "yes\r"; exp_continue }
    "*assword" { send "$passwd2\r" }
}

expect "*#"
send "cd /home/script/\r"

#expect "*#"
#send "svn up\r"

expect "*#"
send "ls -l\r"

expect "*#"
send "exit\r"

#interact

expect eof
EOF

本段脚本,实现了两个功能:

功能1,使用htpasswd命令,输入密码,并再次确认密码

功能2,远程登录服务器,并svn up下载代码,并退出远程服务器

以上脚本,君测试成功!

说明:经过这次尝试些expect,给我的感觉是expect对格式的要求比较高,比如花括号之间必须有空格啊之类的,所以如果有报错,大家可以仔细观察一下是不是语法格式错误了。

 

expect 更多语法

1. expect中的判断语句

if { condition } {
     # do your things
} elseif {
     # do your things
} else {
     # do your things
}

expect中没有小括号(),所有的if/else, while, for的条件全部使用大括号{}, 并且{ 与左边要有空格,否则会报错。另,else 不能单独占一行,否则会报错。

2. 字符串比较

if { "$node" == "apple" } {
     puts "apple"
} elseif { "$node" == "other" } {
     puts "invalid name"
     exit 70
} else {
     puts "asd"
}

对比string,使用==表示相等, !=标示不相等。

3. switch 语句

switch $location {
    "apple" { puts "apple" }
    "banana" { puts "banana" }
    default {
        puts "other"
     }

记得左大括号{ 的左边要有空格,否则会报错

4. 读取用户输入

expect_user -re "(.*)\n"
send_user "$expect_out(1, string)\n"

expect_user -re 表示正则表达式匹配用户按下回车前输入的所有字符
expect_out(1, string) 表示第一个匹配的内容,即回车前所有字符
expect_out(buffer) 所有的buffer内容

5. break && continue
如c中一样,expect一样可以使用break && continue, 并且功能相同。注:只能用在循环中。

6. 定义交互命令

# stick control + z in variable
set ControlZ \032
# stick control + c in variable
set ControlC \x03
# define string embedded ctrl-z && tab
set oddword foo\032bar\tgorp

 

五、在远程服务器上配置ssh信任

网上有很多教程,推荐米扑博客:Linux两台主机之间建立信任 (非常经典,验证成功

感觉长期的话应该比写expect方便,但是我觉得写脚本的话还是最好不要总去操作其他地方,

所以这里我就用expect自己来写的(当然也是想练习一下写expect)

 

 

参考推荐

Linux两台主机之间建立信任