Linux expect命令詳解


前言

​ expect是一種腳本語言,它能夠代替人工實現與終端的交互,開發人員不必再守候在電腦旁邊輸入密碼,或是根據系統的輸出再運行相應的命令。

​ 借助expect,可以將交互過程寫在一個腳本上,使之自動化完成所需要的交互操作。形象的說,像ssh登錄,ftp登錄等都符合交互的定義。

1.判斷當前系統是否安裝expect環境:which expect,一般是安裝在/usr/bin/expect目錄下

2.沒有則進行安裝:yum install expect -y進行安裝

3.使用場景

  • shell腳本實現ssh自動登錄遠程服務器
#!/usr/bin/expect

spawn ssh 登錄用戶名@目標主機
expect "*password:"
send "密碼\r"
expect "*#"
interact

下文我們首先提出一個問題,然后介紹基礎知四個命令,最后提出解決方法。

問題場景

如何從A機器ssh登錄到B機器上,然后執行B機器上的命令?如何使之自動化完成?

命令介紹

Expect中最關鍵的四個命令是send,expect,spawn,interact

spawn:交互程序開始后面跟命令或者指定程序(在殼內啟動這個進程)
expect:獲取匹配信息匹配成功則執行expect后面的程序動作(檢測由殼內進程發出的特定交互指令反饋字符串后向下執行)
send:用於向進程發送字符串(從殼外向殼內進程發送一條字符串,換行符為確認結束)
interact:允許用戶交互

其它命令

exp_continue        在expect中多次匹配就需要用到
send_user           用來打印輸出 相當於shell中的echo
exit                退出expect腳本
eof                 expect執行結束 退出
set                 定義變量
puts                輸出變量
set timeout         設置超時時間

1. send命令

send命令接收一個字符串參數,並將該參數發送到進程(想象給目標進程加了一層殼,從殼外面send密碼到殼內的進程,這個過程類似模擬人類輸入密碼)。

expect1.1> send "hello world\n" 
hello world

2. expect命令

(1)基礎知識

expect命令和send命令正好相反,expect通常是用來等待一個進程的反饋。expect可以接收一個字符串參數,也可以接收正則表達式參數。和上文的send命令結合,現在可以看一個最簡單的交互式的例子:

#!/usr/bin/expect

expect "hi\n"
send "hello there!\n"

這兩行代碼的意思是:從標准輸入中等到hi和換行鍵后,向標准輸出輸出hello there! 。

tips: $expect_out(buffer)存儲了所有對expect的輸入,<$expect_out(0,string)>存儲了匹配到expect參數的輸入。

比如如下程序:

#!/usr/bin/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 expected: hi

(2)模式-動作

expect最常用的語法是來自tcl語言的模式-動作。這種語法極其靈活,下面就各種語法分別說明。

單一分支模式語法:

expect "hi" {send "You said hi"}

殼內進程發出的反饋字符串被殼外的expect匹配到hi字符串后,會輸出"you said hi"

多分支模式語法:

#!/usr/bin/expect

expect "hi" { send "You said hi\n" } "hello" { send "Hello yourself\n" } "bye" { send "That was unexpected\n" }

匹配到hi,hello,bye任意一個字符串時,執行相應的輸出。等同於如下寫法:

#!/usr/bin/expect

expect {
"hi" { send "You said hi\n"}
"hello" { send "Hello yourself\n"}
"bye" { send "That was unexpected\n"}
}

3. spawn命令

上文的案例都是和標准輸入輸出進行交互,但是我們跟希望他可以和某一個進程進行交互。spawm命令就是用來啟動新的進程的。spawn后的send和expect命令都是和spawn打開的進程進行交互的。結合上文的send和expect命令看一下更復雜的程序。

#!/usr/bin/expect

set timeout -1
spawn ftp ftp.test.com      //打開新的進程,該進程用戶連接遠程ftp服務器
expect "Name"             //進程返回Name時
send "user\r"        //向進程輸入anonymous\r
expect "Password:"        //進程返回Password:時
send "123456\r"    //向進程輸入don@libes.com\r
expect "ftp> "            //進程返回ftp>時
send "binary\r"           //向進程輸入binary\r
expect "ftp> "            //進程返回ftp>時
send "get test.tar.gz\r"  //向進程輸入get test.tar.gz\r

這段代碼的作用是登錄到ftp服務器ftp ftp.uu.net上,並以二進制的方式下載服務器上的文件test.tar.gz。

4.interact命令

到現在為止,已經可以結合spawn、expect、send自動化的完成很多任務了。但是,如何讓人在適當的時候干預這個過程呢。比如下載完ftp文件時,仍然可以停留在ftp命令行狀態,以便手動的執行后續命令。interact可以達到這些目的。下面的案例在自動登錄ftp后,允許用戶交互。

#!/usr/bin/expect

spawn ftp ftp.test.com
expect "Name"
send "user\r"
expect "Password:"
send "123456\r"
interact

解決最開始的問題

如何從A機器上ssh登錄到B機器上,然后執行B機器上的命令?如何使之自動化完成?

下面一段腳本實現了從機器A登錄到機器B,然后執行機器B上的pwd命令,並停留在B機器上,等待用戶交互。具體含義請參考上文。執行方法 。./test.sh 用戶名 主機地址 密碼

#!/usr/bin/expect -f
# 設置超時
set timeout -1  
# 殼內啟動ssh進程,取到命令行傳遞的第一個參數作為用戶名,取出命令行的第二個參數作為主機地址
spawn ssh [lindex $argv 0]@[lindex $argv 1]
# 取出命令行傳遞的第三個參數作為密碼
expect  "*password:" { send "[lindex $argv 2]\r" }
expect  "$*" { send "pwd\r" }
interact

expect -f 參數?

expect腳本的開頭一般都寫/usr/bin/expect -f,這個-f選項有什么作用呢?

比如如下腳本

#!/usr/bin/expect -f

# 定義變量i,默認值為0,將參數總個數$argc賦值給i
for {set i 0} {$i < $argc} {incr i} {
    # 輸出 每一個參數
    puts "arg $i: [lindex $argv $i]"  
}  

運行./test.sh -c "puts foo" hehe bar輸出如下

(此處其實沒有搞明白foo是為什么會輸出,推測可能是將puts foo解釋為輸出foo字符串這個命令了,expect中的puts類似bash中的echo)

foo
arg 0: hehe
arg 1: bar

如果改成#!/usr/bin/expect,則輸出如下(expect將命令行傳遞的內容以空格符為分隔全部理解為參數了)

arg 0: -c
arg 1: puts foo
arg 2: hehe
arg 3: bar

腳本示例

  • ssh遠程登錄主機執行命令,在shell腳本中執行expect命令
#!/bin/bash

passwd='登錄密碼'

/usr/bin/expect <<EOF

set time 30
spawn ssh 登錄用戶@主機地址 df -Th
expect {
"*yes/no" { send "yes\r"; exp_continue }
"*password:" { send "$passwd\r" }
}
expect eof
EOF
  • expect執行多條命令
#!/usr/bin/expect -f

set timeout 10
spawn sudo su - root
expect "*password*"
send "登錄密碼\r"
expect "#*"
send "ls\r"
expect "#*"
send "df -Th\r"
send "exit\r"
expect eof
  • 創建ssh key,將id_rsa和id_rsa.pub文件分發到各台主機上面。
# 1.創建主機配置文件
[root@localhost script]# cat host
192.168.1.10 root 123456
192.168.1.20 root 123456
192.168.1.30 root 123456

[root@localhost script]# ls
copykey.sh  hosts

# 2.編寫copykey.sh腳本,自動生成密鑰並分發key.
[root@localhost script]# vim copykey.sh

#!/bin/bash

# 判斷id_rsa密鑰文件是否存在
if [ ! -f ~/.ssh/id_rsa ];then
 ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
else
 echo "id_rsa has created ..."
fi

#分發到各個節點,這里分發到host文件中的主機中.
while read line
  do
    user=`echo $line | cut -d " " -f 2`
    ip=`echo $line | cut -d " " -f 1`
    passwd=`echo $line | cut -d " " -f 3`
    
    expect <<EOF
      set timeout 10
      spawn ssh-copy-id $user@$ip
      expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$passwd\n" }
      }
      expect "password" { send "$passwd\n" }
    EOF
  done <  hosts
  • shell調用expect執行多行命令
#!/bin/bash 

ip=$1  
user=$2 
password=$3 

expect <<EOF  
    set timeout 10 
    spawn ssh $user@$ip 
    expect { 
        "yes/no" { send "yes\n";exp_continue } 
        "password" { send "$password\n" }
    } 
    # 創建用戶
    expect "]#" { send "useradd \n" } 
    # 創建臨時文件
    expect "]#" { send "touch /tmp/test.txt\n" } 
    # 退出
    expect "]#" { send "exit\n" } expect eof 
 EOF
# 執行方法
#./test.sh 192.168.8.8 root root 

參考:

https://www.cnblogs.com/lqyye/p/7224268.html

https://www.cnblogs.com/mingyunrangwozoudaoxianzai/p/11208887.html


免責聲明!

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



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