shell 脚本 交互 expect

时间:2021-02-27 21:37:42

zz https://www.cnblogs.com/lixigang/articles/4849527.html

一、概述

        我们通过Shell可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如telnet服务器等进行交互的功能。而expect就使用来实现这种功能的工具。

       expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。expect需要Tcl编程语言的支持,要在系统上运行expect必须首先安装Tcl。

二、expect的安装

expect是在Tcl基础上创建起来的,所以在安装expect前我们应该先安装Tcl。

(一)Tcl 安装

主页: http://www.tcl.tk
下载地址: http://www.tcl.tk/software/tcltk/downloadnow84.tml
1.下载源码包

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. wget http://nchc.dl.sourceforge.net/sourceforge/tcl/tcl8.4.11-src.tar.gz  


2.解压缩源码包

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. tar xfvz tcl8.4.11-src.tar.gz  


3.安装配置

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. cd tcl8.4.11/unix  
  2. ./configure --prefix=/usr/tcl --enable-shared  
  3. make  
  4. make install  

注意:
1、安装完毕以后,进入tcl源代码的根目录,把子目录unix下面的tclUnixPort.h copy到子目录generic中。

2、暂时不要删除tcl源代码,因为expect的安装过程还需要用。

(二)expect 安装 (需Tcl的库)

主页: http://expect.nist.gov/
1.下载源码包

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. wget http://sourceforge.net/projects/expect/files/Expect/5.45/expect5.45.tar.gz/download  


2.解压缩源码包

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. tar xzvf expect5.45.tar.gz  


3.安装配置

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. cd expect5.45  
  2. ./configure --prefix=/usr/expect --with-tcl=/usr/tcl/lib --with-tclinclude=../tcl8.4.11/generic  
  3. make  
  4. make install  
  5. ln -s /usr/tcl/bin/expect /usr/expect/bin/expect  


三、Expect工作原理

       从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。

       Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。例如下面的 Chat脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应 sillyme。

引用:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. Login: somebody Password: sillyme  

Expect最简单的脚本操作模式本质上和Chat脚本工作模式是一样的。

例子:

1、实现功能

下面我们分析一个响应chsh命令的脚本。我们首先回顾一下这个交互命令的格式。

假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. # chsh chavez   
  2. Changing the login shell for chavez   
  3. Enter the new value, or press return for the default   
  4. Login Shell [/bin/bash]: /bin/tcsh   
  5. #  

可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录shell。我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。


2、实现自动执行

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect  
  2. # Change a login shell to tcsh  
[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. set user [lindex $argv 0]  
  2. spawn chsh $user  
  3. expect "]:"  
  4. send "/bin/tcsh "   
  5. expect eof  
  6.   
  7. exit   

说明:

(1)首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。

(2)程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。

(3)第二个参数使用expect的spawn命令来启动脚本和命令的会话,这里启动的是chsh命令,实际上命令是以衍生子进程的方式来运行的。

(4)随后的expect和send命令用来实现交互过程。脚本首先等待输出中出现]:字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征 字符串往往是等待输入的最后的提示符的特征信息)。对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和 一个回车符给chsh命令。最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。

3、决定如何响应

       系统管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。我们可以通过后面的例子看到expect可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。

     下面的例子是一个更复杂的expect-send例子:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. expect -re "\[(.*)]:"  
  2. if {$expect_out(1,string)!="/bin/tcsh"} {  
  3. send "/bin/tcsh" }  
  4. send " "  
  5. expect eof   

说明:

(1)第一个expect命令现在使用了-re参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于expect和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。

(2)当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。如果不是则发送/bin/tcsh给chsh命令作为输入,如果是则仅仅发送一个回车符。这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。

(3)在一个正则表达时中,可以在()中包含若干个部分并通过expect_out数组访问它们。各个部分在表达式中从左到右进行编码,从1开始(0包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。

4、使用超时

       下一个expect例子中将阐述具有超时功能的提示符函数。这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect  
  2. # Prompt function with timeout and default.  
  3.   
  4. #脚本的第一部分首先是得到运行参数并将其保存到内部变量中  
  5. set prompt [lindex $argv 0]  
  6. set def [lindex $argv 1]   
  7. set response $def  
  8. set tout [lindex $argv 2]   
  9.   
  10. send_tty "$prompt: "  
  11. #send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格  
  12. set timeout $tout  
  13. #set timeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。   
  14. expect " " {  
  15. set raw $expect_out(buffer)  
  16.   
  17. # remove final carriage return  
  18. set response [string trimright "$raw" " "]  
  19. }  
  20. if {"$response" == "} {set response $def}  
  21. send "$response "  
  22.   
  23. # Prompt function with timeout and default.  
  24. set prompt [lindex $argv 0]  
  25. set def [lindex $argv 1]   
  26. set response $def  
  27. set tout [lindex $argv 2]   


说明:

(1)send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格。

(2)set timeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。

(3)然后expect命令就等待输出中出现回车字符。如果在超时之前得到回车符,那么set命令就会将用户输入的内容赋值给变脸raw。随后的命令将用户输入内容最后的回车符号去除以后赋值给变量response。

(4)如果response中内容为空则将response值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。最后send命令将response变量的值加上回车符发送给标准输出。

注意:

(1)该脚本没有使用spawn命令。

(2)该expect脚本会与任何调用该脚本的进程交互。

(3)如果该脚本名为prompt,那么它可以用在任何C风格的shell中。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. % set a='prompt "Enter an answer" silence 10'   
  2. Enter an answer: test   
  3.   
  4. % echo Answer was "$a"   
  5. Answer was test   

prompt设定的超时为10秒。如果超时或者用户仅仅输入了回车符号,echo命令将输出

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. Answer was "silence"   

 

5、一个更复杂的例子

       下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。这个例子用来实现发送write命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect  
  2. # Write to multiple users from a prepared file  
  3. # or a message input interactively  
  4.   
  5. if {$argc<2} {  
  6. send_user "usage: $argv0 file user1 user2 ... "  
  7. exit  
  8. }   
  9. #send_user命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。   
  10.   
  11. set nofile 0  
  12.   
  13. # get filename via the Tcl lindex function  
  14. set file [lindex $argv 0]  
  15. if {$file=="i"} {   
  16. set nofile 1   
  17. } else {   
  18.   
  19. # make sure message file exists  
  20. if {[file isfile $file]!=1} {   
  21. send_user "$argv0: file $file not found. "  
  22. exit }}   
  23.   
  24. ####################################################  
  25. #(1)这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。   
  26. #(2)变量file被设置为脚本的第一个参数的值,是通过一个Tcl函数lindex来实现的,该函数从列表/数组得到一个特定的元素。[]用来实现将函数lindex的返回值作为set命令的参数。   
  27. #(3)如果脚本的第一个参数是小写的"i",那么变量nofile被设置为1,否则通过调用Tcl的函数isfile来验证参数指定的文件存在,如果不存在就报错退出。   
  28. #(4)可以看到这里使用了if命令来实现逻辑判断功能。该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。if条件为false时则运行else后的程序块。   
  29. #######################################################  
  30.   
  31. set procs {}  
  32. # start write processes  
  33.   
  34. for {set i 1} {$i<$argc}  
  35. {incr i} {  
  36. spawn -noecho write   
  37. [lindex $argv $i]   
  38. lappend procs $spawn_id  
  39. }   
  40. #######################################################################################  
  41. #(1)这一部分使用spawn命令来启动write进程实现向用户发送消息.  
  42. #(2)这里使用了for命令来实现循环控制功能,循环变量首先设置为1,然后因此递增。循环体是最后的{}的内容。  
  43. #(3)这里我们是用脚本的第二个和随后的参数来spawn一个write命令,并将每个参数作为发送消息的用户名。  
  44. #(4)lappend命令使用保存每个spawn的进程的进程ID号的内部变量$spawn_id在变量procs中构造了一个进程ID号列表。  
  45. ###################################################################################################  
  46.   
  47. if {$nofile==0} {  
  48. setmesg [open "$file" "r"]  
  49. } else {  
  50. send_user "enter message,  
  51. ending with ^D: " }   
  52. #最后脚本根据变量nofile的值实现打开消息文件或者提示用户输入要发送的消息。   
  53.   
  54. set timeout -1  
  55. while 1 {  
  56. if {$nofile==0} {  
  57. if {[gets $mesg chars] == -1} break  
  58. set line "$chars "   
  59. } else {  
  60. expect_user {  
  61. -re " " {}  
  62. eof break }  
  63. set line $expect_out(buffer) }  
  64.   
  65. foreach spawn_id $procs {   
  66. send $line }  
  67. sleep 1}  
  68. exit   
  69. ########################################################  
  70. #(1)这段代码说明了实际的消息文本是如何通过无限循环while被发送的。  
  71. #(2)while循环中的if判断消息是如何得到的。在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时while循环也就结束了。(break命令实现终止循环) 。   
  72. #(3)在交互模式下,expect_user命令从用户接收消息,当用户输入ctrl+D时结束输入,循环同时结束。 两种情况下变量$line都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。   
  73. #(4)foreach循环遍历spawn的所有进程,这些进程的ID号都保存在列表变量$procs中,实现分别和各个进程通信。send命令组成了foreach的循环体,发送一行消息到当前的write进程。while循环的最后是一个sleep命令,主要是用于处理非交互模式情况下,以确保消息 不会太快的发送给各个write进程。当while循环退出时,expect脚本结束。   
  74. ########################################################  

 

四、使用expect脚本的小窍门

1、使用“-c”选项,从命令行执行expect脚本

expect可以让你使用“-c”选项,直接在命令行中执行它,如下所示:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect -c 'expect "\n" {send "pressed enter\n"}  
  2.   
  3. pressed enter  
  4. $  

如果你执行了上面的脚本,它会等待输入换行符(\n)。按“enter”键以后,它会打印出“pressed enter”这个消息,然后退出。

2、使用“-i”选项交互地执行expect脚本

使用“-i”选项,可以通过来自于标准输入的读命令来交互地执行expect脚本。如下所示:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect -i arg1 arg2 arg3  
  2. expect1.1>set argv  
  3. arg1 arg2 arg3  
  4. expect1.2>  

正常情况下,当你执行上面的expect命令的时候(没有“-i”选项),它会把arg1当成脚本的文件名,所以“-i”选项可以让脚本把多个参数当成一个连续的列表。

当你执行带有“-c”选项的expect脚本的时候,这个选项是十分有用的。因为默认情况下,expect是交互地执行的。

3、当执行expect脚本的时候,输出调试信息

当你用“-d”选项执行代码的时候,你可以输出诊断的信息。如下所示:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ cat sample.exp  
  2. # !/usr/bin/expect -fexpect "\n";send "pressed enter";$ expect -d sample.expexpect version 5.43.0argv[0] = expect  argv[1] = -d  argv[2] = sample.expset argc 0set argv0 "sample.exp"set argv ""executing commands from command file sample.exp  
  3.   
  4. expect: does "" (spawn_id exp0) match glob pattern "\n"? no  
  5.   
  6. expect: does "\n" (spawn_id exp0) match glob pattern "\n"? yes  
  7. expect: set expect_out(0,string) "\n"  
  8. expect: set expect_out(spawn_id) "exp0"  
  9. expect: set expect_out(buffer) "\n"  
  10. send: sending "pressed enter" to { exp0 pressed enter}  

4、使用“-D”选项启动expect调试器

“-D”选项用于启动调试器,它只接受一个布尔值的参数。这个参数表示提示器必须马上启动,还是只是初始化调试器,以后再使用它。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect -D 1 script  

“-D”选项左边的选项会在调试器启动以前被处理。然后,在调试器启动以后,剩下的命令才会被执行。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect -c 'set timeout 10' -D 1 -c 'set a 1'  
  2. 1: set a 1  
  3. dbg1.0>  

5、逐行地执行expect脚本

通常,expect会在执行脚本之前,把整个脚本都读入到内存中。“-b”选项可以让expect一次只读取脚本中的一行。当你没有写完整个脚本的时候,这是十分有用的,expect可以开始执行这个不完整的脚本,并且,它可以避免把脚本写入到临时文件中。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect -b  

6、让expect不解释命令行参数

你可以使用标识符让expect不解释命令行参数。

你可以像下面这样的读入命令行参数:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ cat  print_cmdline_args.exp  
  2. #!/usr/bin/expect  
  3. puts 'argv0 : [lindex $argv 0]';  
  4. puts 'argv1 : [lindex $argv 1]';  

当执行上面的脚本的时候,会跳过命令行选项,它们会被当成参数(而不是expect选项),如下所示:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. $ expect print_cmdline_args.exp -d -c  
  2. argv0 : -d  
  3. argv1 : -c  


四、expect简单例子

为了更好理解except脚本几个简单参数,我们再举一个简单的例子:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect     
  2. set timeout 30     
  3. spawn ssh -l username 192.168.1.1     
  4. expect "password:"     
  5. send "ispass\r"     
  6. interact    


说明:   
1. [#!/usr/bin/expect]   
    这一行告诉操作系统脚本里的代码使用那一个shell来执行。这里的expect其实和linux下的bash、windows下的cmd是一类东西。   
注意:这一行需要在脚本的第一行。   
   
2. [set timeout 30]     
    基本上认识英文的都知道这是设置超时时间的,现在你只要记住他的计时单位是:秒   
    
3. [spawn ssh -l username 192.168.1.1]    
    spawn是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。所以不要用 “which spawn“之类的命令去找spawn命令。好比windows里的dir就是一个内部命令,这个命令由shell自带,你无法找到一个dir.com 或 dir.exe 的可执行文件。    
    它主要的功能是给ssh运行进程加个壳,用来传递交互指令。   
   
4. [expect "password:"]   
    这里的expect也是expect的一个内部命令,有点晕吧,expect的shell命令和内部命令是一样的,但不是一个功能,习惯就好了。这个命令的意思是判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间后返回,这里等待时长就是前面设置的30秒   
   
5. [send "ispass\r"]   
    这里就是执行交互动作,与手工输入密码的动作等效。   
    温馨提示: 命令字符串结尾别忘记加上 “\r”,如果出现异常等待的状态可以核查一下。   
   
6. [interact]    
    执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。如果你只是登录过去执行一段命令就退出,可改为[expect eof]  


五、expect实用案例

1、expect实现ssh无密钥登陆

说明:用了两个脚本,一个bash脚本(send_key.sh),在其中调用另外一个expect脚本(scp_key_to_node.exp),两个脚本放在同一个目录下:
(1)bash脚本:send_key.sh

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/bin/bash    
  2. ssh-keygen -t dsa    
  3. for (( i = 1; i <= 100 ; i ++ ))    
  4. do    
  5.   ./scp_key_to_node.exp $i    
  6. done    

 
(2)expect脚本:(scp_key_to_node.exp)

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect    
  2. set timeout 5    
  3. set hostno [lindex $argv 0]    
  4. spawn scp ~/.ssh/id_dsa.pub impala$hostno:~/.ssh/pub_key    
  5. expect "*password*"    
  6. send "111111\r"    
  7. spawn ssh impala$hostno "cat ~/.ssh/pub_key/ >> ~/.ssh/authorized_keys"    
  8. expect "*password*"    
  9. send "111111\r"    
  10. spawn ssh impala$hostno "chmod 600 ~/.ssh/authorized_keys"    
  11. expect "*password*"    
  12. send "111111\r"    
  13. expect eof    

(3)分析:
set可以设置超时,或者设置一个变量的值
spawn是执行一个命令
expect等待一个匹配的输出流中的内容
send是匹配到之后向输入流写入的内容
[lindex $argv 0]表示脚本的第0个参数
expect eof表示读取到文件结束符
(4)脚本执行方式:
在脚本所在的目录下执行:
# ./send_key.sh 

 

2、ssh实现自动登录,并停在登录服务器上

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect -f  
  2.  set ip [lindex $argv 0 ]     //接收第一个参数,并设置IP  
  3.  set password [lindex $argv 1 ]   //接收第二个参数,并设置密码  
  4.  set timeout 10                   //设置超时时间  
  5.  spawn ssh root@$ip       //发送ssh请滶  
  6.  expect {                 //返回信息匹配  
  7.  "*yes/no" { send "yes\r"; exp_continue}  //第一次ssh连接会提示yes/no,继续  
  8.  "*password:" { send "$password\r" }      //出现密码提示,发送密码  
  9.  }  
  10.  interact          //交互模式,用户会停留在远程服务器上面.  

运行结果如下:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. root@ubuntu:/home/zhangy# ./test.exp 192.168.1.130 admin  
  2. spawn ssh root@192.168.1.130  
  3. Last login: Fri Sep  7 10:47:43 2012 from 192.168.1.142  
  4. [root@linux ~]#  


3、根据IP和密码连接到不同的机器.

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect -f  
  2.   
  3.  set ip 192.168.1.130  
  4.  set password admin  
  5.  set timeout 10  
  6.  spawn ssh root@$ip  
  7.  expect {  
  8.  "*yes/no" { send "yes\r"; exp_continue}  
  9.  "*password:" { send "$password\r" }  
  10.  }  

运行结果如下:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. root@ubuntu:/home/zhangy# ./web.exp  
  2. spawn ssh root@192.168.1.130  
  3. Last login: Fri Sep  7 12:59:02 2012 from 192.168.1.142  


4、远程登录到服务器,并且执行命令,执行完后并退出

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect -f  
  2.  set ip 192.168.1.130  
  3.  set password admin  
  4.  set timeout 10  
  5.  spawn ssh root@$ip  
  6.  expect {  
  7.  "*yes/no" { send "yes\r"; exp_continue}  
  8.  "*password:" { send "$password\r" }  
  9.  }  
  10.  expect "#*"  
  11.  send "pwd\r"  
  12.  send  "exit\r"  
  13.  expect eof  

运行结果如下:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. root@ubuntu:/home/zhangy# ./test3.exp  
  2. spawn ssh root@192.168.1.130  
  3. root@192.168.1.130's password:  
  4. Last login: Fri Sep  7 14:05:07 2012 from 116.246.27.90  
  5. [root@localhost ~]# pwd  
  6. /root  
  7. [root@localhost ~]# exit  
  8. logout  
  9. Connection to 192.168.1.130 closed.  

5、远程登录到ftp,并且下载文件

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect -f  
  2.  set ip [lindex $argv 0 ]  
  3.  set dir [lindex $argv 1 ]  
  4.  set file [lindex $argv 2 ]  
  5.  set timeout 10  
  6.  spawn ftp $ip  
  7.  expect "Name*"  
  8.  send "zwh\r"  
  9.  expect "Password:*"  
  10.  send "zwh\r"  
  11.  expect "ftp>*"  
  12.  send "lcd $dir\r"  
  13.  expect {  
  14.  "*file"  { send_user "local $_dir No such file or directory";send "quit\r" }  
  15.  "*now*"  { send "get $dir/$file $dir/$file\r"}  
  16.  }  
  17.  expect {  
  18.  "*Failed" { send_user "remote $file No such file";send "quit\r" }  
  19.  "*OK"     { send_user "$file has been download\r";send "quit\r"}  
  20.  }  
  21.  expect eof  

运行结果如下:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. root@ubuntu:/home/zhangy# ./test2.exp 192.168.1.130 /var/www/www aaa.html  
  2. spawn ftp 192.168.1.130  
  3. Connected to 192.168.1.130.  
  4. 220 (vsFTPd 2.0.5)  
  5. Name (192.168.1.130:root): zwh  
  6. 331 Please specify the password.  
  7. Password:  
  8. 230 Login successful.  
  9. Remote system type is UNIX.  
  10. Using binary mode to transfer files.  
  11. ftp> lcd /var/www/www  
  12. Local directory now /var/www/www  
  13. ftp> get /var/www/www/aaa.html /var/www/www/aaa.html  
  14. local: /var/www/www/aaa.html remote: /var/www/www/aaa.html  
  15. 200 PORT command successful. Consider using PASV.  
  16. 150 Opening BINARY mode data connection for /var/www/www/aaa.html (66 bytes).  
  17. 226 File send OK.  
  18. 66 bytes received in 0.00 secs (515.6 kB/s)  
  19. quit aaa.html has been download  
  20. 221 Goodbye.  

6、使用expect调用passwd自动更改密码

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/bin/bash  
  2. USER=mynameuser  
  3. PASS=oldpassword  
  4. NPASS=newpassword  
  5. expect << EOF  
  6. spawn passwd  
  7. expect "Changing password for ${USER}."  
  8. send "${PASS}\r"  
  9. expect "Enter new UNIX password:"  
  10. send "${NPASS}\r"  
  11. expect "Retype new UNIX password:"  
  12. send "${NPASS}\r"  
  13. expect eof;  
  14. EOF  

7、完成对服务器的scp任务:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/usr/bin/expect  
  2. set timeout 10  
  3. set host [lindex $argv 0]  
  4. set username [lindex $argv 1]  
  5. set password [lindex $argv 2]  
  6. set src_file [lindex $argv 3]  
  7. set dest_file [lindex $argv 4]  
  8. spawn scp $src_file $username@$host:$dest_file  
  9.  expect {  
  10.  "(yes/no)?"  
  11.    {  
  12.     send "yes\n"  
  13.     expect "*assword:" { send "$password\n"}  
  14.  }  
  15.  "*assword:"  
  16. {  
  17.  send "$password\n"  
  18. }  
  19. }  
  20. expect "100%"  
  21. expect eof  

说明:

(1)注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,如果在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。

(2)这个脚本设置了5个需要手动输入的参数,分别为:目标主机的IP、用户名、密码、本地文件路径、目标主机中的文件路径。如果将以上脚本保存为expect_scp文件,则在shell下执行时需要按以下的规范来输入命令:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. ./expect_scp 192.168.75.130 root 123456 /root/src_file /root/dest_file  

以上的命令执行后,将把本地/root目录下的src_file文件拷贝到用户名为root,密码为123456的主机192.168.75.130中的/root下,同时还将这个源文件重命名为dest_file。

(3)spawn代表在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,然后做出对应的操作。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操作。有了这一句,scp的任务减少了中断的情况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。

 

如果需要实现批量scp的任务,则需要再写一个shell脚本来调用这个expect脚本。

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. #!/bin/sh  
  2. list_file=$1  
  3. src_file=$2  
  4. dest_file=$3  
  5. cat $list_file | while read line  
  6. do  
  7.    host_ip=`echo $line | awk '{print $1}'`  
  8.    username=`echo $line | awk '{print $2}'`  
  9.    password=`echo $line | awk '{print $3}'`  
  10.    echo "$host_ip"  
  11.    ./expect_scp $host_ip $username $password $src_file $dest_file  
  12. done   

指定了3个参数:列表文件的位置、本地源文件路径、远程主机目标文件路径。需要说明的是其中的列表文件指定了远程主机ip、用户名、密码,这些信息需要写成以下的格式:
IP username password

中间用空格或tab键来分隔,多台主机的信息需要写多行内容,如:
192.168.75.130 root 123456
192.168.75.131 knktc testpass

这样就指定了两台远程主机的信息。注意,如果远程主机密码中有“$”、“#”这类特殊字符的话,在编写列表文件时就需要在这些特殊字符前加上转义字符,否则expect在执行时会输入错误的密码。

执行脚本:

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
  1. ./batch_scp.sh ./hosts.list /root/src_file /root/destfile  

用这两个脚本文件,就可以简单地完成批量scp的任务了。

六、综合例子

1、自动化脚本建立主机之间的SSH信任关系

[plain] view plaincopyprint?shell 脚本 交互 expectshell 脚本 交互 expect 
    1. #!/usr/bin/ksh  
    2. #usage ./ssh_trust.sh host1 user1 passwd1 host2 user2 passwd2  
    3. #即建立从user1@host1到user2@host2的ssh信任。  
    4. src_host=$1  
    5. src_username=$2  
    6. src_passwd=$3  
    7.   
    8. dst_host=$4  
    9. dst_username=$5  
    10. dst_passwd=$6  
    11.   
    12. #在远程主机1上生成公私钥对  
    13. Keygen()  
    14. {  
    15. expect << EOF  
    16. spawn ssh $src_username@$src_host ssh-keygen -t rsa  
    17. while 1 {  
    18.         expect {  
    19.                  "password:" {  
    20.                               send "$src_passwd\n"  
    21.                                }  
    22.                 "yes/no*" {  
    23.                             send "yes\n"  
    24.                           }  
    25.                         "Enter file in which to save the key*" {  
    26.                                         send "\n"  
    27.                         }  
    28.                         "Enter passphrase*" {  
    29.                                         send "\n"  
    30.                         }  
    31.                         "Enter same passphrase again:" {  
    32.                                         send "\n"  
    33.                                         }  
    34.   
    35.                         "Overwrite (y/n)" {  
    36.                                         send "n\n"  
    37.                         }  
    38.                         eof {  
    39.                                    exit  
    40.                         }  
    41.         }  
    42. }  
    43. EOF  
    44. }  
    45. #从远程主机1获取公钥保存到本地  
    46. Get_pub()  
    47. {  
    48. expect << EOF  
    49. spawn scp $src_username@$src_host:~/.ssh/id_rsa.pub /tmp  
    50. expect {  
    51.              "password:" {  
    52.                            send "$src_passwd\n";exp_continue  
    53.                 }  
    54.                 "yes/no*" {  
    55.                            send "yes\n";exp_continue  
    56.                 }     
    57.                 eof {  
    58.                                 exit  
    59.                 }  
    60. }  
    61. EOF  
    62. }  
    63. #将公钥的内容附加到远程主机2的authorized_keys  
    64. Put_pub()  
    65. {  
    66. src_pub="$(cat /tmp/id_rsa.pub)"  
    67. expect << EOF  
    68. spawn ssh $dst_username@$dst_host "mkdir -p ~/.ssh;echo $src_pub >> ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys"  
    69. expect {  
    70.             "password:" {  
    71.                         send "$dst_passwd\n";exp_continue  
    72.              }  
    73.             "yes/no*" {  
    74.                         send "yes\n";exp_continue  
    75.              }     
    76.             eof {  
    77.                         exit  
    78.              }   
    79. }  
    80. EOF  
    81. }  
    82. Keygen  
    83. Get_pub  
    84. Put_pub