expect 知識與示例說明


expect 知識與示例說明

2012/04/10 chenxin
2019/07/07 update Chenxin

參考
https://www.cnblogs.com/yinghao1991/p/6926125.html
https://www.jellythink.com/archives/373
https://www.cnblogs.com/chengjian-physique/p/8254381.html
https://core.tcl-lang.org/expect/index
概念與基礎知識
目的
利用expect,則可以根據程序的提示,模擬標准輸入提供給程序,從而實現自動化交互執行.
指令
命令 作用
send 用於向進程發送字符串.如果要發送Ctrl-C結束進程,可以通過send "\003" 實現。send \001(發送ctrl+a) 然后 send "d" (發送d)合起來相當於發送ctrl+a +d.
expect 從進程接收字符串(等待一個進程的反饋,捕獲后匹配)
spawn 啟動新的進程(spawn后的send和expect命令都是和使用spawn打開的進程進行交互).
interact 進入用戶交互(一般情況下使用spawn、send和expect就可以很好的完成任務;在特殊場合下需使用interact,用於退出自動化,進入人工交互。如我們用spawn、send和expect完成了ftp登陸下載文件任務,但是我們希望在文件下載結束后,仍可以停留在ftp命令行狀態,以便手動的執行后續命令,此時就需要使用interact).
timeout expect中等待命令的輸出信息是有一個 timeout的設定的,默認是10秒。這個特性是防止那些執行死機的命令的。一旦到了這個timeout,還是沒有屏幕輸出的話,expect腳本中下面的代碼就會執行 。或者我們在expect腳本中如果定義了timeout的響應代碼的話,這些代碼就會被執行。
示例說明
示例一

!/usr/tcl/bin/expect # 使用expect來解釋該腳本;

set timeout 30 # 設置超時時間,單位為秒,默認情況下是10秒;
set host "101.200.241.109" # 設置變量;
set username "root"
set password "123456"
spawn ssh $username@$host # spawn是進入expect環境后才可以執行的expect內部命令.它主要的功能是給ssh運行進程加個殼,用來傳遞交互指令;
expect "password" {send "$password\r"} # 這里的expect也是expect的一個內部命令.判斷上次輸出結果里是否包含“password”的字符串,如果有則立即返回;否則就等待一段時間后返回(上面設置的30s);send "$password\r":當匹配到對應的輸出結果時,就發送密碼到打開的ssh進程,執行交互動作;這就是expect的 "模式-動作"
interact # 執行完成后保持交互狀態,把控制權交給控制台,這個時候就可以手工操作了。如果沒有這一句登錄完成后會退出,而不是留在遠程終端上。
示例二
set timeout -1
spawn ftp ftp.test.com #打開新的進程,該進程用戶連接遠程ftp服務器
expect "Name" #進程返回Name時
send "user\r" #向進程輸入user\r
expect "Password:"
send "123456\r"
expect "ftp> "
send "binary\r"
expect "ftp> " #進程返回ftp>時
send "get test.tar.gz\r" #向進程輸入get test.tar.gz\r

模式-動作 與 exp_continue循環式匹配
結合着expect "password" {send "$password\r"}這句代碼來說說“模式-動作”。這是一個單一分支模式匹配.簡單的說就是匹配到一個模式,就執行對應的動作;匹配到password字符串,就輸入密碼。
單一分支模式語法:
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"}
}
你可能也會看到這樣的代碼:
expect {
"password" {
send "$password\r";exp_continue # 其中exp_continue表示循環式匹配
}
eof # 一旦接收到標識子進程已經結束的eof字符,expect腳本也就退出結束
{
send "eof"
}
}
通常匹配之后都會退出語句,但如果有exp_continue則可以不斷循環匹配,輸入多條命令,簡化寫法。
關於 exp_continue 的說明
exp_continue 附加於某個 expect 判斷項之后,可以使該項被匹配后,還能繼續匹配該 expect 判斷語句內的其他項。exp_continue 類似於控制語句中的 continue 語句。
例如:下例將判斷交互輸出中是否存在 yes/no 或 *assword。如果匹配 yes/no 則輸出 yes 並再次執行判斷;如果匹配 assword 則輸出 123abc 並結束該段 expect 語句。
expect {
"yes/no" {send "yes\r"; exp_continue;}
"
assword" {set timeout 300; send "123abc\r";}
}
注意:exp_continue允許expect繼續執行自身而不是往下執行.默認情況下,exp_continue會重置timeout,如果不想重置timeout,使用-continue_timer選項。
expect eof 說明
expect {
"password" {
send "$password\r"
exp_continue # 其中exp_continue表示循環式匹配
}
eof # 一旦接收到標識子進程已經結束的eof字符,expect腳本也就退出結束
{
send "eof"
}
}
interact: 執行完成后保持交互狀態,把控制權交給控制台(可以手工操作了)。若沒有這句,登錄完成后就會退出,而不是留在遠程終端上。如果你只是登錄過去執行一段命令就退出,可改為[expect eof]
expect eof: 傳輸一個大文件,腳本最后通過expect eof由於expect eof的超時時間很短,默認10秒,因此很可能導致文件傳輸不完整,解決方法是:將expect eof改成 expect -timeout -1 eof
expect puts 用法
puts輸出到控制台,或輸出到文件.
在shell中嵌套的時候,用puts的話,要注意變量被shell自動展開的問題.調用的時候應該采用以下方式:

!/bin/bash

...
puts "i is $i" # 要""括起來,shell中嵌套的時候,不應該直接puts $i
...
傳參(命令行里的參數傳遞到expect腳本內部)
很多時候,我們需要傳遞參數到腳本中,現在通過下面這段代碼來看看如何在expect中使用參數:

!/usr/tcl/bin/expect

if {$argc < 3} { # $argc表示參數個數
puts "Usage:cmd "
exit 1
}
set timeout -1
set host [lindex $argv 0] # 第一個參數
set username [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $username@$host
expect " password" {send "$password\r"}
interact
在expect中,$argc表示參數個數,而參數值存放在$argv中,比如取第一個參數就是[lindex $argv 0],以此類推。
expect內部變量<$expect_out()>
<$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
shell中嵌套expect 以及 bash與expect 彼此間變量傳遞說明
2019/07/07 update Chenxin
cat test.sh

!/bin/bash

backup_aws="3.1.250.30";
passwd="cx88...adm!@#"
expect <<EOF
set timeout 5
spawn ssh admin@$backup_aws -p 4399
expect {
"refused" {exit 1}
"unreachable" {exit 2}
"No route" {exit 3}
"yes/no" {send "yes\n";exp_continue}
"password:" { send "$passwd\r";sleep 1 }
"
]$" {exit 0}
"
]#" {exit 0}
"
@" {exit 0}
"timeout" {exit 4}
eof {exit 5}
}
expect "
]$" {send "cd /home/admin/tmp\r";}
#expect "
]$" {send "mkdir test_dir\r"; }
expect "
]$" {send "cd ../\r";}
send "echo hello\r";send "echo OK\r"; # dont expect, send immediately
expect "
]$" {send "tail -n 2 uwsgi8000.log\r";sleep 1} # The info of out maybe need some secends for display.
expect "
]$*" {send "cd /opt;ls\r";sleep 1}
interact
EOF
注意,即使本地執行,有時候也需要多嘗試sleep 1,等待1秒的情況,否則輸出還沒來得及看到,就結束了.
bash 與 expect 互相使用彼此變量
如果是在shell中啟動了expect腳本,現在想在expect中使用shell里的變量,可以有兩種方法:
1.首先在shell中進行變量export, 例如export a=1, 然后在expect中通過 $::env(a) 引用,例如set a_exp $::env(a)
2.也可以通過執行子shell調用,例如: set a [exec sh -c {echo $a}]
如果是在expect里面執行shell,並且想在shell里面使用expect的變量,需要在expect里面設置環境變量:
例如:set ::env(LAB) my_lab
那么在shell語句里面可以通過$LAB引用。
附一個完整的主機認證腳本
SSH主機自動認證expect
20121119 Chenxin

!/bin/bash

function renzheng () {
backup_aws="$1";
expect <<EOF
set timeout 5
spawn ssh -i /home/rsyncfiles/.ssh/id_dsa rsyncfiles@$backup_aws -p 4399 echo
expect {
"refused" {exit 1}
"unreachable" {exit 2}
"No route" {exit 3}
"yes/no" {send "yes\n";exp_continue}
"]$" {exit 0}
"]#" {exit 0}
"@" {exit 0}
"timeout" {exit 4}
eof {exit 5}
}
EOF
}
expect命令行指令使用方式
https://www.cnblogs.com/yinghao1991/p/6926125.html
expect命令語法
expect [選項] [ -c cmds] [ [ -[f|b] ] cmdfile] [ args]
可以參考本筆記后面的一些示例腳本的用法.
數字天空游戲服上一些腳本
redis 的啟動與停止
在游戲服啟動之前,需要先啟動 redis服務,如果是關停,則先停止游戲服再停止 redis服;
redis的每次關停后,都會執行備份物理文件操作,放置到 /home/lzadmin/redis_data_bak/下,並清除45天前的備份文件;
cat /usr/local/bin/lzll01_redis_server_manage.exp

!/bin/bash

manage lzll redis server,include the gs,redis cmd,gs log...,except the db script;

By Chenxin 20120727

引入系統環境變量#

if [ -f /etc/init.d/functions ];then
. /etc/init.d/functions;
fi
curr_date=date +%Y%m%d%H%M
export TERM=vt100
cd /home/sftproot/home/;
find /home/sftproot/home/lzupdate/ -maxdepth 2 -name redis__ |awk -F "/" '{print $7}'|grep -v ".sh">/home/lzadmin/redis_server_list.tmp
cd /home/lzadmin/;

start the redis server;

function start_redis_server () {
cd /home/lzadmin/;
for i in cat redis_server_list.tmp
do {
cd /home/sftproot/home/lzupdate/redis_servers/$i
sudo /home/sftproot/home/lzupdate/redis_servers/$i.sh /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf
} done
}

stop the redis server;

function stop_redis_server () {
cd /home/lzadmin/;
for i in cat redis_server_list.tmp
do {
cd /home/sftproot/home/;
redis_pass=cat /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf |grep requirepass|awk '{print $2}'
redis_port=cat /home/sftproot/home/lzupdate/redis_servers/$i/conf/redis.conf |grep port|awk '{print $2}'
expect -c "
set timeout 20
eval spawn screen -S $i
sleep 2
send "/usr/local/redis-2.4.10/src/redis-cli -p $redis_port\r"
expect ">";send "auth $redis_pass\r";
expect "OK";send "save\r";
expect "OK";send "shutdown\r";
expect "
>";send "exit\r";
sleep 2
send "\004"
sleep 2
send "\004"
expect eof"
# 備份物理文件
mkdir -p /home/lzadmin/redis_data_bak # 每次停服都備份一次 redis物理數據文件到lzadmin目錄下
cp -aprf /home/sftproot/home/lzupdate/redis_servers/$i/data/dump.rdb /home/lzadmin/redis_data_bak/dump_$curr_date.rdb
find /home/lzadmin/redis_data_bak/ -mtime +45 -name "*.rdb" -exec rm -rf {} ;
} done
}
case $1 in
start)
start_redis_server;;
stop)
stop_redis_server;;
restart)
stop_redis_server;
start_redis_server;;
*)
echo "(Arguments shuld be start|stop|restart,OK?!)" ;;
esac
游戲服務的啟動與關停
cat /usr/local/bin/lzll02_game_server_manage.exp

!/bin/bash

...
cd /home/lzadmin/;
function start_game_and_log_server () {
cd /home/lzadmin/;
#start the game server;
for i in cat game_server_list.tmp
do {
expect -c "
set timeout 60
eval spawn screen -S $i
sleep 2
send "cd /home/sftproot/home/lzupdate/game_servers/$i/\r"
send "sudo /home/sftproot/home/lzupdate/game_servers/$i/GameServer.sh\r"
expect "cmd>"
sleep 2
send "\001"
send "d"
expect eof"
} done
#start the game server log to file;
for i in cat game_server_list.tmp
do {
expect -c "
set timeout 60
eval spawn screen -S ws_$i
sleep 2
send "cd /home/sftproot/home/lzupdate/game_servers/$i/\r"
send "sudo /home/sftproot/home/lzupdate/game_servers/$i/WriteLogServer.sh\r"
expect "cmd>"
sleep 2
send "\001"
send "d"
expect eof"
} done
}
function stop_game_and_log_server () {
cd /home/lzadmin/;
#stop the game server;
for i in cat game_server_list.tmp
do {
expect -c "
set timeout 60
eval spawn screen -r $i
send "stop\r"
expect "application will exit"
sleep 10
send "\004"
expect eof"
} done
#stop the game log service;
for i in cat game_server_list.tmp
do {
expect -c "
set timeout 60
eval spawn screen -r ws_$i
send "stop\r"
expect "TaskLog finished" # 捕獲到該字符后 ,需要等待10s ,否則如果直接發送 ctrl+d的話,會造成故障;
sleep 10
send "\004"
expect eof"
} done
}
case $1 in
start)
start_game_and_log_server;;
stop)
stop_game_and_log_server;;
restart)
stop_game_and_log_server;
start_game_and_log_server;;
*)
echo "(Arguments shuld be start|stop|restart,OK?!)" ;;
esac
批量獲取服務器序列號
2017/04/20 Chenxin

!/bin/bash

passwd_admin=yRtMHiNc0A5JNnz#
passwd_root=Ywsz098xylz!@#
for i in cat ip.txt
do {
expect << EOF
set timeout 1
spawn ssh -p 4399 admin@$i
expect {
"yes/no" { send "yes\r"; exp_continue }
"password:" { send $passwd_admin\r;sleep 1;exp_continue }
"密碼" { send $passwd_admin\r;sleep 1;exp_continue }
"口令" { send $passwd_admin\r;sleep 1;exp_continue }
"admin@" {
send "su -\r";sleep 1;
expect {
"password:" { send $passwd_root\r;sleep 1;exp_continue }
"密碼:" { send $passwd_root\r;sleep 1;exp_continue}
"口令" { send $passwd_root\r;sleep 1;exp_continue}
"root@" { send "echo -n $i && dmidecode -t 1|grep 'Serial Number'\r" }
}
}
}
expect eof
EOF
} done


免責聲明!

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



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