expect學習筆記及實例詳解


因為最近正在學習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 "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了。

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文件吧)放到同一目錄下,執行時按照以下方式輸入命令就可以了:

./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 ear-
             lier 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.


免責聲明!

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



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