expect

shell腳本實現ssh自動登陸遠程服務器示例:linux

 

#!/usr/bin/expect正則表達式

spawn ssh root@192.168.22.194shell

expect "*password:"bash

send "123\r"服務器

expect "*#"ssh

interact學習

 

 

Expect是一個用來處理交互的命令。藉助Expect,咱們能夠將交互過程寫在一個腳本上,使之自動化完成。形象的說,ssh登陸,ftp登陸等都符合交互的定義。下文咱們首先提出一個問題,而後介紹基礎知四個命令,最後提出解決方法。測試

問題google

如何從機器A上ssh到機器B上,而後執行機器B上的命令?如何使之自動化完成?


四個命令

Expect中最關鍵的四個命令是send,expect,spawn,interact。

send:用於向進程發送字符串

expect:從進程接收字符串

spawn:啓動新的進程

interact:容許用戶交互

1. send命令

send命令接收一個字符串參數,並將該參數發送到進程。

expect1.1> send "hello world\n"

hello world

2. expect命令


(1)基礎知識

expect命令和send命令正好相反,expect一般是用來等待一個進程的反饋。expect能夠接收一個字符串參數,也能夠接收正則表達式參數。和上文的send命令結合,如今咱們能夠看一個最簡單的交互式的例子:

expect "hi\n"

send "hello there!\n"

這兩行代碼的意思是:從標準輸入中等到hi和換行鍵後,向標準輸出輸出hello there。

tips: $expect_out(buffer)存儲了全部對expect的輸入,<$expect_out(0,string)>存儲了匹配到expect參數的輸入。

好比以下程序:

expect "hi\n"

send "you typed <$expect_out(buffer)>"

send "but I only expected <$expect_out(0,string)>"

當在標準輸入中輸入

test

hi

是,運行結果以下

you typed: test

hi

I only expect: hi


(2)模式-動做

expect最經常使用的語法是來自tcl語言的模式-動做。這種語法極其靈活,下面咱們就各類語法分別說明。

單一分支模式語法:

expect "hi" {send "You said hi"}

匹配到hi後,會輸出"you said hi"

多分支模式語法:

expect "hi" { send "You said hi\n" } \

"hello" { send "Hello yourself\n" } \

"bye" { send "That was unexpected\n" }

匹配到hi,hello,bye任意一個字符串時,執行相應的輸出。等同於以下寫法:

expect {

"hi" { send "You said hi\n"}

"hello" { send "Hello yourself\n"}

"bye" { send "That was unexpected\n"}

}


3. spawn命令

上文的全部demo都是和標準輸入輸出進行交互,可是咱們跟但願他能夠和某一個進程進行交互。spawm命令就是用來啓動新的進程的。spawn後的send和expect命令都是和spawn打開的進程進行交互的。結合上文的send和expect命令咱們能夠看一下更復雜的程序段了。

set timeout -1

spawn ftp ftp.test.com      //打開新的進程,該進程用戶鏈接遠程ftp服務器

expect "Name"             //進程返回Name時

send "user\r"        //向進程輸入anonymous\r

expect "Password:"        //進程返回Password:時

send "123456\r"    //向進程輸入don@libes.com\r

expect "ftp> "            //進程返回ftp>時

send "binary\r"           //向進程輸入binary\r

expect "ftp> "            //進程返回ftp>時

send "get test.tar.gz\r"  //向進程輸入get test.tar.gz\r

這段代碼的做用是登陸到ftp服務器ftp ftp.uu.net上,並以二進制的方式下載服務器上的文件test.tar.gz。程序中有詳細的註釋。


4.interact

到如今爲止,咱們已經能夠結合spawn、expect、send自動化的完成不少任務了。可是,如何讓人在適當的時候干預這個過程了。好比下載完ftp文件時,仍然能夠停留在ftp命令行狀態,以便手動的執行後續命令。interact能夠達到這些目的。下面的demo在自動登陸ftp後,容許用戶交互。

spawn ftp ftp.test.com

expect "Name"

send "user\r"

expect "Password:"

send "123456\r"

interact


解決方法

上文中提到:

如何從機器A上ssh到機器B上,而後執行機器B上的命令?如何使之自動化完成?

下面一段腳本實現了從機器A登陸到機器B,而後執行機器B上的pwd命令,並停留在B機器上,等待用戶交互。具體含義請參考上文。

#!/home/tools/bin/64/expect -f

 set timeout -1 

 spawn ssh $BUser@$BHost

 expect  "*password:" { send "$password\r" }

 expect  "$*" { send "pwd\r" }

 interact

 

 

 

 

expect學習筆記及實例詳解

 

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 "*$"

8 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 $username@$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文件和列表文件(就定義爲hosts.list文件吧)放到同一目錄下,執行時按照如下方式輸入命令就能夠了:

1.jpg

2.jpg

3.jpg

1.jpg

2.jpg

11.jpg

12.jpg

13.jpg

14.jpg

相關文章
相關標籤/搜索