一、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 expect 命令无需输入密码登陆

Linux 两台主机之间建立信任

Linux两台主机之间建立信任

Linux 修改SSH 默认端口 22,防止被破解密码

Linux 修改默认端口、增加普通用户、使用密钥等安全登录SSH

Linux ssh 切换登录用户自动转到root用户

Linux sudo 免密码输入

SecureCRT 自动登录设置

JumpServer 堡垒机环境搭建详解

Windows 连接 Linux 常用工具

Linux之/etc/profile、~/.bash_profile等几个文件的执行过程

自建服务器解决外网访问内网的端口穿透映射

LastPass 跨平台密码管理工具