使用expect實現自動交互,shell命令行自動輸入,腳本自動化,變量引用,expect spawn執行帶引號命令,expect 變量為空,不生效,不能匹配通配符*,函數,數組


背景

有需求,在允許命令或者腳本跳出交互行,需要進行內容輸入,但需要人手動輸入,不是很方便,此時可以通過expect來實現自動互動交互。

expect是一個自動交互功能的工具,可以滿足代替我們實際工作中需要從終端手動輸入某些內容來使得程序或命令繼續運行的目的。如安裝軟件是時的一些提示,ssh遠程主機執行命令時需要多次輸入密碼的情況。

安裝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來解釋該腳本。

set timeout 30: 表示設置超時時間,這里是表示超時時間為30秒,默認為10秒,用於執行shell命令的時間,如果執行的shell命令時間較長(如傳輸文件),則需要設置長一點。

set username "root" : 表示設置並定義了變量username,變量值為"root"。

spawn ssh $username@$host ls: 表示使用spawn來執行ssh $username@$host ls 命令,該命令只有在expect環境里才能執行,所以直接在命令行輸入或沒有安裝expect則會報錯,它的主要功能是給它后面的shell命令運行進程加了個殼,進行傳遞交互的內容,注意,如果用引號將變量引起,將可能導致錯誤extra characters after close-quote...,如果執行的命令需要用到引號,使用雙引號,並使用\轉義,但只適用於命令中只有一對引號的情況,如果出現多對引號,將會出現一些奇怪的錯誤,暫時不知道如何解決。

ssh -l root 192.168.200.118 'mysql -uroot -p123456 -e "show datavases;"' 命令。只能先登錄目標主機,再匹配root@ubuntu:~#,send發送命令。

#!/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,否則腳本將可能會卡死。

expect eof: 表示結束expect,讀取到文件結束符 ,當spawn發送指令到終端執行時在返回時被expect捕捉時,在起始會有一個eof,就好比在shell中 cat >>file <<EOF... EOF一樣,在結束時也要有eof;expect eof有時間限制,即我們設置的超時時間,默認10秒,不過可能出現的問題是,如果是在傳輸一個大文件,可能在文件還沒傳輸完成便斷開了命令執行,此時需要設置超時時間長一點或 set timeout -1,或將expect eof改成expect -timeout -1 eof

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" 實現。

參考博客:

果凍想-Linux expect詳解

taoyuanforrest-expect使用技巧


免責聲明!

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



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