Shell 程式設計之免互動
一、Here Document免互動
1. Here Document
Here Document使用I/O重定向的方式將命令列表提供給互動式程式或命令,比如ftp、cat或read命令。
Here Document是標準輸入的一種替代品,可以幫助指令碼開發人員不必使用臨時檔案來構建輸入資訊,而是直接就地產生出一個“檔案”並用作“命令”的標準輸入。
Here Document也可以與非互動式程式和命令一起使用。
2. Here Document 的語法格式
命令 <<標記
...
內容 #標記之間是傳入內容
...
標記
3. Here Document使用注意事項
- 標記可以使用任意合法字元(通常為EOF),前後標記相同即可
- 結尾的標記一定要頂格寫,前後都不能有任何字元(包括空格)
- 開頭標記前後的空格會被省略掉
二、Here Document常規用法
1. 統計行數
免互動方式實現對行數的統計,將要統計的內筒置於標記“EOF”之間,直接將內容傳給“wc -l”來進行統計
[root@localhost ~]# wc -l <<EOF
> ABC
> 123
> EOF
2
2. read 賦值
通過read命令接收輸入並列印,輸入值是兩個EOF標記之間的部分,作為變數i的值。
[root@localhost ~]# wc -l <<EOF
> ABC
> 123
> EOF
2
[root@localhost ~]# read i <<EOF
> ABC
> 123
> 321
> eof
> EOF
[root@localhost ~]# echo $i
ABC
由於每一行的輸入都有回車(換行符)的存在,因此Here Document只能賦值一個變數。
3. 修改使用者密碼
修改密碼的方法有:
[root@localhost ~]# useradd zhangsan
[root@localhost ~]# passwd zhangsan
更改使用者 zhangsan 的密碼 。
新的 密碼:
重新輸入新的 密碼:
passwd:所有的身份驗證令牌已經成功更新。
[root@localhost ~]# echo 123456 | passwd --stdin zhangsan
更改使用者 zhangsan 的密碼 。
passwd:所有的身份驗證令牌已經成功更新。
當然,也可通過Here Document免互動進行修改
[root@localhost ~]# passwd zhangsan <<EOF
> 123456
> 123456
> EOF
更改使用者 zhangsan 的密碼 。
新的 密碼:無效的密碼: 密碼少於 8 個字元
重新輸入新的 密碼:passwd:所有的身份驗證令牌已經成功更新。
4. 分行寫入到檔案
[root@localhost ~]# cat > test.txt <<EOF
> ABC
> 123
> EOF
[root@localhost ~]# cat test.txt
ABC
123
注:“cat >"操作也可使用”tee“命令代替
[root@localhost ~]# tee test.txt <<EOF
> 321
> CBA
> EOF
321
CBA
[root@localhost ~]# cat test.txt
321
CBA
4. 變數替換
在寫入檔案時會將變數替換成實際值,再結合cat/tee命令完成寫入
[root@localhost ~]# vim test.sh
#!/bin/bash
file="eat.txt"
i="food"
tee $file <<EOF
i want to eat $i
EOF
[root@localhost ~]# ./test.sh
i want to eat food
5. 變數賦值
整體賦值給變數,然後通過echo命令將變數值打印出來
[root@localhost ~]# cat test1.sh
#!/bin/bash
var="I want to play a game!"
myvar=$(cat <<EOF
Today is Sunday.
$var
EOF
)
echo "$myvar"
[root@localhost ~]# ./test1.sh
Today is Sunday.
I want to play a game!
6. 關閉變數賦值
關閉變數替換的功能,按照字元原本的樣子輸出,不做任何修改或替換
[root@localhost ~]# cat test1.sh
#!/bin/bash
var="I want to play a game!"
myvar=$(cat <<'EOF' #對標記加單引號,即可關閉變數替換
Today is Sunday.
$var
EOF
)
echo "$myvar"
[root@localhost ~]# ./test1.sh
Today is Sunday.
$var
7. 去掉行前的“TAB”字元
[root@localhost ~]# cat test1.sh
#!/bin/bash
var="I want to play a game!"
myvar=$(cat <<-EOF #在標記前加“-”,即可抑制各首行“TAB”
Today is Sunday. #"TAB"
$var #空格
EOF
)
echo "$myvar"
[root@localhost ~]# ./test1.sh
Today is Sunday.
I want to play a game! #對空格無效
8. 多行註釋
Bash的預設註釋是“#”,該註釋方法只支援單行註釋;Here Document的引入解決了多行註釋的問題。
“:”代表什麼都不做的空命令。中間標記區域的內容不會被執行,會被bash
忽略掉,因此可達到批量註釋的效果。
[root@localhost ~]# cat test1.sh
#!/bin/bash
var="I want to play a game!"
: <<EOF #多行註釋,“:”開頭的Here Document標記內容不會被執行
echo ABC
Today is Sunday.
echo $var
EOF
echo "123"
[root@localhost ~]# ./test1.sh
123
三、Expect
1. Expect概述
Expect是建立在tcl語言基礎上的一個工具,常被用於進行自動化控制和測試,解決shell指令碼中互動相關的問題。
2. Expect基本命令
(1)指令碼直譯器
Expect指令碼中首先引入檔案,表明使用的是哪一個Shell。
#!/usr/bin/expect
(2)spawn
spawn後面通常跟一個Linux執行命令,表示開啟一個會話、啟動程序,並跟蹤後續互動資訊。
例如:
spwan passwd root
(3)expect
判斷上次輸出結果結果中是否包含指定的字串,如果有則立即返回,否則就等待超時時間後返回;
只能捕捉由spawn啟動的程序的輸出;
用於接收命令執行後的輸出,然後和期望的字串匹配。
(4)send
向程序傳送字串,用於模擬使用者的輸入;該命令不能自動回車換行,因此需手動寫入“\r”或者“\n”
例如:
方式一:
expect "密碼" {send "123456\r"} #同行send部分需要有{}
方式二:
expect "密碼"
send "123456\r" #換行send部分不需要有{}
方式三:
expect支援多個分支,只要匹配了其中一個情況,執行相應的send語句後退出該expect
語句
expect {
"密碼" {send "123456\r"}
"password" {send "123456\r"}
}
(5)結束符
-
expect eof
表示互動結束,等待執行結束,退回到原使用者,與spawn對應。
比如切換到root使用者,expect指令碼預設的是等待10s,當執行完命令後,預設停留10s後,自動切回了原使用者。 -
interact
執行完成後保持互動狀態,把控制權交給控制檯,若通過ssh建立了遠端連線,interact會停留在目標終端而不會退回到原終端,這個時候就可以手工操作了,interact後的命令不起作用,比如interact後面新增exit,並不會退出root使用者或終端連線。而如果沒有interact則登入完成後會退出,而不是留在遠端終端上。
使用interact會保持在終端而不會退回到原終端,比如切換到root使用者,會一直保持在root使用者狀態下;比如ssh到另一伺服器,會一直在目標伺服器終端,而不會切回原伺服器。
注:expext eof 與 interact 只能二選一使用。
(6)set
expect預設的超時時間是10秒,通過set命令可以設定會話超時時間,若不限制超時時間則應設定為-1.
例如:
set timeout 30
(7)exp_continue
exp_continue附加於某個expect判斷項之後,可以使該項被匹配後,還能繼續匹配該expect判斷語句內的其他項。exp_continue類似於控制語句中的"continue"語句,表示允許expect繼續向下執行指令。
例如:
判斷互動輸出中是否存在(yes/no)或者*password。如果匹配(yes/no)則輸出yes並再次執行判斷;如果匹配*password則設定超時時間300秒並在輸出123456後結束改短expect。
expect {
"(yes/no)" {send "yes\r";exp_contiinue}
"*password" {set timeout 300; send "123456\r";}
}
(8)send_user
send_user表示回顯命令,相當於echo的作用。
(9)接收引數
expect指令碼可以接受從bash命令列傳遞的引數,使用[lindex $argv n]獲得。其中n從0開始,分別表示第一個,第二個,第三個引數,以此類推。
例如:
修改密碼
[root@localhost ~]# cat test2.sh
#!/usr/bin/expect
#設定位置引數傳入
#設定第一個位置引數,相當於username=$1
set username [lindex $argv 0]
#設定第二個位置引數,相當於password=$2
set password [lindex $argv 1]
#開啟命令追蹤程序
spawn passwd $username
#開啟面交互執行,捕捉資訊並匹配
expect "新的 密碼"
send "${password}\r"
expect "重新輸入新的 密碼"
send "${password}\r"
#結束免互動
expect eof
[root@localhost ~]# vim test2.sh
[root@localhost ~]# ./test2.sh zhangsan 123456
spawn passwd zhangsan
更改使用者 zhangsan 的密碼 。
新的 密碼:
無效的密碼: 密碼少於 8 個字元
重新輸入新的 密碼:
passwd:所有的身份驗證令牌已經成功更新。
四、Expect執行方式
1. 直接執行
expect直接執行,需要使用expect命令去執行指令碼
例如:
su切換使用者
[zhangsan@localhost ~]$ cat test3.sh
#!/usr/bin/expect
#設定超時時間
set timeout 5
#引數傳入
set username [lindex $argv 0]
set password [lindex $argv 1]
#開啟追蹤命令
spawn su $username
#免互動執行,捕捉資訊並匹配
expect "密碼"
send "$password\r"
expect "*]#"
send_user "ok"
#把控制權交給控制檯
interact
[zhangsan@localhost ~]$ ./test3.sh root 123456
spawn su root
密碼:
[root@localhost zhangsan]# ok
2. 嵌入執行
嵌入執行模式,將expect過程融入Shell當中,方便執行和處理。
建立使用者並設定密碼
[root@localhost ~]# cat test4.sh
#!/bin/bash
username=$1
password=$2
#非互動命令放在外面
useradd $username
#開始免互動執行
/usr/bin/expect <<EOF
spawn passwd $username
expect "新的*"
send "${password}\r"
expect "重新*"
send "${password}\r"
expect eof
EOF
[root@localhost ~]# ./test4.sh lisi 123456
useradd:使用者“lisi”已存在
spawn passwd lisi
更改使用者 lisi 的密碼 。
新的 密碼:
無效的密碼: 密碼少於 8 個字元
重新輸入新的 密碼:
passwd:所有的身份驗證令牌已經成功更新。
五、Expect案例
免互動自動建立磁碟分割槽
[root@localhost ~]# cat fdisk.sh
#!/usr/bin/expect
set dev [lindex $argv 0]
spawn fdisk $dev
expect "命令" {send "n\n"}
expect "Select" {send "p\n"}
expect "命令" {send "q\n";exit;exp_continue}
expect "分割槽號" {send "\n"}
expect "起始 扇區" {send "\n"}
expect "Last" {send "\n"}
expect "命令" {send "wq\n"}
expect eof
[root@localhost ~]#
[root@localhost ~]#
[root@localhost ~]# ./fdisk.sh /dev/sdb
spawn fdisk /dev/sdb
歡迎使用 fdisk (util-linux 2.23.2)。
更改將停留在記憶體中,直到您決定將更改寫入磁碟。
使用寫入命令前請三思。
命令(輸入 m 獲取幫助):n
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p
分割槽號 (1-4,預設 1):
起始 扇區 (2048-10485759,預設為 2048):
將使用預設值 2048
Last 扇區, +扇區 or +size{K,M,G} (2048-10485759,預設為 10485759):
將使用預設值 10485759
分割槽 1 已設定為 Linux 型別,大小設為 5 GiB
命令(輸入 m 獲取幫助):wq
The partition table has been altered!
Calling ioctl() to re-read partition table.
正在同步磁碟。