歡迎您光臨本站 註冊首頁

關於使用expect編程遇到的疑問

←手機掃碼閱讀     火星人 @ 2014-03-03 , reply:0
我寫了個簡單的expect測試腳本,但是實際運行的時候發現並沒有實現將互動式的命令調用變成非互動式
這到底應該怎麼改呢,求各位指點
代碼如下

#!/bin/bash
#!/usr/local/tcl/bin
spawn ssh 192.168.0.88
expect "yes"  
send "yes\n"
expect "password:"
send "123456\n"
send "touch /tmp/abc123 \n"
send "exit\n"

一,expect的FAQ
如何匹配多種情況,典型的例子就是ssh ,第一個可能是yes,然後password,或是直接password。就是并行匹配的情況,見下面例子。
  1. #!/usr/bin/expect
  2.     set timeout 60
  3.     set pwd "該機器的密碼"
  4.     spawn ssh  10.10.10.1
  5.            expect {
  6.                       "\[#$\]"     {send "\r" }   ### 假如有了ssh 公鑰之類的,直接回車。當然普通用戶下邊還可能需要sudo,自己處理一下吧。
  7.                     "not know" {send_user "[exec echo \"not know\"]";exit}

  8.                     "(yes/no)?" {send "yes\r";exp_continue} #continue的意義,靠猜測也能差不多知道了吧?可以Man expect

  9.                     "password:" {send  "$pwd\r"}

  10.                     "Permission denied, please try again." {

  11.                         send_user "[exec echo \"Error:Password is wrong\"]"

  12.                         exit  }

  13.                     }
複製代碼
####  把所有可能出現的情況列舉出來做匹配,假如寫法如下就是串列執行了。
expect "*#"
send "ifconfig\r"
send "exit\r"
expect eof # 只有spawn產生的進程的相關信息才能被expect捕捉到,還包含2個特殊情況,eof和timeout,eof關閉spawn 產生的spawn id :exp_id,也就是結束標記。這個eof是必不可少的,至於為什麼,我也沒找到權威答案,包括手冊都沒提到,但實踐中發現很多時候不加會導致得不到你要的結果。請看下邊的一個例子:
  1. #!/usr/bin/expect -f

  2. set ip 10.1.1.1
  3. set pwd 123456

  4. spawn scp ssh.exp root@$ip:/tmp

  5. expect {
  6.          "(yes/no)?" {send "yes\r";exp_continue}

  7.          "password:" {send  "$pwd\r"}
  8.                     }

  9. # expect eof
複製代碼
假如最後的expect eof給註釋了,文件不會被scp到10.1.1.1上,也許你感覺很奇怪,關鍵就在於這個eof,我看了相關資料包括debug信息沒有找到令人信服的答案。
我個人猜測是,沒有eof,那麼僅僅是把密碼send過去,連回車都沒有執行。然後就異常退出了。加了eof部分,讓Expect執行完畢,下邊才能退齣子程序。

注意set timeout 這個要保證你下邊的command能完成
比如你scp一個文件需要30s,而你set timeout 10那麼會超時退出的。

其他自己看tcl和expect的manual吧,假如你想把ip,密碼放文本里,也可以,只需要set 參數,具體到我博客看,我懶得貼了。大致是
set name [lindex $argv 0],或看下邊的例子。

------------------------------------------------------------------------------
添加一個mysql,實際上是expect的interact的例子
#!/usr/bin/expect

set pwd 123456
spawn mysql -uroot -p

expect "password"
send "$pwd\r"
expect "mysql>"
send "show databases;\r"
interact

這樣控制權交給了mysql命令行,你可以手工執行各種操作了。有的時候可能需要這個。
## for 循環一例
改密碼,for循環的sample
spawn passwd test
for {set i 1} {$i<=2} {incr i} {
expect "password:"
send "1\#23abc\r"
}
expect eof
=============================================================

注意expect的特殊情況,需要轉義,僅僅列舉一些例子。
比如expect "#"
send "ps -ef|grep cpusd|grep -v $$ |awk '{print \"kill -9 \"\$2}'|sh\r"
殺死遠程機器上cpusd進程,awk後邊的"和$2都要\轉義,否則$2是一個變數會報錯。「轉義是因為開頭有了",會認為extra close-quota.
set定義的變數也是如此,比如你set pwd 123\abc ,那麼應該寫成set pwd 123\\abc來轉義。

如何得到命令的結果?
有時候你send一個命令,希望得到結果,並加以判斷,需求在這裡http://bbs.chinaunix.net/thread-3575650-1-1.html
你可以看下expect_out(buffer)這個buffer保存了上一個命令輸出的到匹配處之間的輸出,可以通過正則來提取出來。
send "ip addr|awk '/eth0/&&/32/{print \$2}'\r" #執行一個shell命令,看結果是否是172.10.0.249/32
expect -re {.*\/(.*)\r\n} {  #可以用正則的子串來提取命令執行結果,expect_out(0,string)是整個正則匹配的內容,1是第一個子串匹配的內容,有趣的是,包括你send過去的命令連同shell 提示符號[root@test.com~]# 也被被當做輸出。這個可以通過debug看出來,回車符號是\r\n。至於子串匹配,可以看手冊,一共有9個,當然第二個就是expect_out(2,string),以此類推。
set ip $expect_out(1,string)   把這個子串正則匹配的結果賦給IP
}

if {$ip == "172.18.0.249/32" } { #判斷是否,做動作。
send "who\r" }

如何獲取一個本地的shell,在expect里使用?
其實這個在上邊已經出現過了,只需要exec即可。比如獲取本地的IP地址
set ip [exec shell_command]


如何關閉日誌?log_user 0,當然你可以在某個階段關閉,某個階段開始,log_user 1開啟。所有的輸出記錄在expect_out(buffer)和send過去的command都被關閉。如何記錄日誌?log_file 日誌名字。具體看手冊

如何遠程執行本地寫好的腳本?需求見。http://bbs.chinaunix.net/viewthr ... 2585&from=favorites
利用tcl文件操作知識,把腳本內容讀取賦給cmd,然後send過去。

open urscript [ open A.sh ]
while {[ gets $urscript cmd ] >= 0} {send "$cmd\r"}
close $urscript

具體請後邊連接,但不夠通用,比如腳本需要帶上參數這個辦法可能就不太好了
如何把ip,密碼寫文本里,然後批量執行?請看下邊的sample(ssh.exp)
  1. set f [open ip r]
  2. while { [gets $f line ]>=0 } {

  3.     set ip [lindex $line 0]
  4.     set pwd [lindex $line 1]
  5.     spawn ssh $ip

  6.        expect {
  7.                     "not know" {send_user "[exec echo \"not know\"]";exit}

  8.                     "(yes/no)?" {send "yes\r";exp_continue}

  9.                     "password:" {send  "$pwd\r"}

  10.                     "Permission denied, please try again." { send_user "[exec echo \"Error:Password is wrong\"]"
  11.                                                               
  12.                                                        exit  }
  13.                 }
  14. expect "#"
  15. send "ifconfig\r"
  16. expect "#"
  17. send "exit\r"
  18. expect eof
  19.               
  20.          }
  21. close $f
複製代碼
其中ip里放的是ip ,密碼,只需要執行expect -f ssh.exp 即可。這個open,close完全是tcl的文件操作知識。

二,高級話題,你真的懂expect嗎?它是如何工作的?為何有詭異問題出現?為何有時候取不到send過去的命令的執行結果??------有空寫!

三,一個批量執行任務的python腳本(IT民工的福音啊

民工們一般維護同類的機器很多,有的時候需要所有機器都做一個操作,比如20台機器都要安裝mysql之類的,最開始的時候咋處理?稍微聰明點的辦法,寫安裝腳本,調試測試OK,一個個登陸上去執行。或是安裝好了打個包,然後用腳本替換配置文件等。還要20台一個個登陸,萬一機器多到50,100台這種事枯燥而且易出錯,真是民工的活啊。在好友的幫助下,經我反覆測試寫了一個Mssh.py(multipart-job ssh),適用於以上場景。
簡單說明:Linux 平台,python >=2.6(多數linux版本包括不限於RHEL5,CENTOS5都是2.4.3,哪怕你yum 升級也是2.4.3,需要額外安裝2.6,因為需要多進程的模塊,而2.6以後才有這個模塊)
另外需要安裝pexpect(一個expect-like)的模塊,假如yum安裝的話,需要拷貝/usr/lib/python2.4/site-packages/pexpect.py到2.6的目錄下/usr/local/lib/python2.6/site-packages/,否則會報錯,沒有這個模塊,如果自己下載安裝,我沒試過,可能也類似需要放到這個目錄下。

說明,1.假如機器允許root登錄,此時把需要登錄的ip password放一個文件hostlist里,格式
ip password
比如10.10.10.1 123abc
可以寫註釋,以#開頭
比如:
#nginx host
10.10.10.1 123456
。。。
但是不能有空行,否則會報錯。
比如:
10.10.10.1
這裡不能有空行
10.10.1.2
----------------
2.禁止root登錄,只允許普通用戶,假如是test用戶登錄,然後sudo。
那麼hostlist格式
ip test用戶的密碼 sudo到root的密碼
比如:
10.1.1.1 123456 superpwd
同樣不能有空行,可以加#註釋

更新:刪除了之前發的python腳本,更新至最新的多進程測試完畢的版本,考慮到了root登陸,非root然後sudo,要輸入yes,passwd,直接passwd,ssh信任(無密碼和很BT的有密碼的2種),埠非22等各種特殊情況。除了ssh信任key未測外,其他都測試完畢。測試ssh key有一些問題,在改動特別感謝好友jiaion給的幫助

首先在機器上mkdir /tmp/mssh/

為了防止粘貼變形,把腳本內容放在附件里。

測試一下:python mssh.py,會出現提示
  1. Usage: mssh.py -f hostlist -u user -c "cmd" versrion 1.1

  2. Options:
  3. --version show program's version number and exit
  4. -h, --help show this help message and exit
  5. -f FILE, --file=FILE host list ,which stores ip and password
  6. -u USER, --user=USER username,root or other users
  7. --port=PORT sshd's port ,default:22
  8. -d, --debug Output debug messages
  9. -c CMD, --cmd=CMD commadn to be exected,don't forget to type "", e.g
  10. "ifconfig"
複製代碼
完整的用法,python mssh.py -f hostlist -u root -c "ifconfig" --port 22 -d
建議執行命令要加上「」,比如"ifconfig"
其中--port 22可以不加,但port非22要--port portnum 比如--port 51223
-d 觀察整個執行過程的輸出,debug用,但建議慎用,因為多進程,信息輸出打亂了。
看似只能執行簡單的ifconfig,好吧,給你個好辦法,把你的腳本調試完畢,放到一個http server上,我一般建議放監控(cacti)上,路由最全,而且肯定網路通,為啥,自己想去。然後執行批量下載命令
python mssh.py -u root -c "wget http://ip/scrpit.sh"
然後執行python mssh.py -u root -c "run scrpit.sh"
多爽啊,幾十台機器都做一個事,你需要的僅僅是寫好各種腳本即可,就看你的腳本能力了。
假如你熟悉python,完全可以把執行的腳本或命令放到本地,然後read整個內容,賦值給cmd,然後send過去,但這種有很大的弊端,不夠通用。
以上對非root也可以,python mssh.py -u test -c 。。。
特別注意:
不要在執行的命令里出現交互的情況,除非你能解決這個交互。比如python mssh.py -u root -c "yum install expect" 這個結果會等待到超時拋出異常,因為有個y/n,讓你輸入,你要明確指定yum -y install expect
第二,你的執行機器上的環境變數直接決定了腳本是否能執行成功,比如你python mssh.py -u root -c "command",而這個命令沒在$PATH里,那肯定是報錯的.
第三看,不要寫錯ip,password,sudopwd,否則。。。

最後無-d 參數,整個執行日誌記錄在/tmp/mssh/$ip,以IP為日誌名字,是追加的。

[火星人 ] 關於使用expect編程遇到的疑問已經有1965次圍觀

http://coctec.com/docs/service/show-post-799.html