前言
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