1. 程式人生 > 其它 >(轉)linux expect 詳解

(轉)linux expect 詳解

原文:https://blog.csdn.net/zxycyj1989/article/details/125837697

介紹
expect 是由Don Libes基於Tcl(Tool Command Language )語言開發的,主要應用於自動化互動式操作的場景,藉助Expect處理互動的命令,可以將互動過程如:ssh登入,ftp登入等寫在一個指令碼上,使之自動化完成。尤其適用於需要對多臺伺服器執行相同操作的環境中,可以大大提高系統管理人員的工作效率

常用指令
命令速查
spawn:互動程式開始後面跟命令或者指定程式(在殼內啟動這個程序)
expect:獲取匹配資訊匹配成功則執行expect後面的程式動作(檢測由殼內程序發出的特定互動指令反饋字串後向下執行)
send:用於向程序傳送字串(從殼外向殼內程序傳送一條字串,換行符為確認結束)
interact:允許使用者互動
exp_continue:在expect中多次匹配就需要用到
send_user:用來列印輸出 相當於shell中的echo
exit:退出expect指令碼
eof:expect執行結束 退出
set:定義變數
puts:輸出變數
set timeout:設定超時時間
例項
1.簡單地例子
看一個簡單地例子,該例子就是自動登入到主機上

#!/usr/bin/expect
spawn ssh [email protected]
expect "*password:"

send "111111\r"
expect "#"

send "exit \r"
expect eof
1
2
3
4
5
6
7
8
9
如下執行:

expect test.ctl
1
輸出如下:

spawn ssh [email protected]
[email protected]'s password:
Last login: Sat Jul 16 02:44:48 2022 from 127.0.0.1
[root@zxy ~]# exit
logout
Connection to 127.0.0.1 closed.
1
2
3
4
5
6
1. send命令
send命令接收一個字串引數,並將該引數傳送到程序(這個指令的前提是先使用spawn開啟的程序)。

基礎例項中,我們看到有2處使用了send。

......
send "111111\r"
......
send "exit \r"
1
2
3
4
其實就是spawn打開了一次ssh連線以後,會要求我們輸入登陸密碼,第一個send就是將密碼傳送到spawn的程序中。第二個send就是退出ssh的意思。

如果沒有開啟一個程序,會怎麼樣?我們試一下:

#!/usr/bin/expect

send "date \n"
1
2
3
執行後的結果為:

date
1
好像直接把文字遠樣輸出出來了!!!其實和平時我們寫shell指令碼一樣,我們習慣的會在第一行#!/bin/bash,其實是在告訴程式,後面的程式碼要用bash來解釋。所以我們平時在bash的腳本里面,date會被解釋成一條有意義的命令。但是#!/usr/bin/expect的時候,date是沒有這個意義的,所以遠樣輸出了。

2. expect
expect命令和send命令正好相反,expect通常是用來等待一個程序的反饋。expect可以接收一個字串引數,也可以接收正則表示式引數。比如root登陸的之後,介面會輸出一個#,那麼expect此時匹配的是這個#。ssh登陸後,一般shell會返回一個xxxpassword:的輸出,那麼此時可以匹配password的字元。

而如果我們沒有通過spawn開啟一個ssh或者類似的ssh程序,而是直接在expect程式裡面expect一個字串的時候,會怎麼樣?

#!/usr/bin/expect

expect "hello" { send "hello world\n"}
1
2
3
這個例子,我們改變了send的寫法,放在了expect後面使用花挎號誇起來了。這時候,當你執行指令碼的時候,你會發現,除非你再鍵盤上輸出hello,然後確認,才會輸出“hello world”。

這裡做個對比,上述寫法和下面的對比一下:

#!/usr/bin/expect

expect "hello"
send "hello world\n"
1
2
3
4
我們會發現,第二種寫法,不管我們在不在鍵盤上輸入hello,或者輸入什麼,都會一段時間後,輸出【hello world】。

原因其實是:expect是方法是tcl語言的模式-動作。正常的用法是類似第一種,匹配到指定的字元時,執行指定的動作。匹配有2中匹配方式:

2.1 單一分支匹配
類似於上述例子裡面的:

expect "hello" { send "hello world\n"}
1
單一匹配就是隻有一種匹配情況。有點類似於普通程式語言的if語句,只有一個條件的情況。

2.2 多分支匹配模式
類似於普通變成語言的多個if條件的情況。這種情況有2種寫法:

expect "hello" {send "hello world\n"} "hi" {send "hi world"} "bye" {send "bye world"}
1
第二種寫法:

#!/usr/bin/expect
set timeout 5

expect {
"hello" {send "hello world\n"}
"hi" {send "hi world"}
"bye" {send "bye world"}
}
1
2
3
4
5
6
7
8
第二種寫法形式上會更簡潔易讀。我們會發現,expect語言的都是用{}來做程式碼分割和程式碼塊分割的。

spawn
最開始的ssh案例裡面,我們使用spawn開啟了一個ssh程序,然後使用send輸入了密碼。我們再多加一個命令,檢視登陸的機器的hostname看看:

#!/usr/bin/expect
spawn ssh [email protected]
expect "*password:"

send "111111\r"
expect "#"

send "hostname \r"
expect "#"

send "exit \r"
expect eof
1
2
3
4
5
6
7
8
9
10
11
12
spawn開啟一個ssh以後,會進入到一個linux的shell環境下,這時候向程序傳送一個hostname這樣的字串,shell就能夠識別出這是一個有意義的指令,並返回指令的結果。

spawn ssh [email protected]
[email protected]'s password:
Last login: Sat Jul 16 22:19:58 2022 from 127.0.0.1
[root@zxy ~]# hostname
zxy
[root@zxy ~]# exit
logout
Connection to 127.0.0.1 closed.
1
2
3
4
5
6
7
8
interact
上述舉的例子都是自動完了一些動作。有時候,我們可能會發生,停留在介面,等待人工操作的情況。這時候,我們可以用interact指令,來等待人工干預。

#!/usr/bin/expect
spawn ssh [email protected]
expect "*password:"

send "111111\r"
expect "#"

send "hostname \r"
expect "#"
interact
1
2
3
4
5
6
7
8
9
10
該例子執行完hostname以後,會停留在expect開啟的ssh介面,等待人工操作。

set
該指令是用來設定變數值。比如,我們改造一下剛開始的基礎示例中的指令碼:

#!/usr/bin/expect
set uname root
set pwd 111111
spawn ssh ${uname}@127.0.0.1
expect "*password:"

send "${pwd}\r"
expect "#"

send "exit \r"
expect eof
1
2
3
4
5
6
7
8
9
10
11
傳參
很多場景下,我們寫一個指令碼都是要傳遞引數的。expect也不例外。expect 有2個內建變數:a r g c 和 argc和argc和argv。a r g c 表示引數的數量。類似於普通 s h e l l 指令碼的 argc表示引數的數量。類似於普通shell指令碼的argc表示引數的數量。類似於普通shell指令碼的#。而$argv則可以給自身傳遞一個整數,取出指定位置的引數。例如:

#!/usr/bin/expect
set uname [lindex $argv 0]
set pwd [lindex $argv 1]
puts "$argc"
spawn ssh ${uname}@127.0.0.1
expect "*password:"

send "${pwd}\r"
expect "#"

send "exit \r"
expect eof
1
2
3
4
5
6
7
8
9
10
11
12
我們入戲執行

expect test.ctl root 111111
1
結果為:

2
spawn ssh [email protected]
[email protected]'s password:
Last login: Sun Jul 17 02:18:56 2022 from 127.0.0.1
[root@zxy ~]# exit
logout
Connection to 127.0.0.1 closed.
1
2
3
4
5
6
7
expect還有一個非常重要的內建變數,也是和引數有關係的:a r g v 0 。上面我們說過, argv0。上面我們說過,argv0。上面我們說過,argv是儲存了所有傳遞進來的引數的變數。而$argv0則是指令碼的名稱。這個和shell指令碼的$0一個意思。

puts "------argv0--------"
puts "$argv0"
1
2
輸出如下:

------argv0--------
test1.ctl
1
2
腳本里面,出現了一個put指令,這個指令是向標準輸出輸出內容

從結果看,我們傳遞了2個引數,puts指令將$argc輸出到了螢幕,值是2,表示有2個引數。同事,我們通通lindex $argv [int],獲取到指定位置的引數值。

incr
增量。該質量一般用在數學計算的時候可以用到,語法位為:incr [arg] {step},arg是要增加的引數名,step是增量值,可以不指定,不指定為1。如下:

puts "------incr-------"
set x 10
puts "$x"
incr x 5
puts "$x"
1
2
3
4
5
結果如下:

------incr-------
10
15
1
2
3
其實這裡的【incr x 5】也就相當於【set x [expr $x + 5]】

puts "--------計算---------"
set x 5
set x [expr $x + 5]
puts $x
1
2
3
4
執行結果如下:

--------計算---------
10
1
2
運算[]
不確【運算】定這種描述是否準確,因為沒有查到相關的資料,所以暫且這麼認為吧。在shell中,我們會使用``或者( ) 將可執行的命令列包裹起來,這樣包裹起來的部分,在執行的時候, s h e l l 會把它當成要執行的部分來執行。我們可以把 [ ] 理解為類似 s h e l l 中 ‘ ‘ 或者 ()將可執行的命令列包裹起來,這樣包裹起來的部分,在執行的時候,shell會把它當成要執行的部分來執行。我們可以把[]理解為類似shell中``或者()將可執行的命令列包裹起來,這樣包裹起來的部分,在執行的時候,shell會把它當成要執行的部分來執行。我們可以把[]理解為類似shell中‘‘或者()的功能。

例如:

訪問陣列
puts [lindex $argv 1]
1
數學計算的時候
puts [expr $x + 5]
1
分割字串的時候
set ss "aa,bb,cc,dd"
puts [split $ss ","]
1
2
為了說明,[]這玩意的確類似shell的``和$(),我們這麼幹:上面我們舉了個數學計算的例子。我們把puts去掉會怎麼樣?

#!/usr/bin/expect
set timeout 5

[expr 3 + 5]
1
2
3
4
執行的時候,報了個錯誤:

invalid command name "8"
while executing
"[expr 3 + 5]"
(file "test1.ctl" line 3)
1
2
3
4
我們暫且先這麼理解吧。如果後面看到了相關的權威解釋,再糾正說法吧。

陣列
在傳參的主題裡面,我們提到了所有傳遞的引數,被存放在a r g v 中。其實 argv中。其實argv中。其實argv就是陣列。

陣列的定義
陣列的定義,需要結合set的指令。

set j "a b c d"
1
陣列的所有元素之間,需要用空格隔開。

陣列訪問
在傳參的主題裡面,我們使用[lindex $argv 0]的方法,獲取了第0個引數。這個正也是資料的訪問方法:

#!/usr/bin/expect
set timeout 5
set j "a b c d e"
puts "[lindex $j 2]"
1
2
3
4
那我們如何遍歷陣列呢?這裡我們可以使用foreach的方法:

set j "a b c d e"
foreach jj $j {
puts "$jj"
}
1
2
3
4
迴圈
和其他普通語言一樣,expect也是支援for、whle、foreach這樣的迴圈語言的。用法如下

for迴圈
expect的for訓話,可c語言或者shell的for迴圈有些類似。只不過不是使用的符號有些不一樣。

比如shell,我們是這麼寫的:

for((i=1;i<=10;i++));do
echo $i
done
1
2
3
而expect語言則如下寫法:

puts "------遞增-----"
for {set i 0} { $i < 5 } { incr i } {
puts "$i"
}
puts "------遞減-----"
for {set k 5} { $k > 0 } { incr k -1 } {
puts "$k"
}
1
2
3
4
5
6
7
8
這裡要注意【incr k -1】的寫法,中間是有一個空格的。意思是k每次增加-1。incr i其實等於incr i 1。

while寫法
puts "------while遞增-----"
set m 0
while {$m < 5} {
puts "$m"
incr m 2
}
1
2
3
4
5
6
foreach寫法
foreach可以認為和php的有些類似,php的是為了便利陣列用的。expect也是如此。例如,我們想要遍歷傳遞的所有引數:

#!/usr/bin/expect
set timeout 5
puts "------遍歷argv--------"
foreach arg $argv {
puts "$arg"
}
1
2
3
4
5
6
當然遍歷所有引數,我們還可以這麼遍歷:

#!/usr/bin/expect
set timeout 5
for {set y 0 } { $y < $argc} {incr y} {
puts "arg $y: [lindex $argv $y]"
}

1
2
3
4
5
6
shell呼叫expect
我們將基礎例項的程式碼稍作修改:

#!/bin/bash
ip="$1"
pawd="$2"
expect << EOF
set timeout 10
spawn ssh root@${ip}
expect {
"yes/no" {send "yes\n"; exp_continue}
"password:" {send "${pawd}\n"}
}
expect "#" {send "hostname\n"}
expect "#"
send "exit\n"
expect eof
EOF
echo "end!"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
然後如下執行:

sh shell.sh 127.0.0.1 111111
1
這時候,如果是第一次登陸機器,會提示輸入yes/no。也能夠自動輸入。結果如下:

spawn ssh [email protected]
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:zYqdNqHNR510qlQjUaSQj9IYlWhuWDhbi0Sq94nhhV0.
ECDSA key fingerprint is MD5:28:35:80:b8:45:11:b1:85:5c:ad:c8:94:7b:eb:a9:fb.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts.
[email protected]'s password:
Last login: Sun Jul 17 02:26:05 2022 from 127.0.0.1
[root@zxy ~]# hostname
zxy
[root@zxy ~]# exit
logout
Connection to 127.0.0.1 closed.
end!
————————————————
版權宣告:本文為CSDN博主「初碼誅仙」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/zxycyj1989/article/details/125837697