1. 程式人生 > 其它 >Shell 程式設計之免互動

Shell 程式設計之免互動

一、Here Document免互動

1. Here Document

Here Document使用I/O重定向的方式將命令列表提供給互動式程式或命令,比如ftp、cat或read命令。
Here Document是標準輸入的一種替代品,可以幫助指令碼開發人員不必使用臨時檔案來構建輸入資訊,而是直接就地產生出一個“檔案”並用作“命令”的標準輸入。
Here Document也可以與非互動式程式和命令一起使用。

2. Here Document 的語法格式

命令 <<標記
...
內容      #標記之間是傳入內容
...
標記

3. Here Document使用注意事項

  1. 標記可以使用任意合法字元(通常為EOF),前後標記相同即可
  2. 結尾的標記一定要頂格寫,前後都不能有任何字元(包括空格)
  3. 開頭標記前後的空格會被省略掉

二、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)結束符

  1. expect eof
    表示互動結束,等待執行結束,退回到原使用者,與spawn對應。
    比如切換到root使用者,expect指令碼預設的是等待10s,當執行完命令後,預設停留10s後,自動切回了原使用者。

  2. 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.
正在同步磁碟。