expect非交互式批量分发sshkey秘钥

expect非交互式批量分发sshkey秘钥

1.什么是Expect?

Expect是一个用来实现自动交互功能的软件套件,基于Tcl的免费脚本编程工具语言
用于实现自动对交互式任务程序进行通信,无需人手工干预,
例如ssh,ftp等这些程序正常需要手工进行交互,使用expect就可以模拟手工交互的过程,实现自动和远端程序的交互,从而达到自动化运维的目的

虽然使用c,perl等语言一样可以实现交互功能,但是expect更加简单,专业,出色,而且支持linux,unix,windows平台,是为系统管理和软件测试方面的自动交互需求而产生的

2.Expect程序的工作流程

  • spawn启动进程 ssh
  • expect期待关键字 要密码
  • send向程序发送字符 提供密码
  • 退出结束 退出结束

3.使用yum安装安装Expect软件

yum install expect -y

手工连接测试

[nice@zstest1 ~]$ ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
root@192.168.1.72's password: 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:A8:09:67  
          inet addr:192.168.1.72  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fea8:967/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10056911 errors:0 dropped:0 overruns:0 frame:0
          TX packets:19760 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:666475843 (635.6 MiB)  TX bytes:2661121 (2.5 MiB)

expect脚本测试

[nice@zstest1 ~]$ vim expect1.exp 
-----------------------------------------------
#!/usr/bin/expect
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
set timeout 60
expect "*password"
send "123456\n"
expect eof
exit
-----------------------------------------------
[nice@zstest1 ~]$ chmod 700 expect1.exp
[nice@zstest1 ~]$ ll expect1.exp 
-rwx------ 1 nice nice 137 12月 12 18:00 expect1.exp
[nice@zstest1 ~]$ ./expect1.exp  
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
root@192.168.1.72's password: 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:A8:09:67  
          inet addr:192.168.1.72  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fea8:967/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10057781 errors:0 dropped:0 overruns:0 frame:0
          TX packets:19801 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:666544863 (635.6 MiB)  TX bytes:2668445 (2.5 MiB)

或者用expect直接执行

[nice@zstest1 ~]$ expect expect1.exp 
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
root@192.168.1.72's password: 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:A8:09:67  
          inet addr:192.168.1.72  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fea8:967/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10058392 errors:0 dropped:0 overruns:0 frame:0
          TX packets:19832 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:666596473 (635.7 MiB)  TX bytes:2673360 (2.5 MiB)

5.Expect语法

5.1.spawn

用于启动一个进程,没有就无法执行

spawn ssh root@192.168.1.72

在spawn后面直接加上要启动的进程命令等信息

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

5.2.expect

用于等候一个匹配的内容,一旦匹配就执行expect后面的动作
使用方法:
expect 表达式 动作 表达式 动作
参数:

  • -re 表示使用正则表达式的方式匹配

例如:

spawn ssh root@192.168.1.72
expect "password:"
send "123456\r"		"\r"表示回车

或者,匹配动作放到下一行,动作需要加大括号"{}"

spawn ssh root@192.168.1.72
expect "password:"  {send "123456\r" }

多次匹配关键字并给出不同的动作

spawn ssh root@192.168.1.72
expect {
   "yes/no"        { exp_send "yes\r"; exp_continue }
   "password:"   { exp_send "123456\r" }
}

5.3.send和exp_send

send的使用参数

  • -i 制定spawn_id,用来向不同spawn_id的进程发送命令,用于进行多程序控制
  • -s slowly,控制发送的速度,需要与变量send_slow相关联

exp_send可以发送一些特殊符号,例如

  • \r 回车
  • \n 换行
  • \t 制表符

5.4.exp_continue

一般用在动作中,使用条件比较严格
如下脚本示例:

[nice@zstest1 ~]$ cat expect2.exp 
#!/usr/bin/expect
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
set timeout 60
expect {
   -timeout   10
   "yes/no"             { exp_send "yes\r";exp_continue }
   "*password:"         { exp_send "123456\r" }
   timeout              { puts "expect was timeout by oldboy.";return }
}
expect eof
exit
[nice@zstest1 ~]$ > ~/.ssh/known_hosts
[nice@zstest1 ~]$ expect expect2.exp 
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
The authenticity of host '192.168.1.72 (192.168.1.72)' can't be established.
RSA key fingerprint is a3:35:af:8c:26:1a:f0:b0:9f:0a:5f:7c:c0:6c:92:06.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.72' (RSA) to the list of known hosts.
root@192.168.1.72's password: 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:A8:09:67  
          inet addr:192.168.1.72  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fea8:967/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10244521 errors:0 dropped:0 overruns:0 frame:0
          TX packets:20149 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:678855345 (647.4 MiB)  TX bytes:2702841 (2.5 MiB)

[nice@zstest1 ~]$ 
[nice@zstest1 ~]$ 
[nice@zstest1 ~]$ cat ~/.ssh/known_hosts    
192.168.1.72 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAtJ4gTJmW3g/9fqovdImKk87yH+BQKZNihOkCcJFUhHSVWL6da6Cr9yI4CwxbHc6p2Sf1b+l3jcJkVjFJfheNc/nf8OyQZgdCHix0eHE1656XGsRUNRP1kUwiL/Fuv7Adlt1Eto43dqH6yfjW3KWS+mTOWTBzSHbXMUj5dKCEe3femZ5DeVbCJrOIiiTjpwrsIDuoC+BoLwcJeMqj/60YSwb3CgYgCqgqW+dpo/lqwu8kA6SesYC45DJ8+VwWg80MqkBRmPjg2Z44AEIQNcXnOPUibGtoAdB6+P5iTcMGJx9v30Es8OwNMt38rDrwR8T5jjX3EczEFoLxXv/arTp5fQ==

5.5.send_user

把后面的参数输出到标准输出中去,就是向屏幕中打印字符,相当于shell中的printf,echo等,默认的send,exp_send命令都是将参数输出到程序中去,例如:

send_user "Please imput passwd:"

以上语句就尅在标准输出中打印"Please imput passwd:"字符了

[nice@zstest1 ~]$ cat fenfa_sshkey.exp    
#!/usr/bin/expect
if { $argc !=2 } {
   send_user "USAGE: expect fenfa_sshkey.exp file host\n"
   exit
}
# define var
set file [lindex $argv 0]
set host [lindex $argv 1]
set password "123456"
#spawn scp /etc/hosts root@10.0.0.8:/etc/hosts
#spawn scp -P52113 $FILE oldboy@$HOST:$DIR
spawn ssh-copy-id -i $file "-p 22 nice@$host"
expect {
   "yes/no"    {send "yes\r";exp_continue}
   "*password" {send "$password\r"}
}
expect eof
exit -onexit {
   send_user "zhaoshuai say good bye to you!\n"
}
[nice@zstest1 ~]$ 
[nice@zstest1 ~]$ ./fenfa_sshkey-zs.exp 
USAGE: expect fenfa_sshkey.exp file host

5.6.exit

退出脚本,可以利用该命令对脚本做一些扫尾工作。例如

exit -onexit { 
   exec  rm $tmpfile
   send_user "Good bye\n"
}

6.Expect变量

set 变量名 变量值		# 设置变量
puts $变量名			# 调用变量
# define var
set file   [lindex $argv 0]	# 接收命令行的参数
set host [lindex $argv 1]
set password "123456"

7.Expect关键字

expect中的特殊关键字用于匹配过程,代表某些特殊含义或状态,一般用于expect族命令中而不能在外面单独使用,可以理解为事件,使用方法
expect eof { }

7.1.eof

end-of-file 关键字用于匹配结束符,例如文件结束,ftp传输停止等情况,后面接动作来做进一步控制,例如

[nice@zstest1 ~]$ cat expect3.exp 
#!/usr/bin/expect
spawn ftp nice@ftp.coinnice.cn
expect {
   "password"   { exp_send "who am I" }
   eof { ftp connect close }
}
interact { }
[nice@zstest1 ~]$ 

7.2.timeout

是一个全局性的使劲控制开关,可以通过为这个变量赋值来规定整个expect操作得时间,
注意,这个变量时全局的,他不会纠缠于某一条命令,即使命令没有任何错误,到时见仍然会激活这个变量,但是时间到大吼除了激活一个开关之外不会做其他的事情
timeout变量中设置为0表示立即超时,设为-1则表示永不超时

用法如下:

set timeout 60
spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
expect  "yes/no"             { exp_send "yes\r" }
expect  "password:"         { exp_send "123456\r" }
timeout       { puts "expect was timeout";return }

另一种expect格式
在expect命令中间加上横杠“-”,用于设置timeout

spawn ssh -p22 root@192.168.1.72 /sbin/ifconfig eth0
set timeout 60
expect   {
   -timeout   60
   -re   "yes/no"             { exp_send "yes\r" }
   -re   "password:"        { exp_send "123456\r" }
   timeout       { puts "expect was timeout";return }
}

8.生产实例

实例1:

使用expect批量分发/etc/hosts文件

vim fenfa_file.exp
----------------------------------------------------------------
#!/usr/bin/expect
if { $argc !=3 } {  # 判断脚本后面跟的参数数量,部委2则提示以下信息
   send_user "USAGE: expect fenfa_file.exp file host dir\n"
   exit
}

# define var
set file [lindex $argv 0]  # 定义变量file
set host [lindex $argv 1]  # 定义变量host
set dir [lindex $argv 2]   # 定义变量dir
set password "123456"  # 定义变量password

#spawn scp /etc/hosts root@10.0.0.8:/etc/hosts
#spawn scp -P52113 $FILE oldboy@$HOST:$DIR
spawn ssh-copy-id -i $file "-p 22 nice@$host"
expect {
   -timeout  5
   "yes/no"    {send "yes\r";exp_continue}
   "*password" {send "$password\r"}
   timeout    {puts "expect connect timeout";return}
}
expect eof

exit -onexit {
   send_user "Good Bye!\n"
}
# script usage
# expect oldboy-6.exp file host dir
# example:
# expect oldboy-6.exp /etc/hosts 10.0.0.11 /etc/hosts
# expect oldboy-6.exp /etc/hosts 10.0.0.12 /etc/hosts
----------------------------------------------------------------

分发脚本:一次分发多个IP

vim fenfa_hosts.sh
--------------------------------------------------------------------
#!/bin/sh
. /etc/init.d/functions
for ip in `cat ip.list`
do
   expect fenfa_file.exp /etc/hosts $ip /etc/hosts>/dev/null 2>&1
   if [ $? -eq 0 ];then
      action "$ip" /bin/true
   else
      action "$ip" /bin/false
   fi
done
--------------------------------------------------------------------
vim ip.list
--------------------------------------------------------------------
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
--------------------------------------------------------------------

fenfa_file.exp文件用于交互,fenfa_hosts.sh脚本用于分发文件,ip.list用于确定目标主机
如果想分发其他文件需要修改fenfa_hosts.sh脚本里面的文件和目录

实例2:

使用expect实现批量分发ssh秘钥文件(首次分发秘钥)

由于公钥文件和文件夹有固定的权限所以可以直接复制.ssh文件夹
将私钥移出~/.ssh文件夹
修改公钥的名字为sshd_config里面指定的
注意scp复制文件夹时需要使用-p参数保持原有属性

cat fenfa_file.exp 
----------------------------------------------------------------------------
#!/usr/bin/expect
if { $argc !=3 } {
   send_user "USAGE: expect fenfa_file.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 "123456"
spawn scp -P22 -r -p $file root@$host:$dir
expect {
   "yes/no"    {send "yes\r";exp_continue}
   "*password" {send "$password\r"}
}
expect eof
exit
-----------------------------------------------------------------------
vim fenfa_sshkey.sh
--------------------------------------------------------------
#!/bin/sh
. /etc/init.d/functions
for ip in `cat ip.list`
do
   expect fenfa_file.exp /root/.ssh $ip /root/.ssh #>/dev/null 2>&1
   if [ $? -eq 0 ];then
      action "$ip" /bin/true
   else
      action "$ip" /bin/false
   fi
done
-----------------------------------------------------------------
vim ip.list 
----------------------------
192.168.1.72
192.168.1.73
192.168.1.74
192.168.1.75
----------------------------

实验测试

################################################################
[root@zstest1 ~]# ./fenfa_sshkey.sh 
spawn scp -P22 -r -p /root/.ssh root@192.168.1.72:/root/.ssh
The authenticity of host '192.168.1.72 (192.168.1.72)' can't be established.
RSA key fingerprint is a3:35:af:8c:26:1a:f0:b0:9f:0a:5f:7c:c0:6c:92:06.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.72' (RSA) to the list of known hosts.
root@192.168.1.72's password: 
known_hosts                                                                                      100%  394     0.4KB/s   00:00    
authorized_keys                                                                                  100%  602     0.6KB/s   00:00    
192.168.1.72                                               [确定]
spawn scp -P22 -r -p /root/.ssh root@192.168.1.73:/root/.ssh
The authenticity of host '192.168.1.73 (192.168.1.73)' can't be established.
RSA key fingerprint is 30:97:bc:a0:31:e6:24:50:68:08:6e:82:83:ce:0d:25.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.73' (RSA) to the list of known hosts.
root@192.168.1.73's password: 
known_hosts                                                                                      100%  788     0.8KB/s   00:00    
authorized_keys                                                                                  100%  602     0.6KB/s   00:00    
192.168.1.73                                               [确定]
spawn scp -P22 -r -p /root/.ssh root@192.168.1.74:/root/.ssh
ssh: connect to host 192.168.1.74 port 22: No route to host
lost connection
expect: spawn id exp4 not open
    while executing
"expect eof"
    (file "fenfa_file.exp" line 16)
192.168.1.74                                               [失败]
spawn scp -P22 -r -p /root/.ssh root@192.168.1.75:/root/.ssh
ssh: connect to host 192.168.1.75 port 22: No route to host
lost connection
expect: spawn id exp4 not open
    while executing
"expect eof"
    (file "fenfa_file.exp" line 16)
192.168.1.75                                               [失败]
################### 分发前 ######################
[root@zstest2 ~]# rm -rf .ssh/
[root@zstest2 ~]# ll .ssh     
ls: 无法访问.ssh: 没有那个文件或目录

###### 分发后 ######
[root@zstest2 ~]# ll .ssh
总用量 8
-rw-r--r-- 1 root root 602 3月   6 18:33 authorized_keys
-rw-r--r-- 1 root root 394 3月   6 19:30 known_hosts
################### 分发前 ######################
[root@1073-zstest3 ~]# rm -rf .ssh/
[root@1073-zstest3 ~]# ll .ssh              
ls: 无法访问.ssh: 没有那个文件或目录

###### 分发后 ######
[root@1073-zstest3 ~]# ll .ssh
总用量 8
-rw-r--r-- 1 root root 602 3月   6 18:33 authorized_keys
-rw-r--r-- 1 root root 788 3月   6 19:30 known_hosts
##########################################################3

免密钥连接测试

[root@zstest1 ~]# ll
总用量 56
-rw-------. 1 root root  1061 3月   4 2016 anaconda-ks.cfg
-rwxr-xr-x  1 root root   369 3月   6 19:22 fenfa_file.exp
-rwxr-xr-x  1 root root   231 3月   6 19:18 fenfa_sshkey.sh
-rw-r--r--  1 root root   602 3月   6 17:46 id_dsa.pub
-rw-r--r--. 1 root root 22717 3月   4 2016 install.log
-rw-r--r--. 1 root root  6082 3月   4 2016 install.log.syslog
-rw-r--r--  1 root root    52 3月   6 19:14 ip.list
[root@zstest1 ~]# mv id_dsa .ssh/
[root@zstest1 ~]# ll .ssh/
总用量 12
-rw-r--r-- 1 root root 602 3月   6 18:33 authorized_keys
-rw------- 1 root root 668 3月   6 17:46 id_dsa
-rw-r--r-- 1 root root 788 3月   6 19:30 known_hosts
[root@zstest1 ~]# ssh -p22 root@192.168.1.72 free -m
             total       used       free     shared    buffers     cached
Mem:          1869        837       1032          0        173        487
-/+ buffers/cache:        176       1693
Swap:         2047          0       2047
[root@zstest1 ~]# ssh -p22 root@192.168.1.73 free -m 
              total        used        free      shared  buff/cache   available
Mem:            985         110         712          43         162         708
Swap:          4095           0        4095
[root@zstest1 ~]# 

如果禁止了root远程登录,可以使用sudo的方式结合expect来达到免密码分发文件

实例3:

批量查看远程主机的内存

###########################
show_client_status.sh
-----------------------------------------------
#!/bin/sh
. /etc/init.d/functions
for ip in `cat ip.list`
do
   expect show_expect.exp $ip free #>/dev/null 2>&1
done
------------------------------------------------------
vim show_expect.exp
-------------------------------------------------------
#!/usr/bin/expect
if { $argc !=2 } {
   send_user "USAGE: expect show_expect.exp $host $cmd\n"
   exit
}
# define var
set host [lindex $argv 0]
set cmd  [lindex $argv 1]
set password "123456"
spawn ssh -p22 root@$host $cmd
expect {
   "yes/no"    {send "yes\r";exp_continue}
   "*password" {send "$password\r"}
}
expect eof
exit
--------------------------------------------------
vim ip.list 
----------------------------
192.168.1.72
192.168.1.73
192.168.1.74
192.168.1.75
----------------------------

其中:
1.72上面删除了know_hosts文件中1.72的记录
1.72上面清空了.ssh目录下的文件
1.72上面还保留有公钥文件

#############################################
[root@zstest1 ~]# ./show_client_status.sh 
spawn ssh -p22 root@192.168.1.72 free
The authenticity of host '192.168.1.72 (192.168.1.72)' can't be established.
RSA key fingerprint is a3:35:af:8c:26:1a:f0:b0:9f:0a:5f:7c:c0:6c:92:06.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.72' (RSA) to the list of known hosts.
root@192.168.1.72's password: 
             total       used       free     shared    buffers     cached
Mem:       1914496     857440    1057056          0     177608     499068
-/+ buffers/cache:     180764    1733732
Swap:      2097144          0    2097144
spawn ssh -p22 root@192.168.1.73 free
              total        used        free      shared  buff/cache   available
Mem:        1009280      112988      729788       44680      166504      725288
Swap:       4194300           0     4194300
expect: spawn id exp4 not open
    while executing
"expect eof"
    (file "show_expect.exp" line 16)
spawn ssh -p22 root@192.168.1.74 free
ssh: connect to host 192.168.1.74 port 22: No route to host
expect: spawn id exp4 not open
    while executing
"expect eof"
    (file "show_expect.exp" line 16)
spawn ssh -p22 root@192.168.1.75 free
ssh: connect to host 192.168.1.75 port 22: No route to host
expect: spawn id exp4 not open
    while executing
"expect eof"
    (file "show_expect.exp" line 16)
###########################################
posted @ 2026-03-16 15:01  天生帅才  阅读(1)  评论(0)    收藏  举报
// 百度统计