附录 实现用Shell脚本自动登录其他机器并执行任务

用Shell脚本来实现SSH的远程登录与密码输入还有免密钥登录是一件非常重要的工作,因为我们后期的远程分布式安装脚本就依赖于这种技术.

首先我们回顾一下,我会用涉及到那些技术与命令.

  • 通过Python实现
  • 通过Shell实现

通过Shell来实现

免密钥登录实现过程-基础

涉及到的命令与技术:

  • ssh-key-gen 创建公钥和密钥
  • ssh-copy-id 把本地主机的公钥复制到远程主机的authorized_keys文件上,也会给远程主机的用户主目录(home)和~/.ssh, 和~/.ssh/authorized_keys设置合适的权限
  • | IP地址 | 说明 | | -- | -- | | 192.168.1.174 | 通过这台机器,免密钥ssh到178上面 | | 192.168.1.178 | mysql主机 |

步骤1: 用 ssh-key-gen 在本地主机上创建公钥和密钥

chu888chu888@ubuntul-dev:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/chu888chu888/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/chu888chu888/.ssh/id_rsa.
Your public key has been saved in /home/chu888chu888/.ssh/id_rsa.pub.
The key fingerprint is:
11:48:64:43:d3:20:2f:d1:b9:ff:c9:8d:5d:a4:d5:57 chu888chu888@ubuntul-dev
The key's randomart image is:
+--[ RSA 2048]----+
|    o=O=.        |
|     =+o..      E|
|    . ...      ..|
|     ..  .    o o|
|       .S    +  .|
|        .   . .  |
|         o = .   |
|          = o    |
|                 |
+-----------------+

步骤2: 用 ssh-copy-id 把公钥复制到远程主机上

chu888chu888@ubuntul-dev:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
The authenticity of host '192.168.1.178 (192.168.1.178)' can't be established.
ECDSA key fingerprint is ba:a5:10:ad:f9:48:bf:d7:57:51:cc:1f:1b:e7:e6:63.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

[注: ssh-copy-id 把公钥分发即追加到远程主机的 .ssh/authorized_key 上.]

步骤3: 直接登录远程主机

chu888chu888@ubuntul-dev:~$ ssh [email protected]
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.19.0-25-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Wed Jun 22 22:20:48 CST 2016

  System load:  0.0                Processes:           147
  Usage of /:   43.7% of 18.32GB   Users logged in:     1
  Memory usage: 17%                IP address for eth0: 192.168.1.178
  Swap usage:   0%

  Graph this data and manage this system at:
    https://landscape.canonical.com/

0 packages can be updated.
0 updates are security updates.

Last login: Wed Jun 22 22:07:15 2016
chu888chu888@ubuntu-mysql:~$ 

[注: SSH 不会询问密码.] 
[注: 你现在已经登录到了远程主机上]

基于expect的实现

使用Linux的程序员对输入密码这个举动一定不陌生,在Linux下对用户有严格的权限限制,干很多事情越过了权限就得输入密码,比如使用超级用户执行命令,又比如ftp、ssh连接远程主机等等,如下图:

那么问题来了,在脚本自动化执行的时候需要输入密码怎么办?比如你的脚本里有一条scp语句,总不能在脚本执行到这一句时手动输入密码吧。

今天要说的是在脚本里自动输入密码,我们可以想象下,更优雅的方式应该是在脚本里自己配置密码,当屏幕交互需要输入时自动输入进去,要达到这样的效果就需要用到expect。

安装

CentOS下安装命令,如下

sudo yum install expect

Ubuntu下安装命令,如下

sudo apt-get install expect

至于Mac用户,可以通过homebrew安装

brew install expect

我们写一个简单的脚本实现scp拷贝文件,在脚本里配置密码,保存为scp.exp如下:

#!/usr/bin/expect  
#设置超时时间
set timeout 20  

if { [llength $argv] < 2} {  
    puts "Usage:"  
    puts "$argv0 local_file remote_path"  
    exit 1  
}  

set local_file [lindex $argv 0]  
set remote_path [lindex $argv 1]  
set passwd 123456  

set passwderror 0  

spawn scp $local_file $remote_path  

expect {  
    "*assword:*" {  
        if { $passwderror == 1 } {  
        puts "passwd is error"  
        exit 2  
        }  
        set timeout 1000  
        set passwderror 1  
        send "$passwd\r"  
        exp_continue  
    }  
    "*es/no)?*" {  
        send "yes\r"  
        exp_continue  
    }  
    timeout {  
        puts "connect is timeout"  
        exit 3  
    }  
}

注意,第一行很重要,通常我们的脚本里第一行是#!/bin/bash,而这里是你机器上expect程序的路径,说明这段脚本是由expect来解释执行的,而不是由bash解释执行,所以代码的语法和shell脚本也是不一样的,其中set passwd your_passwd设置成你自己的密码,然后执行如下命令

./scp.exp ./local_file user@host:/xx/yy/

执行前确保scp.exp有执行权限,第一个参数为你本地文件,第二个为远程主机的目录,运行脚本如果报错“connect is timeout”,可以把超时设长一点,第二行set timeout 20可以设置超时时间,单位是秒。脚本执行效果如下

还能做什么 细心的同学一定发现了,其实expect提供的是和终端的一种交互机制,输入密码只是其中一种应用形式,只要是在终端阻塞需要输入时,都可以通过expect脚本完成自动输入,比如前面脚本里配置了两种交互场景,一种是终端提示"password:"时输入密码,还有一种是提示"yes/no)?"时输入“yes”,如果和远程主机是第一次建立连接,执行scp.exp脚本效果是这样的

所以我们可以根据终端的提示来配置输入命令,这样就能达到了自动化的效果。至于处理其它交互场景,只需要照着上面的脚本依葫芦画瓢就行了

expect的使用

Expect是Unix系统中用来进行自动化控制和测试的软件工具,由DonLibes制作,作为Tcl脚本语言的一个扩展,应用在交互式软件中如telnet,ftp,Passwd,fsck,rlogin,tip,ssh等等。该工具利用Unix伪终端包装其子进程,允许任意程序通过终端接入进行自动化控制;也可利用Tk工具,将交互程序包装在X11的图形用户界面中。

简单地说,expect是一个工具,可以根据用户设定的规则和系统进程进行自动化交互,例如远程登陆的密码输入、自动化的执行远程命令。

1 命令行参数

$argc,$argv 0,$argv 1 ... $argv n  
argc表示命令行参数个数,后面分别表示各个参数项,0表示第一个参数,1表示第二个参数,以此类推,可以通过lindex获取对应参数值(lindex $argv 0)。

if {$argc < 2} {
puts stdout "$argv0 err params\n"
exit 1
}

2 输入输出

puts stderr "Usage: $argv0 login passwaord.n "
puts “hello world”
puts stdout "1234"

3 嵌套命令

set user [lindex $argv 0]
set password [lindex $argv 1]
把命令行第一个参数赋给user,第二个参数赋给password

4 命令调用

spawn ssh [email protected]#36000
spawn启动一个进程,进程执行ssh命令,程序后面可以通过expect/send和新起的进程进行交互。

5 函数定义和调用

proc do_console_login {login pass} { 

}
do_console_login $user $password

6 变量赋值

set user “arlen ”
set int0 1

7 循环

while ($done) { 

}

8 条件分支Switch

switch -- $var { 

0 {

  } 

1 { 

  } 

2 { 

  } 

}

常用指令

1. [#!/usr/bin/expect] 
  这一行告诉操作系统脚本里的代码使用那一个shell来执行。这里的expect其实和linux下的bash、windows下的cmd是一类东西。 注意:这一行需要在脚本的第一行。

2. [set timeout 30]
  设置超时时间的,单位是:秒 

3. [spawn ssh [email protected]#36000]
  spawn是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。它主要的功能是给ssh运行进程加个壳,用来传递交互指令。 

4. [expect "assword: "]
  这里的expect也是expect的一个内部命令,expect的shell命令和内部命令是一样的,但不是一个功能。这个命令的意思是判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间后返回,这里等待时长就是前面设置的30秒。

5. [send "$password\n"] 
  这里就是执行交互动作,与手工输入密码的动作等效。

6. [interact] 
  执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。

expect例子

不能按照习惯来用sh autosu.sh来这行expect的程序,会提示找不到命令,如下:

#!/usr/bin/expect
#切换用户
spawn su - oracle
#提示让输入密码
expect "password:"
#输入oracle的密码
send "99billzy\r"
#操作完成
interact

results matching ""

    No results matching ""