1. 程式人生 > >expect學習筆記及例項詳解

expect學習筆記及例項詳解

expect學習筆記及例項詳解

引用自:http://wenku.baidu.com/view/b65e103610661ed9ad51f374.html

 

1. expect 是基於tcl 演變而來的,所以很多語法和tcl 類似,基本的語法如下

所示:

1.1 首行加上/usr/bin/expect

1.2 spawn: 後面加上需要執行的shell 命令,比如說spawn sudo touch testfile

1.3 expect: 只有spawn 執行的命令結果才會被expect 捕捉到,因為spawn 會啟

動一個程序,只有這個程序的相關資訊才會被捕捉到,主要包括:標準輸入的提

示資訊,eof 和timeout。

1.4 send 和send_user:send 會將expect 指令碼中需要的資訊傳送給spawn 啟動

的那個程序,而send_user 只是回顯使用者發出的資訊,類似於shell 中的echo 而

已。

 

2. 一個小例子,用於linux 下賬戶的建立:

filename: account.sh,可以使用./account.sh newaccout 來執行;

1 #!/usr/bin/expect

2

3 set passwd "mypasswd"

4 set timeout 60

5

6 if {$argc != 1} {

7 send "usage ./account.sh \$newaccount\n"

8 exit

9 }

10

11 set user [lindex $argv [expr $argc-1]]

12

13 spawn sudo useradd -s /bin/bash -g mygroup -m $user

14

15 expect {

16 "assword" {

17 send_user "sudo now\n"

18 send "$passwd\n"

19 exp_continue

20 }

21 eof

22 {

23 send_user "eof\n"

24 }

25 }

26

27 spawn sudo passwd $user

28 expect {

29 "assword" {

30 send "$passwd\n"

31 exp_continue

32 }

33 eof

34 {

35 send_user "eof"

36 }

37 }

38

39 spawn sudo smbpasswd -a $user

40 expect {

41 "assword" {

42 send "$passwd\n"

43 exp_continue

44 }

45 eof

46 {

47 send_user "eof"

48 }

49 }

 

3. 注意點:

 

第3 行: 對變數賦值的方法

第4 行: 預設情況下,timeout 是10 秒;

第6 行: 引數的數目可以用$argc 得到;

第11 行:引數存在$argv 當中,比如取第一個引數就是[lindex $argv 0];並且

如果需要計算的話必須用expr,如計算2-1,則必須用[expr 2-1]

第13 行:用spawn 來執行一條shell 命令,shell 命令根據具體情況可自行調整;

有文章說sudo 要加-S,經過實際測試,無需加-S 亦可;

第15 行:一般情況下,如果連續做兩個expect,那麼實際上是序列執行的,用。expect 與“{ ”之間直接必須有空格或則TAB間隔,否則會出麻煩,會報錯invalid command name "expect{" 

例子中的結構則是並行執行的,主要是看匹配到了哪一個;在這個例子中,如果

你寫成序列的話,即

expect "assword"

send "$passwd\n"

expect eof

send_user "eof"

那麼第一次將會正確執行,因為第一次sudo 時需要密碼;但是第二次執行時由於

密碼已經輸過(預設情況下sudo 密碼再次輸入時間為5 分鐘),則不會提示使用者

去輸入,所以第一個expect 將無法匹配到assword,而且必須注意的是如果是

spawn 命令出現互動式提問的但是expect 匹配不上的話,那麼程式會按照timeout

的設定進行等待;可是如果spawn 直接發出了eof 也就是本例的情況,那麼expect

"assword"將不會等待,而直接去執行expect eof。

這時就會報expect: spawn id exp6 not open,因為沒有spawn 在執行,後面的

expect 指令碼也將會因為這個原因而不再執行;所以對於類似sudo 這種命令分支

不定的情況,最好是使用並行的方式進行處理;

第17 行:僅僅是一個使用者提示而已,可以刪除;

第18 行:向spawn 程序傳送password;

第19 行:使得spawn 程序在匹配到一個後再去匹配接下來的互動提示;

第21 行:eof 是必須去匹配的,在spawn 程序結束後會向expect 傳送eof;如果

不去匹配,有時也能執行,比如sleep 多少秒後再去spawn 下一個命令,但是不

要依賴這種行為,很有可能今天還可以,明天就不能用了;

 

4. 其他

下面這個例子比較特殊,在整個過程中就不能expect eof 了:

1 #!/usr/bin/expect

2

3 set timeout 30

4 spawn ssh 10.192.224.224

5 expect "password:"

6 send "mypassword\n"

7 expect "*$"

send "mkdir tmpdir\n" #遠端執行命令用send傳送,不用spawn

9 expect "*$" #注意這個地方,要與作業系統上環境變數PS1相匹配,尤其是有PS1有空格的情況下,一定在expct "*$ "把空格加上,加不上你就完蛋了。我試過。

這個例子實際上是通過ssh 去登入遠端機器,並且在遠端機器上創佳一個目錄,

我們看到在我們輸入密碼後並沒有去expect eof,這是因為ssh 這個spawn 並沒

有結束,而且手動操作時ssh 實際上也不會自己結束除非你exit;所以你只能

expect bash 的提示符,當然也可以是機器名等,這樣才可以在遠端建立一個目

錄。

注意,請不要用spawn mkdir tmpdir,這樣會使得上一個spawn 即ssh 結束,那

麼你的tmpdir 將在本機建立。

當然實際情況下可能會要你確認ssh key,可以通過並行的expect 進行處理,不

多贅述。

 

5. 覺得bash 很多情況下已經很強大,所以可能用expect 只需要掌握這些就好了,

其他的如果用到可以再去google 了。

 

原始碼圖片:

 

 

 

 

 

6 \例項:下面這個指令碼是完成對單個伺服器scp任務。

 1: #!/usr/bin/expect
 2: 
 3: set timeout 10
 4: set host [lindex $argv 0]
 5: set username [lindex $argv 1]
 6: set password [lindex $argv 2]
 7: set src_file [lindex $argv 3]
 8: set dest_file [lindex $argv 4]
 9: 
10: spawn scp  $src_file [email protected]$host:$dest_file
 11: expect {
 12:     "(yes/no)?"
 13:         {
 14:             send "yes\n"
 15:             expect "*assword:" { send "$password\n"}
 16:         }
 17:     "*assword:"
 18:         {
 19:             send "$password\n"
 20:         }
 21:     }
 22: expect "100%"
 23: expect eof
參考原始碼圖片:

注意程式碼剛開始的第一行,指定了expect的路徑,與shell指令碼相同,這一句指定了程式在執行時到哪裡去尋找相應的啟動程式。程式碼剛開始還設定了timeout的時間為10秒,如果在執行scp任務時遇到了程式碼中沒有指定的異常,則在等待10秒後該指令碼的執行會自動終止。

spawn代表在本地終端執行的語句,在該語句開始執行後,expect開始捕獲終端的輸出資訊,然後做出對應的操作。expect程式碼中的捕獲的(yes/no)內容用於完成第一次訪問目標主機時儲存金鑰的操作。有了這一句,scp的任務減少了中斷的情況。程式碼結尾的expect eof與spawn對應,表示捕獲終端輸出資訊的終止。

 

有了這段expect的程式碼,還只能完成對單個遠端主機的scp任務。如果需要實現批量scp的任務,則需要再寫一個shell指令碼來呼叫這個expect指令碼。

1: #!/bin/sh

 2: 
 3: list_file=$1
 4: src_file=$2
 5: dest_file=$3
 6: 
7: cat $list_file | while    read line
 8: do
 9:     host_ip=`echo $line | awk '{print $1}'`
 10:     username=`echo $line | awk '{print $2}'`
 11:     password=`echo $line | awk '{print $3}'`
 12:     echo "$host_ip"
 13:   ./expect_scp $host_ip $username $password $src_file $dest_file
 15: done

參考程式碼圖片如下:

很簡單的程式碼,指定了3個引數:列表檔案的位置、本地原始檔路徑、遠端主機目標檔案路徑。需要說明的是其中的列表檔案指定了遠端主機ip、使用者名稱、密碼,這些資訊需要寫成以下的格式:

IP username password

中間用空格或tab鍵來分隔,多臺主機的資訊需要寫多行內容。

這樣就指定了兩臺遠端主機的資訊。注意,如果遠端主機密碼中有“$”、“#”這類特殊字元的話,在編寫列表檔案時就需要在這些特殊字元前加上轉義字元,否則expect在執行時會輸入錯誤的密碼。

對於這個shell指令碼,儲存為batch_scp.sh檔案,與剛才儲存的expect_scp檔案和列表