【Linux學習】Shell腳本學習之expect命令


Shell腳本學習之expect命令

一、概述

        我們通過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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. wget http://nchc.dl.sourceforge.net/sourceforge/tcl/tcl8.4.11-src.tar.gz  


2.解壓縮源碼包

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. tar xfvz tcl8.4.11-src.tar.gz  


3.安裝配置

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. wget http://sourceforge.net/projects/expect/files/Expect/5.45/expect5.45.tar.gz/download  


2.解壓縮源碼包

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. tar xzvf expect5.45.tar.gz  


3.安裝配置

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. Login: somebody Password: sillyme  

Expect最簡單的腳本操作模式本質上和Chat腳本工作模式是一樣的。

例子:

1、實現功能

下面我們分析一個響應chsh命令的腳本。我們首先回顧一下這個交互命令的格式。

假設我們要為用戶chavez改變登錄腳本,要求實現的命令交互過程如下:

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. #!/usr/bin/expect  
  2. # Change a login shell to tcsh  
[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. Answer was "silence"   

 

5、一個更復雜的例子

       下面我們將討論一個更加復雜的expect腳本例子,這個腳本使用了一些更復雜的控制結構和很多復雜的交互過程。這個例子用來實現發送write命令給任意的用戶,發送的消息來自於一個文件或者來自於鍵盤輸入。

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. $ expect -D 1 script  

“-D”選項左邊的選項會在調試器啟動以前被處理。然后,在調試器啟動以后,剩下的命令才會被執行。

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. $ expect -b  

6、讓expect不解釋命令行參數

你可以使用標識符讓expect不解釋命令行參數。

你可以像下面這樣的讀入命令行參數:

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. $ expect print_cmdline_args.exp -d -c  
  2. argv0 : -d  
  3. argv1 : -c  


四、expect簡單例子

為了更好理解except腳本幾個簡單參數,我們再舉一個簡單的例子:

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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 plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. ./batch_scp.sh ./hosts.list /root/src_file /root/destfile  

用這兩個腳本文件,就可以簡單地完成批量scp的任務了。

六、綜合例子

1、自動化腳本建立主機之間的SSH信任關系

[plain]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  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  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM