学而实习之 不亦乐乎

Linux 下 expect 命令的使用

2023-11-11 19:30:38

一、Expect介绍

expect是一个用来实现自动交互功能的软件套件,是用来实现自动和交互式任务程序进行通信,无需人的手工干预。比如SSH、FTP等,这些程序正常情况下都需要手工和他们交互,而使用 expect 就可以模拟人工交互的过程,实现自动化运维的目的。

二、Expect 安装及应用

1、安装

这里在 Centos 下使用 yum 方式安装:

# yum -y install expect

2、简单应用

在我们ssh连接服务器的情况下,如果没有把自己的公钥复制对目标主机的.ssh/authorized_keys文件下,我们是需要输入密码才可以连接的,但是在ssh脚本中如何实现自己输入密码?这个问题我们使用expect就可以解决,看如下操作:

直接使用ssh连接主机需要输入密码:

# ssh 192.168.10.5 /sbin/ifconfig eth0
root@192.168.10.5's password:
eth0      Link encap:Ethernet  HWaddr 00:16:3E:03:78:60
          inet addr:192.168.10.5  Bcast:192.168.10.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:273007 errors:0 dropped:0 overruns:0 frame:0
          TX packets:374106 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:93143657 (88.8 MiB)  TX bytes:91464671 (87.2 MiB)
          Interrupt:160

这是一个写好的 expect 脚本,如下:

#!/usr/bin/expect
spawn ssh 192.168.10.5 /sbin/ifconfig eth0
set timeout 60

expect {
         -timeout 5
         "yes/no"     { exp_send "yes\r" }
         "*password:" { exp_send "passtest\r" }
         timeout  {puts "expect was timeout by fblinux."; return}
}

expect eof
exit

使用expect命令执行这个脚本,我们可以看到没有提示我们输入密码就可以在目标主机执行命令

# expect expect.expect
spawn ssh 192.168.10.5 /sbin/ifconfig eth0
root@192.168.10.5's password:
eth0      Link encap:Ethernet  HWaddr 00:16:3E:03:78:60
          inet addr:192.168.10.5  Bcast:192.168.10.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:273073 errors:0 dropped:0 overruns:0 frame:0
          TX packets:374223 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:93162147 (88.8 MiB)  TX bytes:91491805 (87.2 MiB)
          Interrupt:160

三、Expect语法

1、Spawn

Spawn 命令是 expect 的初始命令,他用于启动一个进程,之后所有 expect 都在这个进程中进行,如果没有 spawn 语句,整个expect就没有办法进行了,spawn使用方法如下:

# spawn ssh 192.168.10.5 /sbin/ifconfig eth0

在spawn命令后面,直接加上要启动的进程、命令信息,除此之外,spawn还支持其他选项如:

  • -open:启动文件进程
  • -ignore:忽略某些信号

2、Expect

Expect表达式,动作 表达式 动作……

Expect命令用于等候一个相匹配内容的输出,一旦匹配上就执行expect后面的动作或命令,这个命令接收几个特有的参数,用的最多的就是-re,表示使用正则表达式的方式匹配,使用案例如下:

spawn ssh -p22 root@192.168.100.2 /sbin/ifconfig
expect "*password:"  { send "passtest\r"}

 

从上面的例子可以看出,expect是依附与spawn命令的,当执行ssh命令后,expect就匹配命令执行后的关键字:password:,如果匹配到了关键字就执行包含在{}括号中的send或exp_send动作,匹配以及动作可以放在二行,这样就不需要使用{}括号了,就像下面这样,实际完成的功能与上面是一样的。

spawn ssh -p22 root@192.168.100.2 /sbin/ifconfig
expect "*password:"
send "passtest\r"

3、exp_send和send

在上面的介绍中,我们已经看到了exp_send命令的使用,exp_send是expect中的动作命令,可以发送一些特殊符号,\r表示回车,\n换行、\t制表符等等,这些都与TCP中的特殊符号相同。

spawn ssh -p22 root@192.168.100.2 /sbin/ifconfig
expect "*password:"  
send "passtest\n"

send命令还有几个可用的参数:

  •   -i:指定spawn_id,这个参数用来向不同的spawn_id的进程发送命令,是进行多程序控制的关键参数。
  •   -s:s代表slowly,也就是控制发送的速度,这个参数使用的时候要与expect中的变量send_slow相关联。

4、Exp_continue

这个命令一般用在动作中,它被使用的条件比较,看看下面的例子:

#!/usr/bin/expect
spawn ssh 192.168.10.5 /sbin/ifconfig eth0
set timeout 60

expect {
         -timeout 5
         "yes/no"     { exp_send "yes\r";exp_continue }
         "*password:" { exp_send "passtest\r" }
         timeout  {puts "expect was timeout by fblinux."; return}
}

expect eof
exit

在这个例子中,可以发现exp_continue命令的使用方法,首先它要处于一个expect命令中,然后它属于一种动作命令,完成的工作就是从头开始遍历,也就是说如果没有这个命令,匹配第一个关键字以后就会继续匹配第二个关键字,但有了这个命令后,匹配第一个关键字以后,第二次匹配仍然从第一个关键字开始。

5、Send_user

Send_user命令用来把后面的参数输出到标准输出中去,默认的send、exp_send命令都是将参数输出到程序中去。如下:

#!/usr/bin/expect
if { $argc != 3 } {  #判断:如果执行脚本传入的参数少于3个,则在终端输入下面send_user中的内容
 send_user "usage: expect scp-expect.exp file host dir\n"
 exit
}

#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir  [lindex $argv 2]
set password "passtest"
spawn scp $file root@$host:$dir

expect {
        "yes/no"    {send "yes\r";exp_continue}
        "*password" {send "$password\r"}
}

expect eof
exit

如果执行脚本的时候参数少于三个,输入如下内容:

# expect expect.expect
usage: expect scp-expect.exp file host dir

如果执行脚本的时候传入正确参数则执行结果如下:

# expect expect.expect /etc/passwd 192.168.10.5 /root/
spawn scp /etc/passwd root@192.168.10.5:/root/
root@192.168.10.5's password:
passwd                                                     100% 1321     1.3KB/s   00:00

6、exit

exit命令功能很简单,就是直接退出脚本,但是你可以利用这个命令对脚本做一些扫尾工作,比如下面这样:

exit -onexit {
  exec rm $tmpfile  #删除临时文件
  send_user "Oldboy say good bye to you!\n" #终端输出字符
}

四、Expect变量

Expect中有很多有用的变量,他们的使用方法如下,比如:

  • set 变量名 变量值 #设置变量
  • puts $变量名 #读取变量
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir  [lindex $argv 2]
set password "123456"

Timeout 是 expect 中的一个重要变量,他是一个全局性的时间控制开关,你可以通过为这个变量赋值来规定整个 expect 的操作的时间,注意这个变量是全局的,他不会纠缠于某一条命令,即使命令没有任何错误,到时间任然会激活这个变量,但这个时间到达以后除了激活一个开关之外不会做其他的事情,如何处理是脚本编写人员的事情。使用案例如下:

set timeout 60
spawn ssh root@192.168.10.5
expect "password:" {send "word\r"}
expect timeout {puts "expect was timeout";return}

上面的处理中,首先将timeout变量设置为60秒,当出现问题的时候程序可能会停止下来,只要到60秒,就会激活下面的timeout动作。

在另一种expect格式中,我们还有一种设置timeout变量的方法,看看下面的例子。

spawn ssh root@192.168.10.5

expect {
         -timeout 5
         "yes/no"     { exp_send "yes\r" }
         "*password:" { exp_send "redhat\r" }
         timeout  {puts "expect was timeout by fblinux."; return}
}

五、使用案例

1、ssh分发

当新安装了一批服务器,需要通过 ansible 来管理服务器(ansible是通过ssh来认证的),这个时候如果每台服务器都使用 ssh-copy-id 来交互就比较麻烦了,因为每台服务器都需要输入密码,这种场景使用 expect 实现是极好的。

【1】分发前的准备工作

  初始化服务器的初始密码必须是一样的,这个应该放在运维装机规范中

  把自己的公钥文件命名为authorized_keys放到一个目录中,因为脚本是直接拷贝.ssh目录

# mkdir /data/.ssh/
# cp .ssh/id_rsa.pub /data/.ssh/authorized_keys

【2】写脚本

expect脚本:

# cat /shell/expect.exp

#!/usr/bin/expect

if { $argc != 3 } {
 send_user "usage: expect scp-expect.exp file host dir\n"
 exit
}

#define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set dir  [lindex $argv 2]
spawn scp -P22 -r -p $file root@$host:$dir

expect {
        "yes/no"    {send "yes\r";exp_continue}
        "*password" {send "passtest\r"}
}

expect eof
exit

shell脚本:

# cat /shell/ssh_init.ssh

#!/bin/bash

. /etc/init.d/functions
host="192.168.10.5"     #多个主机使用空格分隔,也可以使用两个变量网段+IP的形式,然后循环IP地址

for ip in $host;do
 expect /shell/expect.exp /data/.ssh/ $segment$ip /root/ >>/dev/null

 if [ $? -eq 0 ];then
  action "$segment$ip" /bin/true
 else
  action "$segment$ip" /bin/false

 fi

done

【3】使用

# sh /shell/ssh_init.ssh
192.168.10.5                                               [  OK  ]

执行完成之后,我们就可以直接登录到目标主机,而无须输入密码

2、Openvpn 用户自动创建脚本

创建openvpn用户的时候都需要有一大堆交互如输入国家、省份、城市、组织、邮件等等信息,特别麻烦。我现在想写一个脚本,只需要输入员工姓名和邮箱,就会自动把生产的密钥信息,还有 client 安装包,以及使用教程发送给员工,让员工按照教程操作即可。

注意:使用脚本需要配置邮件发送环境,不然创建成功后,无法自动发送证书文件和使用教程给员工。

expect脚本:

# cat /shell/vpn_expect.expect

#!/usr/bin/expect -f

if $argc<1 {
        puts stderr "Usage: $argv0 need argv.\n"
        exit 1
}

set vpnuser [lindex $argv 0]
set path /usr/local/openvpn/easy-rsa/2.0/
spawn $path/build-key $vpnuser
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "\r"
expect "*"
send "y\r"
expect "*"
send "y\r"

expect eof
exit

shell脚本:

# cat /shell/create_vpnuser.sh

#!/bin/bash
# 设置相关变量

work_dir=/usr/local/openvpn/easy-rsa/2.0
tmp_dir=/data/tmp/openvpn
mail_content=/shell/mail.txt
client_config=client.ovpn

# 使用帮助
help(){
  echo '添加vpn用户执行命令sh create_vpnuser.sh add'
  echo '删除vpn用户执行命令sh create_vpnuser.sh del'
}

# 判断用户是否存在
add_user(){
  # 交互输入用户名和邮箱
  read -p "please input a user name:" name
  read -p "please input a user email:" email

  if [ -f $work_dir/keys/$name.crt ];then
    echo "新建vpn用户存在,请检查!"
    exit 1
  else
   # 创建用户密钥
   cd $work_dir && source ./vars
   /usr/bin/expect /shell/vpn_expect.expect $name

   if [ $? != 0 ];then
    echo "创建密钥失败"
    exit 10
   fi

   # 密钥和配置文件打包
   cd $work_dir/keys/
   cp $name.* ca.crt $tmp_dir

   if [ $? != 0 ];then
    echo "复制密钥失败"
    exit 20
   fi

   sed -i s@vpnclient@$name@g $tmp_dir/$client_config
   cd $tmp_dir
   tar zcf $name.tar.gz $name.* ca.crt $client_config openvpn-2.2.2-install.exe

   # 发送邮件给员工
   sed -i s@vpnclient@$name@g $mail_content
   cat $mail_content | mutt -s "VPN帐号开通" $email -a $tmp_dir/$name.tar.gz
   sed -i s@$name@vpnclient@g $tmp_dir/$client_config
   sed -i s@$name@vpnclient@g $mail_content
  fi
}

del_user(){

  # 交互输入用户名
  read -p "please input a user name:" name
  if [ -f $work_dir/keys/$name.crt ];then
     cd $work_dir && source ./vars && ./revoke-full $name
  else
     echo "删除vpn用户不存在,请检查"
  fi

}

main(){

  case $1 in
   add)
   add_user;
   ;;

   del)
   del_user;
   ;;

   *)
   help;
   esac
}

main $1

发送邮件的内容:

# cat /shell/mail.txt
hi vpnclient:
    你的vpn帐号已经开通,使用方法见wiki连接;有问题及时与我联系。
    这里放上你们公司wiki中的openvpn安装教程