shell腳本交互:expect學習筆記及實例詳解

最近項目需求,須要寫一些shell腳本交互,管道不夠用時,expect能夠很好的實現腳本之間交互,搜索資料,發現網上好多文章都是轉載的,以爲這篇文章還不錯,因此簡單修改以後拿過來和你們分享一下~shell

 1. expect是spawn: 後面加上須要執行的shell命令,好比說spawn sudo touch testfile
1.3 expect: 只有spawn執行的命令結果纔會被expect捕捉到,由於spawn會啓動一個進程,只有這個進程的相關信息纔會被捕捉到,主要包括:標準輸入的提示信息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 exit
  9 }
 10
 11 set user [lindex $argv [$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           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行: 測試,無需加-S亦可;
第15行:通常狀況下,若是連續作兩個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"
 9  expect "*$"
這個例子其實是經過ssh去登陸遠程機器,而且在遠程機器上創佳一個目錄,咱們看到在咱們輸入密碼後並無去expect eof,這是由於ssh這個spawn並無結束,並且手動操做時ssh實際上也不會本身結束除非你exit;因此你只能expect bash的提示符,固然也能夠是機器名等,這樣才能夠在遠程建立一個目錄。

注意,請不要用spawn mkdir tmpdir,這樣會使得上一個spawn即ssh結束,那麼你的tmpdir將在本機創建。

固然實際狀況下可能會要你確認ssh key,能夠經過並行的expect進行處理,很少贅述。

5. 以爲bash不少狀況下已經很強大,因此可能用expect只須要掌握這些就行了,其餘的若是用到能夠再去google了。
bash

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  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:  23: expect eof

注意代碼剛開始的第一行,指定了expect的路徑,與shell腳本相同,這一句指定了程序在執行時到哪裏去尋找相應的啓動程序。代碼剛開始還設定了timeout的時間爲10秒,若是在執行scp任務時遇到了代碼中沒有指定的異常,則在等待10秒後該腳本的執行會自動終止。ssh

spawn表明在本地終端執行的語句,在該語句開始執行後,expect開始捕獲終端的輸出信息,而後作出對應的操做。expect代碼中的捕獲的(yes/no)內容用於完成第一次訪問目標主機時保存密鑰的操做。有了這一句,scp的任務減小了中斷的狀況。代碼結尾的expect eof與spawn對應,表示捕獲終端輸出信息的終止。post

 

有了這段expect的代碼,還只能完成對單個遠程主機的scp任務。若是須要 實現批量scp的任務,則須要再寫一個shell腳原本調用這個expect腳本。測試

     1: #!/bin/shui

  2:google

 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、用戶名、密碼,這些信息須要寫成如下的格式:spa

IP username password.net

中間用空格或tab鍵來分隔,多臺主機的信息須要寫多行內容。

這樣就指定了兩臺遠程主機的信息。注意,若是遠程主機密碼中有「$」、「#」這類特殊字符的話,在編寫列表文件時就須要在這些特殊字符前加上轉義字符,不然expect在執行時會輸入錯誤的密碼。

對於這個shell腳本,保存爲batch_scp.sh文件,與剛纔保存的expect_scp文件和列表文件(就定義爲hosts.list文件吧)放到同一目錄下,執行時按照如下方式輸入命令就能夠了:

  ./batch_scp.sh ./hosts.list /root/src_file /root/destfile

 ===============================================================================

下面咱們來看一些expect的一些內部參數:

exp_continue [-continue_timer]
             The command exp_continue allows expect itself to continue executing rather than returning as it  normally would.  By  default  exp_continue  resets the timeout timer. The -continue_timer flag prevents timer from being restarted.

exp_version [[-exit] version]
             is useful for assuring that the script is compatible with the current version of Expect.

With  no  arguments, the current version of Expect is returned.  This version may then be encoded in your script.  If you actually know that you are not using features of recent versions, you can specify an earlier version.

具體的用法還能夠查看文檔~

#!/bin/sh
# \
exec expect -- "$0" ${1+"$@"}
exp_version -exit 5.0
if {$argc!=2} {
    send_user "usage: remote-exec command password\n"
    send_user "Eg. remote-exec \"ssh user@host ls\; echo done\" password\n"
    send_user "or: remote-exec \"scp /local-file user@host:/remote-file\" password\n"
    send_user "or: remote-exec \"scp user@host:/remote-file local-file\" password\n"
    send_user "or: remote-exec \"rsync --rsh=ssh /local-file user@host:/remote-file\" password\n"
    send_user "Caution: command should be quoted.\n"
    exit
}
set cmd [lindex $argv 0]
set password [lindex $argv 1]
eval spawn $cmd
set timeout 600
while {1} {
    expect -re "Are you sure you want to continue connecting (yes/no)?" {
            # First connect, no public key in ~/.ssh/known_hosts
            send "yes\r"
        } -re "assword:" {
            # Already has public key in ~/.ssh/known_hosts
            send "$password\r"
        } -re "Permission denied, please try again." {
            # Password not correct
            exit
        } -re "kB/s|MB/s" {
            # User equivalence already established, no password is necessary
            set timeout -1
        } -re "file list ..." {
            # rsync started
            set timeout -1
        } -re "bind: Address already in use" {
            # For local or remote port forwarding
            set timeout -1
        } -re "Is a directory|No such file or directory" {
            exit
        } -re "Connection refused" {
            exit
        } timeout {
            exit
        } eof {
            exit
        }
}

注意用法:

Eg. remote-exec "ssh user@host ls; echo done" password
or: remote-exec "scp /local-file user@host:/remote-file" password
or: remote-exec "scp user@host:/remote-file local-file" password
or: remote-exec "rsync --rsh=ssh /local-file user@host:/remote-file" password
Caution: command should be quoted.

轉自:http://blog.csdn.net/nero_g/article/details/53945654

相關文章
相關標籤/搜索