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

posted @ 2022-04-01 23:58  黄河大道东  阅读(2576)  评论(0编辑  收藏  举报