使用背景

有时候在写shell脚本的时候,总是会遇到需要输入密码等情况,比如这次要在虚拟机上安装公司产品,但是由于虚拟机环境,又不能安装公司提供镜像。所以就需要手动安装,但是由于配套产品过多,所需操作也多,所以就打算写一个脚本,进行自动安装(这个时候你可能会想问 那为什么不使用 linux 管理面板比如 宝塔、APPNODE等 进行批量管理这些服务器呢,emmm 主要是因为公司的特殊性大多都是安防产品,不适合用第三方的管理工具,而且也不能保证第三方管理工具会不会和公司产品冲突,所以还是自己写个简便的脚本进行安装吧。)。
安装的时候发现由于命令繁琐,如果一台台的安装几十台可能需要重复的命令打几十遍,这个时候就想到使用脚本在一台主机上将文件分发到各个服务器,然后使用远程命令进行安装。
首先想到的肯定是使用Python进行编写。我在使用Python的时候,一般就是调用标准库 OS 的 system 命令和 popen 命令进行安装,这个时候就难免会使用到SSH 命令。而SSH 命令的话,是需要输入密码的那显然就无法做到自动输入密码。所以我也就放弃这个方法了。(也许是我对os库了解的不够透彻)
我转而使用shell脚本。刚刚开始是打算使用shell 进行远程安装,但是还是遇到了相同的问题。
这个时候就要想其他办法,减少不必要的麻烦,最终找到了可以和linux进行交互的expect命令加入进去进行脚本编写,就可以实现输入密码了。
我们来介绍下并且学习下ecpect这个命令。

EXPECT介绍

expect是一个自动化交互套件,主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。
expect自动交互流程:
spawn启动指定进程---expect获取指定关键字---send向指定程序发送指定字符---执行完成退出.
注意该脚本能够执行的前提是安装了expect

yum install -y expect

expect常用命令总结:

spawn               交互程序开始后面跟命令或者指定程序
expect              获取匹配信息匹配成功则执行expect后面的程序动作
send exp_send       用于发送指定的字符串信息
exp_continue        在expect中多次匹配就需要用到
send_user           用来打印输出 相当于shell中的echo
exit                退出expect脚本
eof                 expect执行结束 退出
set                 定义变量
puts                输出变量
set timeout         设置超时时间

send命令接收一个字符串参数,并将该参数发送到进程。
expect命令和send命令相反,expect通常用来等待一个进程的反馈,我们根据进程的反馈,再发送对应的交互命令。
spawn命令用来启动新的进程,spawn后的send和expect命令都是和使用spawn打开的进程进行交互。
interact命令用的其实不是很多,一般情况下使用spawn、send和expect命令就可以很好的完成我们的任务;但在一些特殊场合下还是需要使用interact命令的,interact命令主要用于退出自动化,进入人工交互。比如我们使用spawn、send和expect命令完成了ftp登陆主机,执行下载文件任务,但是我们希望在文件下载结束以后,仍然可以停留在ftp命令行状态,以便手动的执行后续命令,此时使用interact命令就可以很好的完成这个任务。

示例,我们看下如何使用spawn

1、ssh登录远程主机执行命令,执行方法 expect 1.sh 或者 ./1.sh

# vim 1.sh 

#!/usr/bin/expect

spawn ssh root@127.0.0.1 df -th
expect "*password"
send "******\n"
expect eof

运行结果如下,你会发现在交互命令的时候,已经自动输入我们在脚本里面事先写好的密码了。

1610521917(1).jpg

2、ssh远程登录主机执行命令,在shell脚本中执行expect命令,执行方法sh 2.sh、bash 2.sh./2.sh都可以执行.

#!/bin/bash

passwd='123456'

/usr/bin/expect <<-EOF

set time 30
spawn ssh root@127.0.0.1 df -Th
expect {
"*yes/no" { send "yes\r"; exp_continue }
"*password:" { send "$passwd\r" }
}
expect eof
EOF

3、expect执行多条命令

#!/usr/bin/expect -f

set timeout 10

spawn sudo su - root
expect "*password*"
send "123456\r"
expect "#*"
send "ls\r"
expect "#*"
send "df -Th\r"
send "exit\r"
expect eof

4、创建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

5、 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 hehe\n" } 
    expect "]#" { send "touch /tmp/test.txt\n" } 
    expect "]#" { send "exit\n" } expect eof 
 EOF

然后在linux上执行:

 #./ssh5.sh 127.0.0.1 root 123456

6、使用普通用户登录远程主机,并通过sudo到root权限,通过for循环批量在远程主机执行命令.

$ cat timeout_login.txt 
1.1.1.1
1.1.1.1
1.1.1.1

创建一个ip地址文件一行一个ip地址。

#!/bin/bash

for i in `cat /home/admin/timeout_login.txt`
do

    /usr/bin/expect << EOF
    spawn /usr/bin/ssh -t -p 22022 admin@$i "sudo su -"

    expect {
        "yes/no" { send "yes\r" }
    }   

    expect {
        "password:" { send "xxo1#qaz\r" }
    }
    
    expect {
        "*password*:" { send "xx1#qaz\r" }
    }

    expect "*]#"
    send "df -Th\r"
    expect "*]#"
    send "exit\r"
    expect eof

EOF
done

例如下面的脚本可以实现 当密码过期需要批量修改密码的时候。

#!/bin/bash

for i in `cat /root/soft/ip.txt`
do

    /usr/bin/expect << EOF
    spawn /usr/bin/ssh root@$i

    expect {
        "UNIX password" { send "dahua@123\r" }
    }
    
    expect {
        "New password:" { send "dahua@1234#\r" }
    }

   expect {
        "Retype new password:" { send "dahua@1234#\r" }
    }

    expect "*]#"
    send "echo dahua@123|passwd --stdin root\r"
    expect "*]#"
    send "exit\r"
    expect eof
EOF
done