• Post author:
  • Post category:shell
  • Post comments:0评论

一、工具介绍

  有时候在编写shell脚本的过程中,我们难免会遇到必须需要交互的情况,但我们又想让脚本全自动化,此时我们就可以使用expect来实现自动交互,而无需人工干预。

二、基本语法

  expect最重要最基础的就是下面三个命令,也是最常用的,掌握了基本上就可以很好的满足我们一些简单的自动交互需求了。

命令 含义
spawn 创建一个新的进程运行程序,以便其他Expect命令可以读写它们
expect 匹配进程的反馈,即匹配进程进行交互输出信息的关键字,或者说捕获进程要进行交互的输出信息,交由后面的send命令进行交互
send 将字符串发送给当前进程

我们来看看以下几种使用方式:

[root@test expect]# yum install -y expect
[root@test expect]# cat expect1.sh     # 首推
#!/bin/bash
expect << EOF
spawn read -p "Please enter your password: "
expect "password"
send "000000\n"
expect eof
EOF

[root@test expect]# cat expect2.sh 
#!/bin/bash
expect -c "
spawn read -p \"Please enter your password: \"
expect \"password\"
send \"000000\n\"
expect eof
"

[root@test expect]# cat expect3.sh     # 不能使用bash或者sh执行脚本,需要用路径方式执行
#!/usr/bin/expect -f
spawn read -p "Please enter your password: "
expect "password"
send "000000\n"
expect eof

三、示例

1、ssh

我们先来看下ssh具体的交互输出信息

[root@test scripts]# ssh root@127.0.0.1 uptime
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:Lm3xK3Mxlh4MzOms8v/Lnk1NH8eyzJTLEsTBBGpcI/A.
ECDSA key fingerprint is MD5:90:1b:0d:c5:ad:b4:9d:a6:9d:2a:eb:d8:77:77:a4:70.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts.
root@127.0.0.1's password: 
 20:19:58 up 45 min,  3 users,  load average: 0.08, 0.05, 0.05、

注意,进行下面测试时删除已经保存的公钥指纹。

使用expect自动交互

[root@test scripts]# vim expect.sh 
#!/bin/bash

expect << EOF
spawn ssh root@127.0.0.1 uptime
expect "(yes/no)?"
send "yes\r"
expect "password"
send "000000\r"
expect eof
EOF

[root@test scripts]# sh expect.sh 
spawn ssh root@127.0.0.1 uptime
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:Lm3xK3Mxlh4MzOms8v/Lnk1NH8eyzJTLEsTBBGpcI/A.
ECDSA key fingerprint is MD5:90:1b:0d:c5:ad:b4:9d:a6:9d:2a:eb:d8:77:77:a4:70.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts.
root@127.0.0.1's password: 
 20:23:35 up 48 min,  3 users,  load average: 0.00, 0.03, 0.05

要知道公钥检查,只会在第一次连接是询问,那么再次执行脚本时就出现下面问题。

[root@test scripts]# sh expect.sh 
spawn ssh root@127.0.0.1 uptime
root@127.0.0.1's password: 
Permission denied, please try again.
root@127.0.0.1's password: [root@kvm01 scripts]# 
[root@kvm01 scripts]# 

  为此,我们可以用以下方式写。exp_continue指令允许expect本身继续执行,而不是像通常那样返回。需要注意的是在expect包含中最后一个send不要加此指令,因为允许向下继续执行,如果向下没有了,就相当于没有匹配上,就会触发等待10秒超时。

[root@test scripts]# cat expect.sh 
#!/bin/bash

expect << EOF
spawn ssh root@127.0.0.1 uptime
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password" {send "000000\r"}
}
expect eof
EOF

[root@test scripts]# sh expect.sh 
spawn ssh root@127.0.0.1 uptime
root@127.0.0.1's password: 
 21:34:23 up  1:59,  1 user,  load average: 0.00, 0.01, 0.05


四、问题
1、当匹配的模式不存在时,expect会如何应对
  注意点:以下依照man帮助手册,可能翻译理解不准确。以下模式含义为expect命令匹配的进程的交互问询。
   • 单模式匹配:即只有单个匹配。如果expect匹配上,则执行相应的send。如果匹配不到模式,它默认会等待10秒,传递空字符串交互。
   • 多模式匹配:即多个匹配。如果expect匹配上,则执行相应的send。如果匹配不到模式,它默认会等待10秒,10秒后按顺序填入send传递的信息,因为按顺序来,那么这个send对应是上一个问询的应答,这就是会造成问询和交互信息对不上号。所以多模式匹配时建议,如果有类似ssh这种情况,建议将问询和应答都包含在expect命令中,这种写法会一一对应,即使匹配不上,也不会出现对应错误,而是跳过匹配不上问询对应的应答,执行下面的。
  以上是总结,以下是小实验验证。
  当匹配模式不存在时,并不影响send命令的交互,会按顺序来,就会出现下面情况。整体流程,当脚本执行时,程序的问询已经出来了在等待,expect开始匹配"awd",没匹配上,等待2秒,然后按顺序执行下面send交互,所以填入了a。因为实验,为了不等10秒,将超时时间定义为2秒。

[root@test scripts]# cat expect1.sh 
#!/bin/bash
expect << EOF
set timeout 2
spawn read -p "Please enter your password: "
expect "awd"
send "a\n"
expect "password"
send "000000\n"
expect eof
EOF
[root@test scripts]# sh expect1.sh 
spawn read -p Please enter your password: 
Please enter your password: a
000000

2、exp_continue
  这里有些没搞清楚,写的有点混乱,建议不要阅读,免得大家也搞懵,望了解的大佬指点。
  man帮助手册是这样解释的:exp_continue命令允许expect本身继续执行,而不是像通常那样返回,默认情况下exp_continue会重置超时计时器。

[root@test scripts]# cat expect1.sh 
#!/bin/bash
expect << EOF
set timeout 2
spawn read -p "Please enter your password: "
expect {
"pawd" {send "a\n";exp_continue}
"password" {send "000000\n"}
}
expect eof
EOF

[root@test scripts]# sh expect.sh 
spawn read -p Please enter your password: 
Please enter your password: 000000

[root@test scripts]# cat expect2.sh 
#!/bin/bash

expect << EOF
set timeout 2
spawn read -p "Please enter your password: "
expect {
"pawd" {send "a\n"}
"password" {send "000000\n"}
}
expect eof
EOF

[root@test scripts]# sh expect.sh 
spawn read -p Please enter your password: 
Please enter your password: 000000

  通过上面对比,我们发现在这种书写形式中加不加exp_continue,效果一样。即一个问询对应着一个应答,如果不存在会直接跳过不存在的问询和应答。那问题来了这个指令有啥用?
  有点懵,害算了,不深究了,死用算了,写的时候还是加上exp_continue,能达成效果就好了。
  需要注意的是exp_continue必须用在expect中,且只作用当前expect。最后一个应答不要使用exp_continue,它允许expect本身继续执行,而不是像通常那样返回。因为允许向下继续执行,如果向下没有了,就相当于没有匹配上,就会触发等待10秒超时。如下例子

[root@test ~]# cat test.sh 
#!/bin/bash
expect << EOF
set timeout 5
spawn ssh 127.0.0.1
expect {
"(yes/no)?" {send "yes\r";exp_continue}
"password" {send "000000\n";exp_continue}}
expect "#"
send "ls\n"
expect eof
EOF
[root@test ~]# sh test.sh 
spawn ssh 127.0.0.1
root@127.0.0.1's password: 
Last login: Fri Nov 27 06:15:44 2020 from localhost
[root@test ~]# ls       # 因为加了exp_continue,触发超时,等待了5秒执行。
anaconda-ks.cfg  awk  grep  sed  test.sh
[root@test ~]# [root@test ~]#       # 等待超时5秒,自动退回原窗口。

发表评论

验证码: 6 + 3 =