Linux expect命令详解
前言
expect是一种脚本语言,它能够代替人工实现与终端的交互,开发人员不必再守候在电脑旁边输入密码,或是根据系统的输出再运行相应的命令。
借助expect,可以将交互过程写在一个脚本上,使之自动化完成所需要的交互操作。形象的说,像ssh登录,ftp登录等都符合交互的定义。
1.判断当前系统是否安装expect
环境:which expect
,一般是安装在/usr/bin/expect
目录下
2.没有则进行安装:yum install expect -y
进行安装
3.使用场景
- shell脚本实现ssh自动登录远程服务器
#!/usr/bin/expect
spawn ssh 登录用户名@目标主机
expect "*password:"
send "密码\r"
expect "*#"
interact
下文我们首先提出一个问题,然后介绍基础知四个命令,最后提出解决方法。
问题场景
如何从A机器ssh登录到B机器上,然后执行B机器上的命令?如何使之自动化完成?
命令介绍
Expect中最关键的四个命令是send
,expect
,spawn
,interact
spawn:交互程序开始后面跟命令或者指定程序(在壳内启动这个进程)
expect:获取匹配信息匹配成功则执行expect后面的程序动作(检测由壳内进程发出的特定交互指令反馈字符串后向下执行)
send:用于向进程发送字符串(从壳外向壳内进程发送一条字符串,换行符为确认结束)
interact:允许用户交互
其它命令
exp_continue 在expect中多次匹配就需要用到
send_user 用来打印输出 相当于shell中的echo
exit 退出expect脚本
eof expect执行结束 退出
set 定义变量
puts 输出变量
set timeout 设置超时时间
1. send命令
send命令接收一个字符串参数,并将该参数发送到进程(想象给目标进程加了一层壳,从壳外面send密码到壳内的进程,这个过程类似模拟人类输入密码)。
expect1.1> send "hello world\n"
hello world
2. expect命令
(1)基础知识
expect命令和send命令正好相反,expect通常是用来等待一个进程的反馈。expect可以接收一个字符串参数,也可以接收正则表达式参数。和上文的send命令结合,现在可以看一个最简单的交互式的例子:
#!/usr/bin/expect
expect "hi\n"
send "hello there!\n"
这两行代码的意思是:从标准输入中等到hi和换行键后,向标准输出输出hello there! 。
tips: $expect_out(buffer)存储了所有对expect的输入,<$expect_out(0,string)>存储了匹配到expect参数的输入。
比如如下程序:
#!/usr/bin/expect
expect "hi\n"
send "you typed $expect_out(buffer)"
send "but I only expected $expect_out(0,string)"
当在标准输入中输入时
test
hi
运行结果如下
you typed: test
hi
I only expected: hi
(2)模式-动作
expect最常用的语法是来自tcl语言的模式-动作。这种语法极其灵活,下面就各种语法分别说明。
单一分支模式语法:
expect "hi" {send "You said hi"}
壳内进程发出的反馈字符串被壳外的expect匹配到hi
字符串后,会输出"you said hi"
多分支模式语法:
#!/usr/bin/expect
expect "hi" { send "You said hi\n" } "hello" { send "Hello yourself\n" } "bye" { send "That was unexpected\n" }
匹配到hi,hello,bye任意一个字符串时,执行相应的输出。等同于如下写法:
#!/usr/bin/expect
expect {
"hi" { send "You said hi\n"}
"hello" { send "Hello yourself\n"}
"bye" { send "That was unexpected\n"}
}
3. spawn命令
上文的案例都是和标准输入输出进行交互,但是我们跟希望他可以和某一个进程进行交互。spawm命令就是用来启动新的进程的。spawn后的send和expect命令都是和spawn打开的进程进行交互的。结合上文的send和expect命令看一下更复杂的程序。
#!/usr/bin/expect
set timeout -1
spawn ftp ftp.test.com //打开新的进程,该进程用户连接远程ftp服务器
expect "Name" //进程返回Name时
send "user\r" //向进程输入anonymous\r
expect "Password:" //进程返回Password:时
send "123456\r" //向进程输入don@libes.com\r
expect "ftp> " //进程返回ftp>时
send "binary\r" //向进程输入binary\r
expect "ftp> " //进程返回ftp>时
send "get test.tar.gz\r" //向进程输入get test.tar.gz\r
这段代码的作用是登录到ftp服务器ftp ftp.uu.net上,并以二进制的方式下载服务器上的文件test.tar.gz。
4.interact命令
到现在为止,已经可以结合spawn、expect、send自动化的完成很多任务了。但是,如何让人在适当的时候干预这个过程呢。比如下载完ftp文件时,仍然可以停留在ftp命令行状态,以便手动的执行后续命令。interact可以达到这些目的。下面的案例在自动登录ftp后,允许用户交互。
#!/usr/bin/expect
spawn ftp ftp.test.com
expect "Name"
send "user\r"
expect "Password:"
send "123456\r"
interact
解决最开始的问题
如何从A机器上ssh登录到B机器上,然后执行B机器上的命令?如何使之自动化完成?
下面一段脚本实现了从机器A登录到机器B,然后执行机器B上的pwd命令,并停留在B机器上,等待用户交互。具体含义请参考上文。执行方法 。./test.sh 用户名 主机地址 密码
#!/usr/bin/expect -f
# 设置超时
set timeout -1
# 壳内启动ssh进程,取到命令行传递的第一个参数作为用户名,取出命令行的第二个参数作为主机地址
spawn ssh [lindex $argv 0]@[lindex $argv 1]
# 取出命令行传递的第三个参数作为密码
expect "*password:" { send "[lindex $argv 2]\r" }
expect "$*" { send "pwd\r" }
interact
expect -f 参数?
expect脚本的开头一般都写/usr/bin/expect -f,这个-f选项有什么作用呢?
比如如下脚本
#!/usr/bin/expect -f
# 定义变量i,默认值为0,将参数总个数$argc赋值给i
for {set i 0} {$i < $argc} {incr i} {
# 输出 每一个参数
puts "arg $i: [lindex $argv $i]"
}
运行./test.sh -c "puts foo" hehe bar
输出如下
(此处其实没有搞明白foo是为什么会输出,推测可能是将puts foo
解释为输出foo字符串这个命令了,expect中的puts类似bash中的echo)
foo
arg 0: hehe
arg 1: bar
如果改成#!/usr/bin/expect
,则输出如下(expect将命令行传递的内容以空格符为分隔全部理解为参数了)
arg 0: -c
arg 1: puts foo
arg 2: hehe
arg 3: bar
脚本示例
- ssh远程登录主机执行命令,在shell脚本中执行expect命令
#!/bin/bash
passwd='登录密码'
/usr/bin/expect <<EOF
set time 30
spawn ssh 登录用户@主机地址 df -Th
expect {
"*yes/no" { send "yes\r"; exp_continue }
"*password:" { send "$passwd\r" }
}
expect eof
EOF
- expect执行多条命令
#!/usr/bin/expect -f
set timeout 10
spawn sudo su - root
expect "*password*"
send "登录密码\r"
expect "#*"
send "ls\r"
expect "#*"
send "df -Th\r"
send "exit\r"
expect eof
- 创建ssh key,将id_rsa和id_rsa.pub文件分发到各台主机上面。
# 1.创建主机配置文件
[root@localhost script]# cat host
192.168.1.10 root 123456
192.168.1.20 root 123456
192.168.1.30 root 123456
[root@localhost script]# ls
copykey.sh hosts
# 2.编写copykey.sh脚本,自动生成密钥并分发key.
[root@localhost script]# vim copykey.sh
#!/bin/bash
# 判断id_rsa密钥文件是否存在
if [ ! -f ~/.ssh/id_rsa ];then
ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
else
echo "id_rsa has created ..."
fi
#分发到各个节点,这里分发到host文件中的主机中.
while read line
do
user=`echo $line | cut -d " " -f 2`
ip=`echo $line | cut -d " " -f 1`
passwd=`echo $line | cut -d " " -f 3`
expect <<EOF
set timeout 10
spawn ssh-copy-id $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$passwd\n" }
}
expect "password" { send "$passwd\n" }
EOF
done < hosts
- shell调用expect执行多行命令
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
# 创建用户
expect "]#" { send "useradd \n" }
# 创建临时文件
expect "]#" { send "touch /tmp/test.txt\n" }
# 退出
expect "]#" { send "exit\n" } expect eof
EOF
# 执行方法
#./test.sh 192.168.8.8 root root
参考:
https://www.cnblogs.com/lqyye/p/7224268.html
https://www.cnblogs.com/mingyunrangwozoudaoxianzai/p/11208887.html