Linux课程笔记 SSH介绍
一. SSH
1.1 SSH介绍
SSH是Secure Shell Protocol的简称。在进行数据传输之前,SSH先对联机数据包通过加密技术进行加密处理,加密后再进行数据传输。确保了传递的数据安全。SSH是专为远程登录会话和其他网络问题提供安全性的协议。
在默认状态下,SSH协议提供两个服务功能:一个是提供类似telnet远程链接服务器的服务,即上面提到的SSH服务;另一个是类似FTP服务的stp-server,借助SSH协议来传输数据,提供更安全的FTP服务。
特别提醒:SSH客户端还包含一个很有用的远程拷贝命令scp,也是通过ssh协议工作的。
1.2 SSH结构
SSH服务由服务端软件OpenSSH和客户端组成,SSH服务默认使用22端口提供服务。它有两个不兼容的版本,分别是1.x和2.x.
SSH服务端是一个守护进程,它在后台运行并响应来自客户端的连接请求。SSH服务端的进程名为sshd,负责实时监听远程SSH客户端的连接请求,并进行处理,一般包括公共密钥认证、秘钥切换、对称密钥加密和非安全连接等。
ssh客户端包含ssh程序以及像scp、slogin、sftp等应用程序。
ssh的工作机制大致是本地的ssh客户端先发送一个连接请求到远程的ssh服务端,服务端检查连接的客户端发送的数据包和IP地址,如果确认合法,就会发送个秘钥给SSH的客户端,此时客户端本地再将秘钥发回给服务端,自此连接建立。
1.6 ssh的scp命令
scp -P [端口号] -[rp] (用户名@源主机名:)源文件名 (用户名@目标服务器名:)目标文件名
要注意在目标服务器端的权限。
-r:表示拷贝,目录
-p:表示在拷贝前后保持文件或目录属性
[root@test3 ~]# scp -P 19880 scp.test root@192.168.1.5:/scp root@192.168.1.5's password: scp.test 100% 0 0.0KB/s 00:00 |
scp出现Permission denied的两个案例(密码都是正确):
1.root用户 [root@test3 ~]# scp -P 19880 scp.test root@192.168.1.5:/scp root@192.168.1.5's password: Permission denied, please try again. root@192.168.1.5's password: Permission denied, please try again. root@192.168.1.5's password:
#-------------qinbf------------------------# 20 Port 19880 #默认端口 21 PermitRootLogin no #禁止root登陆 22 PermitEmptyPasswords no #禁止空密码登陆 23 UseDNS no #不使用DNS 24 #-------------qinbf------------------------# 将PermitRootLogin no改为yes,然后执行/etc/init.d/sshd restart即可。
2.目标文件夹无权限 [qinbf@test3 ~]$ touch scp.test3 [qinbf@test3 ~]$ scp -P 19880 scp.test3 192.168.1.5:/scp qinbf@192.168.1.5's password: scp: /scp/scp.test3: Permission denied
#查看目标服务器中目标文件夹的权限是root用户创建的,并且是755。对于目标服务器的qinbf用户来说,没有写入(w)的权限。 [root@test4 ~]# ll -d /scp drwxr-xr-x 2 root root 4096 08-27 08:54 /scp
#修改文件夹的权限为777测试 [root@test4 ~]# chmod 777 -R /scp [root@test4 ~]# ll -d /scp drwxrwxrwx 2 root root 4096 08-27 12:38 /scp
#现在源主机即可实现scp [qinbf@test3 ~]$ scp -P 19880 scp.test3 192.168.1.5:/scp qinbf@192.168.1.5's password: scp.test3 100% 0 0.0KB/s 00:00 |
1.7 sftp命令
用法:scp -oPort 端口号 目标服务器名 在sftp中可以使用ls,pwd等linux环境下的命令。
[root@test3 ~]# sftp -oPort=19880 192.168.1.5 Connecting to 192.168.1.5... root@192.168.1.5's password: Remote working directory: /root sftp> ifconfig Invalid command.
[root@test3 ~]# sftp -oPort=19880 qinbf@192.168.1.5 Connecting to 192.168.1.5... qinbf@192.168.1.5's password: sftp> sftp> pwd Remote working directory: /home/qinbf
Windows环境(使用SecureCRT连接SFTP) 注意:此时的客户端是Window(即我本人使用的笔记本电脑),服务端是当前标签所打开的服务器。
测试: put:
sftp> pwd /root/sftp sftp> put D:\sftp.txt Uploading sftp.txt to /root/sftp/sftp.txt 100% 0 bytes 0 bytes/s 00:00:00 sftp> ls sftp.txt sftp> ls -l -rw-r--r-- root root 0 Aug 27, 2013 12:55 sftp.txt
get: sftp> get perl-5.10.0.tar.gz Downloading perl-5.10.0.tar.gz from /root/perl-5.10.0.tar.gz 100% 15229KB 15229KB/s 00:00:01 /root/perl-5.10.0.tar.gz: 15595020 bytes transferred in 1 seconds (15229 KB/s)
|
二.SSHKEY认证生产应用
2.1 具体需求
要求所有服务器在同一用户下odlboy系统用户下,实现A机器从本地分发数据到B、C机器上,在分发过程中不需要B、C的提示系统密码验证,除了分发还需要可以批量查看客户机上的CPU,LOAD,MEM等使用信息。
2.2 实现拓扑
2.3 行前准备
2.3.1 添加系统账号
统一创建系统用户oldboy,设置密码:echo “19880328” | passwd --stdin oldboy
注意:很多公司直接用root用户操作,这样很不规范,很不安全。另外,如果禁止了root远程登录,这个方法就无法用了。
2.3.2 开始部署
因为A服务器为中心分发服务器,所以我们选择在A端建立Public key(锁)与Private key(钥匙),实际上只需要一对秘钥就可以,在哪个机器上都是一样的。
提别提示:在整个方案中,钥匙和锁,仅需要建立一次即可,可以在A、B、C任意机器上来执行,本文选择了在A服务器来生成秘钥对。
[root@test2 ~]# su - oldboy 1. [oldboy@test2 ~]$ ssh-keygen -t dsa #-t 指定生成的秘钥类型:rsa和dsa,对我们来说两个都可以正常使用,区别不大。 Generating public/private dsa key pair. Enter file in which to save the key (/home/oldboy/.ssh/id_dsa): #生成的私钥存放路径 Created directory '/home/oldboy/.ssh'. Enter passphrase (empty for no passphrase): #空密码 Enter same passphrase again: Your identification has been saved in /home/oldboy/.ssh/id_dsa. #私钥存放路径 Your public key has been saved in /home/oldboy/.ssh/id_dsa.pub. #公钥存放路径 The key fingerprint is: 9b:42:69:1d:85:97:ec:79:16:83:13:0a:cf:10:3a:c4 oldboy@test2.localdomain
[oldboy@test2 ~]$ ll -d .ssh drwx------ 2 oldboy oldboy 4096 08-31 10:24 .ssh [oldboy@test2 ~]$ [oldboy@test2 ~]$ ll .ssh/id_dsa* -rw------- 1 oldboy oldboy 668 08-31 10:24 .ssh/id_dsa -rw-r--r-- 1 oldboy oldboy 614 08-31 10:24 .ssh/id_dsa.pub #.ssh目录的权限为700 #.ssh/id_dsa文件的权限为600而且必须为600. #.ssh/id_dsa.pub的权限为644
2. 把公钥(锁)从A服务器考到B、C服务器的家目录各一份 在A端执行以下命令: [oldboy@test2 ~]$ cd .ssh [oldboy@test2 .ssh]$ ssh-copy-id -i id_dsa.pub "-p 19880 oldboy@192.168.1.4" [oldboy@test2 .ssh]$ ssh-copy-id -i id_dsa.pub "-p 19880 oldboy@192.168.1.5" #如果ssh是默认的端口,只需要输入ssh-copy-id -i id_dsa.pub oldboy@192.168.1.4 #文件传输过去之后更名为.ssh/authorized_keys,这是/etc/ssh/sshd/sshd_config配置文件中默认的 #要注意目录和文件的权限 #ssh-copy-id仅适合分发公钥 【测试】 [oldboy@test2 .ssh]$ ssh -p 19880 oldboy@192.168.1.4 /sbin/ifconfig |grep ^eth0 -A 2 eth0 Link encap:Ethernet HWaddr 00:0C:29:B7:92:A0 inet addr:192.168.1.4 Bcast:192.168.1.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:feb7:92a0/64 Scope:Link
现在还有些问题: 1) 免密码登录验证是单向的 2) 基于用户的,最好不要跨用户 3) ssh连接慢的问题 4) 批量分发1000台出示都需要输入一次密码,并且第一次连接要确认 |
操作步骤总结:
- 打开虚拟机,A,B,C,批量添加建立秘钥的的用户oldboy,并设置用户密码
- 在其中一台虚拟机中切换到用户oldboy下,建立密钥对。
命令为:ssh-keygen -t dsa,一直回车
- 在A中,分别执行ssh-copy-id -i .ssh/id_dsa.pub oldboy@B服务器地址,ssh-copy-id -i .ssh/id_dsa.pub oldboy@C服务器地址
执行上面命令把公钥文件拷贝到B,C服务器上,此步可用except批量操作。
- 在A服务器的用户oldboy用户上,测试A到B,A到C是否OK。免密码验证。
2.3.3 分发数据
有两种方法传输数据:(免密码)
- rsync同步数据(但使用的是ssh协议)
[oldboy@test3 ~]$ cd /tmp/oldboy/ [oldboy@test3 oldboy]$ ll 总计 0
[oldboy@test2 .ssh]$ /usr/bin/rsync -avz --progress -e 'ssh -p 19880' /tmp/oldboy/ oldboy@192.168.1.4:/tmp/oldboy sending incremental file list ./ 1.txt 0 100% 0.00kB/s 0:00:00 (xfer#1, to-check=4/6) 2.txt 0 100% 0.00kB/s 0:00:00 (xfer#2, to-check=3/6) 3.txt 0 100% 0.00kB/s 0:00:00 (xfer#3, to-check=2/6) 4.txt 0 100% 0.00kB/s 0:00:00 (xfer#4, to-check=1/6) 5.txt 0 100% 0.00kB/s 0:00:00 (xfer#5, to-check=0/6)
sent 282 bytes received 110 bytes 261.33 bytes/sec total size is 0 speedup is 0.00
[oldboy@test3 oldboy]$ ll 总计 0 -rw-rw-r-- 1 oldboy oldboy 0 2013-08-31 1.txt -rw-rw-r-- 1 oldboy oldboy 0 2013-08-31 2.txt -rw-rw-r-- 1 oldboy oldboy 0 2013-08-31 3.txt -rw-rw-r-- 1 oldboy oldboy 0 2013-08-31 4.txt -rw-rw-r-- 1 oldboy oldboy 0 2013-08-31 5.txt
#先删除本地的数据,然后才能同步删除远程客户端上的数据 [oldboy@test2 oldboy]$ rm -f * [oldboy@test2 oldboy]$ /usr/bin/rsync -avz --delete --progress -e 'ssh -p 19880' /tmp/oldboy/ oldboy@192.168.1.4:/tmp/oldboy sending incremental file list ./ deleting 5.txt deleting 4.txt deleting 3.txt deleting 2.txt deleting 1.txt
sent 52 bytes received 15 bytes 134.00 bytes/sec total size is 0 speedup is 0.00 |
- 使用脚本(基于scp命令)
#!/bin/bash
. /etc/init.d/functions
File="$1" Remote_dir="$2"
if [ $# -ne 2 ];then echo "usage:$0 argv1 argv2" echo "must have two args!" exit fi
for ip in `cat all_iplists.txt` do scp -P 19880 -r -p $File oldboy@$ip:$Remote_dir >/dev/null 2>&1 if [ $? -eq 0 ];then action "$ip is sccessful copied!" /bin/true else action "$ip is failure cpied!" /bin/false fi done
#当参数不等于两个的时候提示: [oldboy@test2 ~]$ /bin/bash fenfa.sh usage:fenfa.sh argv1 argv2 must have two args!
[oldboy@test2 ~]$ /bin/bash fenfa.sh /tmp/oldboy /tmp/oldboy 192.168.1.4 is sccessful copied! [确定] 192.168.1.5 is sccessful copied! [确定] 192.168.1.6 is failure cpied! [失败] |
特别提示:本文免验证登陆是基于普通用户oldboy,如果要实现hosts文件等文件推送工作,需要root权限。
方法大概有三:
法一 把oldboy用户配置成sudo权限用户,然后就可以实现通过sudo功能来实现普通用户只有root才能出路的问题了。处理方法有2:
1) 执行visudo开启Default requiretty参数
2) 使用SSH的如下命令“ssh -t hostame sudo <cmd>”此法为老师推荐的方法
【测试】
以oldboy普通用户的身份把服务器A端的/etc/hosts文件同步到客户端机器B、C上
思路:首先把文件拷贝到客户端机器的家目录,然后再远程执行sudo拷贝到/etc/等目录。 解决:
odlboy ALL=(ALL) NOPASSWD:/usr/bin/rsync
scp -P 19880 -r -p data/hosts oldboy@$192.168.1.4:~
ssh -p 19880 -t oldboy@192.168.1.4 sudo /usr/bin/rsync /etc/hosts /etc/hosts.$(date +%F%M)
ssh -p 19880 -t oldboy@192.168.1.4 sudo /usr/bin/rsync ~/hosts /etc/hosts
将以上的步骤写成脚本: #!/bin/bash . /etc/init.d/functions
File="$1"
if [ $# -ne 1 ];then echo "usage:$0 argv1" echo "must have one args!" exit fi
for ip in `cat all_iplists.txt` do scp -P 19880 -r -p $File oldboy@$ip:~ >/dev/null 2>&1 &&\ ssh -p 19880 -t oldboy@$ip sudo /usr/bin/rsync /etc/hosts /etc/hosts.$(date +%F%M) >/dev/null 2>&1 &&\ ssh -p 19880 -t oldboy@$ip sudo /usr/bin/rsync ~/hosts /etc/host >/dev/null 2>&1 if [ $? -eq 0 ];then action "$ip is sccessful copied!" /bin/true else action "$ip is failure cpied!" /bin/false fi done
|
法二 把scp等命令设置为setuid位
chmod 4755 /bin/cp
命令: ssh -p 19880 -t oldboy@$ip sudo /usr/bin/rsync /etc/hosts /etc/hosts.$(date +%F%M) >/dev/null 2>&1 可以替换成: ssh -p 19880 oldboy@$ip /bin/cp -a /etc/hosts /etc/hosts.$(date +%F%M) >/dev/null 2>&1 #注意:生产环境不要使用此法 |
法三 可以配置root用户的免登录认证,但这又会带来一定的安全问题(虽然可以防火墙封堵SSH端口),而且,我们前面的禁止root远程连接的优化都要打开才行,显然法三更方便,但是从安全角度看,比使用法一sudo方法稍逊一筹。
不管使用什么方法,我们一定要管理好中心分发服务器A,因为它的权限很大。那么如何,管理好中心分发服务器A呢?
1) 取消中心分发服务器的外网IP
2) 开启防火墙禁止SSH对外用户登陆,并仅给某一台后端无外网机器访问。然后这台后端的服务器依然没外网IP。并且仅能通过VPN连接,这样中心分发服务器就相对安全了。
可能大家有疑问,为什么会有很多公司会ssh key + rsync的方式而不用cfengine/puppet方案呢?原因就是:ssh key + rsync技术实现相对简单,实施方便,功能非常强大,如果建立了普通用户加sudo权限或root的免验证连接,那几乎就是无所不能了。cfengine/puppet方案的分发文件功能还不错,但是配置起来相对复杂,尤其是批量管理功能。一般的初学者很难部署和掌控。expect也可以实现分发。
2.4 生产场景备份数据架构
备份数据则需要把中心服务器的私钥(private key)发送到其他机器上,然后把中心服务器的公钥(public key)改名为authorized_keys。需要保持各个文件的权限。
在生产场景使用ssh key备份数据的架构不推荐使用,老师给出4种方案:
方法一:使用rsync服务,在备份服务器部署rsync守护进程,把所有备份节点作为rsync客户端,此方案是老男孩老师在生产环境常用的备份方案,推荐大家使用该方案,对于特别碎的文件,要进行打包,再传输。
方法二:ftp的方式,在备份服务器部署ftp守护,把所有备份节点作为ftp客户端,在本地备份完毕,把数据通过ftp的方式推送到备份服务器上。此法是老师多年前的备份方案读者也可以采用。
方法三:NFS方式,在备份服务器部署NFS服务,把所有备份节点作为NFS客户端,在本地备份完毕(甚至可以直接备份到远端的NFS SEVER上),把数据通过挂载的方式把数据推送到NFS备份服务器上。此法也是个方案,机器数量少(10台以内)时,可以采用。当前,最好不要用此方案。
方法四:scp加ssh key或except交互式的方法备份,作为一个备份思路列在这里,不推荐使用。
2.5 SSH登陆服务器慢的问题解决
SSH登录服务器慢可能的原因:
- 服务器的/etc/resolve.conf中有错误的dns地址,或为内网地址或为不可用的dns地址
- sshd_config配置文件中使用了UseDNS yes(若是注释的,默认也是启用的),改用UseDNS no
- sshd_config配置文件中使用了GSSAPIAuthentication yes,默认是这样的,改成GSSAPIAuthentication no或者注释掉。
解决:
1. 在server上/etc/hosts文件中把你本机的ip和hostname加入
2. 在server上/etc/sshd_config文件中修改或加入UseDNS=no
3. 修改server上/etc/nsswitch.conf中hosts为hosts:files
4. authentication gssapi-with-mic也有可能出现问题,在server上/etc/ssh/sshd_config文件中修改GSSAPIAuthentication no。然后重启ssh进程,使配置生效。
2.6 批量分发、部署、管理的十种解决方案
1) secboy
老男孩老师自己开发的分发工具
2) SecureCRT
打开多个标签页,使用交互窗口,使一条命令在多窗口执行
3) ssh免秘钥的方案
1通过root用户直接建立秘钥认证
2普通用户建立秘钥(需要时通过sudo提权操作)
3普通用户建立秘钥(setuid对命令提权操作)
4) expect
这里可以直接用expect做批量分发管理,省了秘钥认证。交互式命令(结合rsync+scp+sudo),可实现普通用户,root用户之间文件分发,批量部署及配置管理,查看信息
5) puppet
部署复杂
6) cfengine
部署复杂
7) rsync
分发服务器上部署rsync daemon,然后客户机上通过定时任务抓取的方式
8) lsyncd(sersync)
触发式实时的抓取或推送
9) http方式
http server +客户机cron,实现文件分发
10) nfs网络文件系统
把要分发的文件放在NFS上,然后在客户端通过定时任务,复制到需要的目录。
推荐:在服务器数量不多的情况下,可以考虑SecureCRT。服务器较多的情况下,考虑ssh key 免秘钥或者rsync的方式
三 Expect非交互脚本语言详解
3.1 Expect介绍
Expect是基于Tcl的相对简单的一个免费的脚本编程工具语言,用来实现自动和交互式任务程序进行通信,无需人手工干预。比如SSH、FTP等,这些程序正常情况下都需要手工与它们进行交互,而使用Expect就可以模拟人手工交互的过程,实现自动的和远端的程序交互,从而达到自动化运维的目的。
3.2 Expect程序工作流程
Expect的工作流程可以理解为,spawn启动进程-->Expect期待关键字-->send向进程发送字符-->退出结束
3.3 首先,配置好yum安装源,并且确保机器可以上网,然后执行yum install Expect -y即可安装Expect软件
3.4 Expect的一个实例
#!/usr/bin/Expect
spawn ssh -p 19880 root@192.168.1.4 /sbin/ifconfig eth0 set timeout 60 expect “*Password:” send “123456\n” expect eof exit
#使用expect 脚本名来调用这个脚本 #这个脚本的权限最好是设置为700,这样子即使脚本里面的密码是明文的,但是其他用户也看不到 |
3.5 Expect语法
命令的使用语法如下:
命令 [选项] 参数
3.5.1 spawn
spawn命令是Expect的初始命令,它用于启动一个进程,之后所有expect操作都在这个进程中进行。
spawn ssh root@192.168.1.4
在spawn命令后面,直接加上要启动的进程、命令等信息,除此之外,spawn还支持其它选项:
-open 启动文件进程
-ignore 忽略某些新号
3.5.2 expect
使用方法:
expect 表达式 动作 表达式 动作
expect命令用于等候一个相匹配内容的输出,一旦匹配上就执行expect后面的动作或命令,这个命令接受几个特有的参数,用的最多的就是-re,表示使用正则表达式匹配,使用起来就像这样:
spawn ssh root@192.168.1.4 expect “password:” {send “123456\r”} |
从上面的例子可以看出,expect是依附于spawn命令的,当执行ssh命令后,expect就匹配命令执行后的关键字password:如果匹配到关键字就会执行后面包含在{}括号中的send或exp_send动作,匹配以及动作可以放在第二行,这样就不需要使用括号了。就像下面这样,实际完成的功能与上面是一样的:
spawn ssh root@192.168.1.179 expect “password:” send “oldboy\r” |
expect命令还有一种用法,它可以在一个expect匹配中多次匹配关键字,并给出处理动作,只需要将关键字放在一个大括号中就可以了,当然还要exp_continue
spawn ssh root@192.168.1.4 expect { “yes/no” { exp_send “yes\r”;exp_continue } “password:” { exp_send “123456\r” } |
3.5.3 exp_send和send
send命令有几个可用的参数:
-i 指定spawn_id,这个参数用来向不同spawn_id的进程发送命令,是进行多程序控制的关键参数。
-s s代表slowly,也就是控制发送的速度,这个参数使用的时候 与expect中的变量send_slow相关联
3.5.4 exp_continue
[qinbf@test3 ~]$ cat qinbf01.exp #!/usr/bin/expect
spawn ssh -p19880 qinbf@192.168.1.5 /sbin/ifconfig eth0 set timeout 60 expect { "yes/no" {exp_send "yes\r";exp_continue} "password:" {exp_send "qbf19880328\r"} } expect eof exit |
执行结果: [qinbf@test3 ~]$ expect qinbf01.exp spawn ssh -p19880 qinbf@192.168.1.5 /sbin/ifconfig eth0 The authenticity of host '192.168.1.5 (192.168.1.5)' can't be established. RSA key fingerprint is 72:cb:b3:8c:ff:6a:fc:50:5b:c9:7e:51:e7:0a:ab:3a. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.1.5' (RSA) to the list of known hosts. qinbf@192.168.1.5's password: eth0 Link encap:Ethernet HWaddr 00:0C:29:D1:8D:B5 inet addr:192.168.1.5 Bcast:192.168.1.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fed1:8db5/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8207 errors:0 dropped:0 overruns:0 frame:0 TX packets:7799 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:3038085 (2.8 MiB) TX bytes:761074 (743.2 KiB) |
当有多个匹配关键字时,使用exp_continue命令,会继续匹配下一个关键字。否则就停止匹配,这个与C语言的continue命令类似。
就算匹配的第一个关键字不存在,这个脚本可以直接匹配第二个个关键字。
[qinbf@test3 ~]$ ssh -p 19880 qinbf@192.168.1.5 /sbin/ifconfig eth0 qinbf@192.168.1.5's password:
[qinbf@test3 ~]$ expect qinbf01.exp spawn ssh -p19880 qinbf@192.168.1.5 /sbin/ifconfig eth0 qinbf@192.168.1.5's password: eth0 Link encap:Ethernet HWaddr 00:0C:29:D1:8D:B5 inet addr:192.168.1.5 Bcast:192.168.1.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fed1:8db5/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:11091 errors:0 dropped:0 overruns:0 frame:0 TX packets:10952 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:3327352 (3.1 MiB) TX bytes:1082426 (1.0 MiB) |
缺少exp_continue关键字的执行结果
[qinbf@test3 ~]$ expect qinbf01.exp spawn ssh -p19880 qinbf@192.168.1.5 /sbin/ifconfig eth0 The authenticity of host '192.168.1.5 (192.168.1.5)' can't be established. RSA key fingerprint is 72:cb:b3:8c:ff:6a:fc:50:5b:c9:7e:51:e7:0a:ab:3a. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.1.5' (RSA) to the list of known hosts. qinbf@192.168.1.5's password: #缺少exc_continue关键字,只匹配第一个关键字便停止匹配 |
3.5.5 send_user
send_user命令用来把后面的参数输出到标准输出中去,默认的send、exc_pend命令都是将参数输出到程序中去,用起来就像这样:
send_user “Please input passwd: ”
这个语句就可以在标准输出中打印Please input passwd字符了
if { $argc !=3 } { send_user “usage: expect scp-expect.exp file host dir\n” exit }
|
3.6 expect变量
expect中有很多有用的变量,它们的使用方法与TCL语言中的变量相同,比如:
set 变量名 变量值 #设置变量的方法
puts $变量名 #读取变量的方法
#define var set file [lindex $argv 0] set host [lindex $argv 1] set dir [lindex $argv 2] set password “123456” |
3.7 expect关键字
eof (end-of-file)
eof关键字用于匹配结束符,比如文件的结束符、FTP传输停止等情况,在这个关键字后跟上动作做进一步的控制,特别是FTP交互操作方面,它的作用很大。用一个例子来看看:
spawn ftp anonymous@192.168.1.5 expect { “password:” {exp_send “123456\r”} eof {ftp connect close} } interact {} |
timeout
timeout是expect中的一个重要变量,它是一个全局性的时间控制开关,你可以通过为这个变量赋值来规定整个expect操作的时间,注意这个变量时服务于expect全局的,它不会纠缠于某一条命令,即使命令没有任何错误,到时间仍然会激活这个变量,但到达以后除了激活一个开关之外不会做其他的事情。
set timeout 60 spawn ssh -p 19880 qinbf@192.168.1.4 expect “password:” { exp_send “word\r”} expect timeout {puts “Expect was timeout”;return} |
上面的处理中,首先timeout变量设置为60秒,当出现问题的程序可能会停止下来,只要到60秒,就会激活下面的timeout动作。
在另一种expect格式中,我们还有一种设置timeout变量的方法,看看下面的例子:
spawn ssh qinbf@192.168.1.4 expect { -timeout 60 -re “password” { exp_send “word\r”} -re “TopsecOS# { } timeout {puts “expect was timeout”;return} } 在expect命令中间加上一个小横杠,也可以设置timeout变量,设置0表示立即超时,-1 表示永不超时 |
设置参数
#!/usr/bin/expect
if { $argc != 3 } { send_user "usage:expect qinbf02.exp file host dir\n" exit }
set file [lindex $argv 0] set host [lindex $argv 1] set dir [lindex $argv 2] set password "qbf19880328"
spawn scp -P19880 -p $file qinbf@$host:$dir set timeout 60 expect { "yes/no" {exp_send "yes\r";exp_continue} "password:" {exp_send "qbf19880328\r"} } expect eof exit
执行结果: [qinbf@test3 ~]$ expect qinbf02.exp /data/hosts 192.168.1.5 ~ spawn scp -P19880 -p /data/hosts qinbf@192.168.1.5:/home/qinbf qinbf@192.168.1.5's password: hosts |
exit -onexit是结束expect后传递其他信息,比如输出结尾信息或者删除临时文件
exit -onexit { send_user “”Qinbf say good bye” }
3.8 生产实战
- 使用expect实现批量分发/etc/hosts文件
#本案例使用的是普通用户qinbf [qinbf@test3 ~]$ cat qinbf02.exp #这里是expect文件 #!/usr/bin/expect
if { $argc != 3 } { send_user "usage:expect qinbf02.exp file host dir\n" exit }
set file [lindex $argv 0] set host [lindex $argv 1] set dir [lindex $argv 2] set password "qbf19880328"
spawn scp -P19880 -p $file qinbf@$host:$dir set timeout 30 expect { "yes/no" {exp_send "yes\r";exp_continue} "password:" {exp_send "$password\r";exp_continue} timeout {puts "Expect was timeout,Please contact Qinbf!\n";return} } spawn ssh -p19880 -t qinbf@$host sudo /usr/bin/rsync ~/hosts /etc/hosts set timeout 30 expect { "password:" {exp_send "$password\r"} timeout {puts "Expect was timeout,Please contact Qinbf!\n";return} } expect eof exit |
注意:由于普通用户qinbf不能修改/etc/hosts文件,所以需要对rsync配置sudo权限。在这里的expect文件就有了两个命令,那么就需要输入两次密码,所以需要分别在expect 后面匹配两次密码,然后推送两次密码。特别注意,在第一次匹配password之后,还是需要加上exc_continue,否则不会匹配下一个命令。
[qinbf@test3 ~]$ cat scp.test1 #这是执行分发的脚本 #!/bin/bash . /etc/init.d/functions
for ip in `cat all_iplist.txt`
do expect qinbf02.exp /data/hosts $ip ~ >/dev/null 2>&1 #这里是省去了expect文件的输出信息
if [ $? -eq 0 ];then action "$ip" /bin/true #用[确定]或[失败]来返回执行结果 else action "$ip" /bin/false fi done
[qinbf@test3 ~]$ cat all_iplist.txt #这是ip地址列表 192.168.1.3 192.168.1.5 10.1.1.1
执行结果: [qinbf@test3 ~]$ sh scp.test1 192.168.1.3 [ 确定 ] 192.168.1.5 [ 确定 ] 10.1.1.1 [ 失败 ] |
3.8.2 用expect分发公钥,实现免密码登陆
注意到上一个脚本需要输入两次密码,如果使用expect分发了公钥,那么就不需要发送两次密码,比较方便。
cat fenfa_sshkey.exp #!/usr/bin/expect
if { $argc != 2 } { send_user "usage:expect fenfa_sshkey.exp file host\n" exit }
set file [lindex $argv 0] set host [lindex $argv 1] set password "19880328"
spawn ssh-copy-id -i $file "-p 19880 disdata@$host"
expect { "yes/no" {exp_send "yes\r";exp_continue} "password:" {exp_send "$password\r" } } expect eof
exit -onexit { send_user "disdata say goodbye to you!\n" }
cat fenfa_sshkey.sh #!/bin/bash
. /etc/init.d/functions
for ip in `cat iplist` do expect fenfa_sshkey.exp ~/.ssh/id_dsa.pub $ip >/dev/null 2>&1 if [ $? -eq 0 ];then action "$ip" /bin/true else action "$ip" /bin/false fi done
cat iplist 192.168.1.201 192.168.1.202 192.168.1.203 192.168.1.204 192.168.1.205
最后只需要在该脚本的目录下执行sh fenfa_sshkey.sh即可。
最关键的命令其实还是” ssh-copy-id -i $file "-p 19880 disdata@$host"”
|