背景
有需求,在容許命令或者腳本跳出交互行,須要進行內容輸入,但須要人手動輸入,不是很方便,此時能夠經過expect來實現自動互動交互。javascript
expect是一個自動交互功能的工具,能夠知足代替咱們實際工做中須要從終端手動輸入某些內容來使得程序或命令繼續運行的目的。如安裝軟件是時的一些提示,ssh遠程主機執行命令時須要屢次輸入密碼的狀況。java
安裝expect
- 安裝依賴:
yum install tcl -y
- 安裝expect:Centos系統
yum install expect -y
或Ubuntu系統apt-get install expect -y
一些基本的expect命令
- spawn :啓動新進程,用於執行shell命令;
- expect :從發起交互的命令的進程接受字符串,用於匹配咱們預想的字符串;
- send :用於向發起交互的命令的進程發送字符串;
- interact:容許用戶交互,即此命令後,交互將不會由expect進行,將交回給用戶;
示例
#!/usr/bin/expect set timeout 30 set host "192.168.200.221" set username "root" set password "123456" spawn ssh $username@$host ls expect "password" {send "$password\r"} expect eof interact
#!/usr/bin/expect: 表示使用expect來解釋該腳本。mysql
set timeout 30: 表示設置超時時間,這裏是表示超時時間爲30秒,默認爲10秒,用於執行shell命令的時間,若是執行的shell命令時間較長(如傳輸文件),則須要設置長一點。nginx
set username "root" : 表示設置並定義了變量username,變量值爲"root"。sql
spawn ssh username@username@host ls: 表示使用spawn
來執行ssh $username@$host ls
命令,該命令只有在expect環境裏才能執行,因此直接在命令行輸入或沒有安裝expect則會報錯,它的主要功能是給它後面的shell命令運行進程加了個殼,進行傳遞交互的內容,注意,若是用引號將變量引發,將可能致使錯誤extra characters after close-quote...,若是執行的命令須要用到引號,使用雙引號,並使用\轉義,但只適用於命令中只有一對引號的狀況,若是出現多對引號,將會出現一些奇怪的錯誤,暫時不知道如何解決。shell
如ssh -l root 192.168.200.118 'mysql -uroot -p123456 -e "show datavases;"'
命令。只能先登陸目標主機,再匹配root@ubuntu:~#,send發送命令。ubuntu
#!/usr/bin/expect -f set timeout -1 spawn ssh root@192.168.200.118 expect -re "password" { send "userpwd123\r" } expect -re ":~#" { send "mysql -uroot -p123456\r" } expect -re "mysql>" { send "show databases;\r" } expect -re "mysql>" { exit } expect eof
expect "password": 表示從spawn
執行的命令的進程裏接受字符串,通常是彈出終端的交互行的標準輸入提示信息,如須要你肯定時的(yes/no?),須要你輸入密碼的(...password:)。這裏由於ssh命令的交互內容是叫你輸入密碼,交互提示的內容有password,因此這裏匹配password。須要注意的是,expect接受的是spawn執行的命令進程中可能出現的字符串,若是你的spawn執行的命令在執行完以後直接沒有進程了,那expect也將不能匹配到任何的字符串,如spawn簡單的執行ls等命令,這也說明expect多用於須要執行鏈接的場景。數組
send "$password\r": 表示當expect
命令匹配成功,就把$password
發送給spawn
執行的命令的進程,完成交互,至關於手動輸入$password
,這裏的\r表明回車,也可使用\n,記得加上\r或\n,不然腳本將可能會卡死。bash
expect eof: 表示結束expect
,讀取到文件結束符 ,當spawn發送指令到終端執行時在返回時被expect捕捉時,在起始會有一個eof,就比如在shell中 cat >>file <<EOF... EOF
同樣,在結束時也要有eof;expect eof
有時間限制,即咱們設置的超時時間,默認10秒,不過可能出現的問題是,若是是在傳輸一個大文件,可能在文件還沒傳輸完成便斷開了命令執行,此時須要設置超時時間長一點或 set timeout -1
,或將expect eof
改爲expect -timeout -1 eof
。markdown
interact: 執行完命令後,控制權交互控制檯,此時再有交互,expect將不會進行交互,須要手動進行輸入內容交互。若是沒有這句,在須要交互的ssh命令執行完畢後將會退出遠程,而不是繼續保持在遠程。
expect參數
$argc
:表示命令行參數個數[lindex $argv n]
:表示index爲n的參數(index從0開始計算),index的區間爲左閉右開,如[lindex $argv 0]
表明命令行輸入的第一個參數,[lindex $argv 0 3]
表明命令行輸入的第一到第三個參數 。
示例
#!/usr/bin/expect set host [lindex $argv 0] set username [lindex $argv 1] set num $argc if { num < 3 } { ... }
- 將第一個命令行參數賦值給變量host,將第二個命令行參數賦值給變量username,將總參數個數賦值給變量num。
expect流程控制
if語句:
if {條件1} { expect { "(yes/no)" { send "yes\r" expect "*assword:" {send "$password\r"} } "password" { send "$password\r" } } else { puts "Expect was timeout" send_user "Expect was timeout" }
expect {}: 多行指望,從上往下匹配,匹配成功裏面的哪一條,將執行與之的send
命令,注意,這裏面的匹配字符串只會執行一個,即匹配到的那個,其他的將不會執行,若是想匹配這句命令執行成功後(如登陸成功後等待輸入的root@ubuntu:~#)的其餘字符,須要另起一個expect命令,並保證不在expect{}裏面。
puts與send_user: 打印信息,相似echo
其餘:
- 判斷條件用{}包含
- 花括號與花括號,和括號與控制語句之間須要有空格,不然會報錯expect:extra characters after close-brace
- if右邊要有左花括號,else左邊要有右花括號,不能單獨一行
for語句:
for {set i 0} {$i < 10} {incr i} { puts "I inside first loop: $i" }
while語句:
set i 0 while {$i < 10} { puts "I inside third loop: $i" incr i puts "I after incr: $i" }
incr: 遞增運算符 incr i ,相似++
switch語句:
switch--$var { 0 { 語句塊 } 1 { 語句塊 } ... }
函數定義和調用:
使用proc定義函數,使用時輸入函數名和參數調用
proc test_exp {argv1 argv2} { puts "hello:$argv1" } test_exp 參數1 參數2
expect數組:
set arr(n) "hello" # 賦值,arr爲數組名 set arr(1) "first" $arr(1) # 引用 array size arr # 查看數組大小 注意:若是是shell中插入的一段expect中想使用數組,須要轉義\$,或<<\EOF...EOF
其餘的一些內容
-
使用正則匹配:使用
-re
選項,expect -re "\\\[(.*)]" 其中[在expect shell 正則中都有特殊意義,所以要\三次 ,若是spawn執行的命令不能匹配通配符*,須要在spawn 後加 bash -c。 -
expect -i選項:已交互的方式運行expect。
-
expect -D選項:交互式的調試器,相似gdb。
-
expect -c選項:可執行命令的前置符,expect命令可在命令行執行,該選項-c後的命令須要引號引發來,引號內多個命令分號隔開,可以使用屢次-c選項,空格隔開。
-
expect -f選項:常見於文件第一行,即
#!/usr/bin/expect -f
,指定expect讀取的expect命令文件,可選項,該選項會將文件一次性所有讀取入內存,加上-f選項能夠爲執行expect提供更多參數。 -
expect -b選項:相似-f選項,只是每次只讀取一行,便可以逐行的執行expect。
-
拼接字符串:使用append命令
append "hello"$user",welcome!"
-
sleep:腳本進入睡眠,使用和其餘語言同樣,直接跟數字便可,單位爲秒。
-
exit:退出
-
foreach:對指定集合的每個元素,依次賦值給變量。
foreach [變量] {集合} {語句;} foreach i {1 2 3} { puts $i } 輸出:1 2 3
-
exp_continue
: 循環匹配,一般匹配以後會退出語句,但使用exp_continue
則能夠不斷循環執行某段語句。expect { "password" { send "$password\r" exp_continue # 不斷匹配字符串"password",只要匹配成功就send } } expect eof
-
shell 嵌套使用expect,使用重定向,須要注意EOF之間的互相對應,而且變量須要在shell中定義,否者將會找不到變量,expect引用變量部分將是空內容,如同變量消失。若是想使在expect裏定義的變量生效,使用<<\EOF...EOF,或用引號將第一個EOF引發來,即<<"EOF"...EOF,這樣expect中set定義的變量,遍歷時賦值的變量以及expect數組就都能使用了,可是相對的,shell裏定義的變量也就不能使用了。
#!/bin/bash hostname=$1 #接收第一個參數 password=$2 /usr/bin/expect <<-EOF # 重定向到expect,想使用expect中set定義的變量,須要轉義\$ spawn ssh root@${hostname} # 或使用\EOF,但若是是\EOF,將不能使用Shell的變量 expect { "(yes/no)" { send "yes\r" expect "*assword:" {send "$password\r"} } "password" { send "$password\r" } } expect eof EOF # 因爲用的-EOF,這裏的EOF能夠有空格,tab鍵 /usr/bin/expect <<EOF set m_pm(1) "hello" set m_pm(2) "world" puts "\$m_pm(2)" foreach i {1 2 3} { puts \$i } expect eof EOF
-
excpet中執行shell語句,
exec sh -c {shell語句}
,多用於賦值變量,須要注意的是,expect裏使用exec執行的shell語句,即便有打印和交互內容(echo,read命令)也不會輸出到終端,即執行了命令,你並不知道是否出錯,也不知道執行結果,若是須要將shell中echo命令打印的內容輸出到終端,只能將執行結果賦值給expect變量,再使用puts命令打印出來,但即便這樣,也會出現一些莫名頭疼的問題,因此儘可能不要在expect中調用複雜的shell語句。你也可使用匹配字符,send 「命令\r」 的方式執行shell命令,至關於交互互動,如expect ":~#" { send "ls\r" }
匹配到root登陸後的終端待輸出狀態,send發送ls命令並回車。exec sh -c {shell 命令} # 執行的shell命令即便有打印和須要交互的內容也不會出如今終端 set test_echo [exec sh -c {echo "test"}] puts "$test_echo"
-
expect/shell互相使用彼此變量
-
若是二者在同一文件中,二者只是做爲一段語句存在,使用
#!/bin/bash
解釋的shell文件,expect調用shell變量直接$變量
,和shell腳本調用變量方式並沒有異同,使用#!/usr/bin/expect
解釋的expect腳本文件,shell做爲expect文件的語句,如set a [exec sh -c {echo \$LAB}]
調用expect變量,須要在expect裏面設置環境變量。如:
set ::env(LAB) my_lab
- 若是二者是分別爲不一樣文件,expect做爲腳本在shell腳本文件中被調用,如
./test.excp
,首先須要在shell中進行變量export, 例如export a="test"
, 而後在expect腳本文件中經過 $::env(a) 引用shell腳本文件的變量,例如set a_exp \$::env(a)
,同時也能夠經過執行子shell調用,例如:set a [exec sh -c {echo $a}]
-
-
向進程發送
Ctcl + c
,若是想向遠端發送Ctrl-C結束遠端進程,能夠經過send "\003" 實現。
參考博客: