第十二章 腳本進階

1.SHELL腳本編程進階

1.1 循環

1.1.1 循環執行介紹

在這裏插入圖片描述

將某代碼段重複運行多次,通常有進入循環的條件和退出循環的條件

重複運行的次數

  • 循環次數事先已知
  • 循環次數事先未知

常見的循環的命令:for,while,until

1.2 for循環

格式1:

for 變量名 in 列表;do
	循環體;
done

格式2:

for((exp1;exp2;exp3));do
	循環體
done	

例:輸出1~100

for i in {1..100};do
	echo ${i};
done

for((i=1;i<=100;i++));do
	echo ${i};
done	

執行機制:依次將列表中的元素賦值給“變量名”;每次賦值後執行一次循環體;直到列表中的元素耗盡,循環結束。

for循環列表生成方式:

  • 直接給出列表
  • 整數列表

1.1.2 三個範例

1.創建YYYY-MM-DD文件夾,在YYYY-MM-DD下隨機創建日誌文件

#!/bin/bash
for i in {1..365};
do
	DIR=`date -d "-$i days" +%F`
	mkdir /data/test/$DIR;
	for j in {1..10};
	do 
		touch /data/test/$DIR/$RANDOM.log
	done
done

2.將YYYY-MM-DD文件下得文件移動到YYYY-MM/DD下

#!/bin/bash

DIR=/data/test
cd ${DIR}

for DIR in *;do
	YYYY_MM=`echo ${DIR} | cut -d- -f1,2`;
	DD=`echo ${DIR} | cut -d- -f3`;
	if [ -d /data/test/${YYYY_MM}/${DD} ]; then
		mkdir /data/test/${YYYY_MM}/${DD} -p
	fi
	mv /data/test/$DIR/* /data/test/${YYYY_MM}/${DD}
done

3.99乘法表

#!/bin/bash

for i in {1..9};do
	for j in `seq $i`;do
		x=$[i*j]
		echo -e "${i}x${j}=${x} \c\t"; 
	done
	echo
done

#格式二
for((i=1;i<=9;i++)); do 					for((j=1;j<=i;j++)); do 
		echo -e "${i}*${j}=$[i*j]\t\c"; 	done;
	echo""; 
done;

1.2.3 練習:用for實現

1、判斷/var/目錄下所有文件的類型

2.添加10個用戶user 1-user10,密碼爲8位隨機字符

3./etc/rc.d/rc3.d目錄 下分別有多個以K開頭和以S開頭的文件:分別讀取每個文件,以K開頭的輸出爲文件加stop,以S開頭的輸出爲文件名加start,如K34filename stop 66filename start

4、編寫腳本,提示輸入正整數n的值,計算1+2…+n的總和

5、計算100以內所有能被3整除的整數之和

6、編寫腳本,提示請輸入網絡地址,如192.168.0.0,判斷輸入的網段中主機在線狀態

7.打印九九乖法表

8、在/testdir目錄 下創建10個html文件,文件名格式爲數字N (從1到10) 加隨機8個字母,如: 1AbCdeFgH.html

9、打印等腰三角形

10、猴子第一天摘下若干個桃子,當即吃了一半,還不癌,又多吃了一個。第二天早上又將剩下的桃子吃掉一半,又多吃了一個。以後每天早上都吃了前一天剩下的一半零-個。到第10天早上想再吃時,只剩下一個桃子了。求第一天共摘了多少?

1.2 while循環

格式:

while CONDITION;do
	循環體
done	

說明:

CONDITION:循環控制條件;進入循環之前,先做一次判斷;每一次循環之後會再次做判斷;條件爲”true“,則執行一次循環;直到條件測試狀態爲”false“終止循環,因此:CONDTION一般應該有循環控制變量;而此變量值會在循環體不斷地被修正

進入條件:CONDITION爲true

退出條件:CONDITION爲false

1.2.1 範例

​ 磁盤大於80%發送郵件

#!/bin/bash
SECOND=60
SIZE=80
#':' 和 'true'一樣地意義
while :;do
	disk=`df | sed -rn "/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p"`
	echo ${disk}
	if [ ${disk} -ge ${SIZE} ] ;then 
		echo "disk 要滿了" | `mail -s disk_warning [email protected]`
	fi
	sleep ${SECOND};
done

1.2.2 無限循環

格式1:

while true;do
	循環體
done

格式2:

while : do
	循環體
done	

1.2.3 while 特殊用法

while循環的特殊用法,遍歷文件或文本地每一行

格式:

while read line; do
	循環體
done < /PATH/FROM/SOMEFILE	

例:


#!/bin/bash

while read line; do
        if [[ ${line} =~ nologin$ ]] ;then
                echo $line | cut -d: -f1,7
        fi
done < /etc/passwd

說明,依次讀取/PATH/FROMSOMFILE文件中的每一行,且將行賦值給變量line

範例:

​ read命令

[root@centos7: ~]#echo magedu | read X; echo $X

[root@centos7: ~]#echo magedu | while read X;do echo $X;done
magedu
[root@centos7: ~]#echo magedu | { read X;echo $X; }
magedu
[root@centos7: ~]#echo magedu | ( read X;echo $X; )
magedu
[root@centos7: ~]#echo mage wang zhang | ( read X Y Z; echo $X $Y $Z  )
mage wang zhang
[root@centos7: ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang


範例:

​ 檢查磁盤使用率,發送郵件

#!/bin/bash

EMAIL=[email protected]
TIME=10
WARNING=80

df | sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p" | while read DEVICE use;
do
	if [ ${use} -gt ${WARNING} ]; then
		echo "${DEVICE} while be full,use:${use}" | mail -s "DISK WARNING" ${EMAIL}
	fi

done

範例:

​ 拒絕黑客ssh攻擊

#!/bin/bash
lastb | sed -rn '/ssh:/s@.*([0-9.]{1,3}{3}[0-9]{1,3}) .*@\1@p' | sort | uniq -c | while read count ip; do
	if [ ${count} -gt 3 ]; then
		iptables -A INPUT -s ${ip} -j REJECT
	fi
done    

1.2.4 練習:用while實現

1.編寫腳本,求100以內所有正奇數之和

2、編寫腳本,提示請輸入網絡地址,如192.168.0.0, 判斷輸入的網段中主機在線狀態,並統計在線和離線主機各多少

3、編寫腳本,打印九九乘法表

4.編寫腳本,利用變是RANDOM生成10個隨機數字,輸出這個10數字,並顯示其中的最大值和最小值

5、編寫腳本,實現打印國際象棋棋盤

6、後續六個字符串: efbaf275cd. 4be9c40b8b、 44b2395c46、 f8c8873ce0、 b902c16c8b. ad865d2f63是通過對隨機數變量RANDOM隨機執行命令: echo SRANDOM |mdsum|cut -C1-10後的結果, 請破解這些字符串對應的RANDOM值

1.3 until循環

格式

until CONDITION;do
	循環體
done	

說明:

進入條件:CONDITION爲false

退出條件:CONDITION爲true

1.3.1 無限循環

until false;do
	循環體
done	

1.4 循環控制語句

1.4.1 循環體控制語句:continue

continue [N]:提前結束第N層地本輪循環,而直接進入下一輪判斷;最內層爲第1層

格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		continue
	fi
    CMDn
    ...
done    

1.4.2 循環控制語句 break

break [N]:提前結束第N層循環,最內層爲1層

格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		break;
	fi
    CMDn
    ...
done    

範例:

#!/bin/bash
sum=0
while true;
do
cat << EOF
1) 鮑魚
2) 滿漢全席
3) 龍蝦
4) 燕窩
5) 帝王蟹
6) 退出
EOF
read -p "請點餐" MENU
case $MENU in

1|4)
	echo 價格10元
	let sum+=10
	;;
3|5)
	echo 價格20元
	let sum+=20
	;;
2)
	echo 價格20000元
	let sum+=20000;
	;;
6)
	echo "退出"
	echo "總價錢$sum"
	break
	;;
*)
	echo "點錯了沒有這道菜"
esac

done

1.4.3 循環控制shift命令

shift [n] 用於將參量列表list 左移指定次數,缺省爲左移一次。

參數列表list一旦被移動,最左端地那個參數就從列表中刪除。while循環遍歷位置參量列表時,常用到 shift

1.4.3.1 範例:

​ 打印參數

#!/bin/bash
while [ "$1" ]; do
	echo $1
	shift
done

創建用戶

#!/bin/bash
if [ $# -eq 0 ];
then 
	echo "請輸入要創建的用戶"
	exit 100;
else
	echo "開始創建用戶"
fi

while [ "$1" ];do
	if id $1 &> /dev/null;then
		echo $1 is exist
	else
		useradd $1
	fi
	shift
done
echo "ALL user is  created"

1.4.3.2 練習

1、每隔3秒鐘到系統上獲取已經登錄的用戶的信息;如果發現用戶hacker登錄,則將登錄時間和主機記錄於日誌var/log/login.log中,並退出腳本

2、隨機生成10以內的數字,實現猜字遊戲,提示比較大或小,相等則退出

#!/bin/bash
NUM=$[RANDOM%9]
echo "輸入0 - 9 之前地數字"
while read -p "guess num:" guess;
do
	if [ ${NUM} -eq ${guess} ]; 
	then
		echo "恭喜猜對了"
		break;
	elif [ ${NUM} -lt ${guess} ]
	then 
		echo "數字猜大";
	else
		echo "數字猜小了";
	fi	
done

3.用文件名做爲參數,統計所有參數文件的總行數

4、用二個以上的數字爲參數,顯示其中的最大值和最小值

1.5 select循環與菜單

格式:

select variable in list; do
	循環體命令
done

說明:

  • select 循環主要用於創建菜單,按數字順序排列的菜單項顯示在標準錯誤上,並顯示PS3提示符,等待用戶輸入
  • 用戶輸入菜單列表中的某個數字,執行相應的命令
  • 用戶輸入被保存在內置變量REPLY中
  • select是個無限循環,因此要記住用break命令退出循環,或用exit命令終止腳本。也可以按ctrl + c退出循環
  • select經常和case聯合使用
  • 與for循環類似,可以省略in list,此時使用位置參量

範例:

#!/bin/bash
PS3="請點菜(1-6):"
select MENU in 鮑魚 滿漢全席 龍蝦 燕窩 帝王蟹 退出; do
	case $REPLY in
	1|4)
		echo "價格是:\$10";
		;;
	3|5)
		echo "價格是:\$20";
		;;
	2)
		echo 價格是:\$1000;
		;;
	6)
		break;
		;;
	*)
		echo "請選擇正確的菜";
		;;
	esac
done

範例:

​ 修改系統部分屬性(網卡修改後要修改/etc/sysconfig/network-scripts/ifcfg-ens33 配置文件名和內容,重啓網卡服務 systemctl restart network)

#!/bin/bash

PS3="請輸入相應的編號:(1-5):"

select menu in 禁用SELinux 關閉防火牆  修改提示符 修改網卡名稱 退出; do
	case ${REPLY} in
	1)
		sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
		echo “重啓生效”;
		;;
	2)
		systemctl disable --now firewalld
		echo "防火牆已禁用"
		;;
	3)
		echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
		echo "請重新登錄"
		;;
	4)
		sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub 
		grub2-mkconfig -o /boot/grub2/grub.cfg
		echo "網卡名稱修改完成,重新啓動才能生效"
		;;
	5)
		exit;	
		;;
	*)
		echo "請輸入正確的數字"
	esac
done

2 函數介紹

函數function是由若干條shell命令組成的語句快,實現代碼重用和模塊化編程。

它與shell程序形式上是相似的,不同的它不是一個單獨的進程,不能獨立運行,而是shell程序的一部分

函數和shell程序比較相似,區別在於:shell程序在子shell中運行,而shell函數在當前shell中運行。因此在當前shell中,函數可對shell中變量進行修改

2.1 管理函數

函數由兩部分組成:函數名和函數體

幫助參看:help function

2.1.1 定義函數

#語法一:
func_name(){
	函數體
}

#語法二
function func_name{
	函數體
}

#語法三
function func_name(){
	函數體
}

2.1.2 查看函數

#查看當前已定義的函數名
declare -F
#查看當前已定義的函數定義
declare -f
#查看指定當前已定義的函數名
declare -f func_name
#查看當前已定義的函數名定義
declare -F func_name

2.1.3 刪除函數

格式:

unset func_name

2.2 函數調用

函數的調用方式

  • 可在交互式環境下定義函數
  • 可將函數放在腳本文件中作爲它的一部分
  • 可放在只包含函數的單獨文件中

調用:函數只有被調用纔會執行,通過給定函數名調用函數,函數名出現的地方,會被自動替換爲函數代碼。

函數的生命週期:被調用時創建,返回時終止

2.2.1 交互式環境調用函數

交互式環境下定義和使用函數

範例:

[root@centos7: function]#dir(){
> ls -l
> }
[root@centos7: function]#dir
total 8
-rw-r--r-- 1 root root  48 Apr 13 21:39 demo.sh
-rw-r--r-- 1 root root 231 Apr 13 21:37 test_function.sh

2.2.2 在腳本中定義及使用函數

函數在使用前必須定義,因此應將函數定義放在腳本開始部分,直至shell首次發現它後才能使用,調用函數僅使其函數名即可

[root@centos7: function]#cat test1.sh 
#!/bin/bash
function echoHello(){
	echo hello world;
}

echoHello
[root@centos7: function]#bash test1.sh 
hello world


範例:

#!/bin/bash

function disableSELinux(){
	sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
	echo “重啓生效”;
}

function closeFireWall(){
	systemctl disable --now firewalld
        echo "防火牆已禁用"
}

function setPS1(){
	 echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
         echo "請重新登錄"
}

function setNetWorkName(){
	sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
        grub2-mkconfig -o /boot/grub2/grub.cfg
        echo "網卡名稱修改完成,重新啓動才能生效"
}

PS3="請輸入相應的編號:(1-5):"

select menu in 禁用SELinux 關閉防火牆  修改提示符 修改網卡名稱 退出; do
	case ${REPLY} in
	1)
		disableSELinux
		;;
	2)
		closeFireWall
		;;
	3)
		setPS1
		;;
	4)
		setNetWorkName
		;;
	5)
		exit;	
		;;
	*)
		echo "請輸入正確的數字"
	esac
done

2.2.3 使用函數文件

可以將經常使用的函數存入一個單獨的函數文件,然後將函數文件載入shell,再進行調用函數。文件名可任意選取,但最好與相關人物有某種聯繫,例如:functions

一旦函數文件載入shell,就可以在命令行或腳本中調用函數。可以使用delcare -f或set命令查看所有定義的函數,其輸出列表包括已經載入shell的所有函數

若要改動函數,首先用unset命令從shell中刪除函數。改動完畢後,再重新載入此文件

實現函數文件的過程:

1.創建愛你函數文件,只存放函數的定義

2.在shell腳本或交互式shell中調用函數文件,格式如下:

filename 或 source filename

範例:

​ 使用 /etc/init.d/functions 下的 action 函數

#!/bin/bash

. /etc/init.d/functions

function disableSELinux(){
	sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
	action  “重啓生效”;
}

function closeFireWall(){
	systemctl disable --now firewalld
        action "防火牆已禁用"
}

function setPS1(){
	 echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
         action "請重新登錄"
}

function setNetWorkName(){
	sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
        grub2-mkconfig -o /boot/grub2/grub.cfg
        action "網卡名稱修改完成,重新啓動才能生效"
}
PS3="請輸入相應的編號:(1-5):"

select menu in 禁用SELinux 關閉防火牆  修改提示符 修改網卡名稱 退出; do
	case ${REPLY} in
	1)
		disableSELinux
		;;
	2)
		closeFireWall
		;;
	3)
		setPS1
		;;
	4)
		setNetWorkName
		;;
	5)
		exit;	
		;;
	*)
		action "請輸入正確的數字" false
	esac
done

2.3 函數返回值

函數的執行結果返回值:

  • 使用echo等命令進行輸出
  • 函數體中調用命令的輸出結果

函數的退出狀態碼:

  • 默認取決於函數中執行的最後一條命令的退出狀態碼

  • 自定義退出狀態碼,其格式爲:

    return 從函數中返回,用最後狀態命令決定返回值

    return 0 無錯誤返回

    return 1-255 有錯誤返回

2.4 環境函數

類似於環境變量,也可以定義環境函數,使子進程也可使用父進程定義的函數

定義環境函數:

export -f function_name
declare -xf function_name

查看環境函數:

export -f
declare -xf

5.5 函數參數

函數可以接受參數:

  • 傳遞參數給函數:在函數名後面以空白分隔給定參數列表即可,如:testfunc arg1 arg2 …
  • 在函數體當中,可使用$1,2,...調使2,... 調用這些參數;還可以使用@,,*,#等特殊變量

5.6 函數變量

變量作用域:

  • 普通變量:只有在當前shell進程有效,爲執行腳本會啓動專用子shell進程;因此,本地變量的作用範圍是當前shell腳本程序文件,包括腳本中的函數
  • 環境變量:當前shell和子shell有效
  • 本地變量:函數的生命週期;函數結束時變量被自動銷燬

注意:

  • 如果函數中定義了普通變量,且名稱和局部變量相同,則使用本地變量
  • 由於普通變量和局部變量會衝突,建議在函數中只使用本地變量

在函數中定義本地變量的方法

local NAME=VALUE

5.7 函數遞歸

函數遞歸:函數直接或間接調用自身,注意遞歸層數,可能會陷入死循環。

遞歸示例:

階乘是基斯頓·卡曼於1808年發明的運算符號,是數學術語, 一個正整數的階乘(factorial) 是所有小於及等於該數的正整數的積,並且有0的階乘爲1,自然數n的階乘寫作n!。n!=1x2x3x4x…xn

階乘亦可以遞歸方式定義:0!=1,n!=(n-1)! *n

n!=n(n-1)(n-2)…1=n(n-1)!=n(n-1)(n-2)!

範例:fact.sh

#!/bin/bash
function fact(){
	if [ $1 -eq 0 -o $1 -eq 1 ]; then
		echo 1
	else
    	echo $[$1*$(func $[$1 -1])]
    fi
}
fact $1

fork炸彈是一種惡意程序,它的內部是一個不斷在fork進程的無限循環,實質是一個簡單的遞歸程序。由於程序是遞歸的,如果沒有任何限制,這會導致這個簡單的程序迅速耗盡系統裏面的所有資源

函數實現

:(){ :|:& };:

#解釋管道符號會開啓子進程
bomb(){ bomb | bomb & }; bomb

腳本實現

cat Bomb.sh
#!/bin/bash
./$0|./$0&

練習

1.編寫函數,實現OS的版本判斷

2.編寫函數,實現取出當前系統eth0的IP地址

3.編寫函數,實現打印綠色OK和紅色FAILED

4.編寫函數,實現判斷是否無位置參數,如無參數,提示錯誤

5.編寫函數,實現兩個數字做爲參數,返回最大值

6.編寫服務腳本/root/bin/testsrv.sh,完成如下要求

​ (1)腳本可接受參數: start, sfop, restart, status

​ (2)如果參數非此四者之一,提示使用格式後報錯退出

​ (3)如是start:則創建/var/lock/subsys/SCRIPT_NAME,並顯示”啓動成功”。考慮:如果事先已經啓動過一次,該如何處理?

​ (4)如是stop:則刪除/var/lock/subsys/SCRIPT_NAME,並顯示"停止完成”考慮:如果事先已然停止過了,該如何處理?

(5)如是restart,則先stop, 再start考慮:如果本來沒有start,如何處理?

(6)如是status,則如果/var/lock/subsys/SCRIPT_NAME文件存在,則顯示"SCRIPT. NAME is runn…".如果/var/lock/subsys/SCRIPT _NAME文件不存在,則顯示"SCRIPT_NAME is stopped…"

(7)在所有模式下禁止啓動該服務,可用chkconfig 和service命令管理。說明: SCRIPT_NAME爲當前腳本名

7.編寫腳本/root/bin/copycmd.sh

​ (1)提示用戶輸入一個可執行命令名稱

​ (2)獲取此命令所依賴到的所有庫文件列表

​ (3)複製命令至某目標目錄(例如/mnt/sysroot)下的對應路徑下如: /bin/bash ==> /mnt/sysroot/bin/bash

​ /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd

​ (4)複製此命令依賴到的所有庫文件至目標目錄下的對應路徑下:如: /lib64/ld-inux-x86-64.so.2 ==>/mnt/sysrot/lib64/ld-linux-x86-64.so.2

​ (5)每次複製完成一個命令後, 不要退出,而是提示用戶鍵入新的要複製的命令,並重復完成上述功能;直到用戶輸入quit退出.

8.斐波那契數列又稱黃金分割數列,因數學家列昂納多斐波那契以兔子繁殖爲例子而引入,故又稱爲“兔子數列",指的是這樣一個數列: 0、1. 1、2、3、5、8、13、21. 34、… 斐波納契數列以如下被以遞歸的方法定義: F (0) =0,F (1) =1, F (n) =F(n-1)+F(n-2) (n≥2) ,利用函數,求階斐波那契數列

9.漢諾塔(又稱河內塔)問題是源於印度一個古老傳說。 大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤,利用函數,實現N片盤的漢諾塔的移動步驟

6其他腳本相關工具

6.1 信號捕捉 trap

trap ‘觸發指令’ 信號

​ 進程收到系統發出的指定信號後,將執行自定義指令,而不會執行原操作

trap ‘’ 信號

  • 忽略信號的操作

trap ‘-’ 信號

  • 還原原信號的操作

trap -p

  • 列出自定義信號操作

trap finish EXIT

  • 當腳本退出時,執行finish函數

範例:

#!/bin/bash
trap 'echo "signal:SIGINT"' int quit
trap -p

for((i=0;i<=10;i++))
do
	sleep 0.5
	echo ${i};
done
trap '' int
trap -p

for i in {11..20}
do
	sleep 0.5
	echo ${i};
done

trap '-' int
trap -p
for ((i=21;i<=30;i++))
do 
	sleep 0.5
	echo $i;
done

範例

#!/bin/bash

function finish(){
	echo "finish" | tee -a finish.log
}
trap finish exit
while :;do
	echo running;
	sleep 0.5;
done

6.2 創建臨時文件 mktemp

mktemp命令用於創建並顯示臨時文件,可避免衝突

格式:

mktemp [option]... [TEMPLATE]

說明:TEMPLATE:filenameXXX,X至少要出現三個

常見選項:

  • d 創建臨時目錄
  • -p DIR 或 --tmpdir=DIR 指明臨時文件所存放目錄位置

範例:

#創建臨時文件
mktemp /tmp/testXXX
#創建臨時文件夾
tmpdir=`mktemp -d /tmp/testdirXXX`
#創建臨時文件
mktemp --tmpdir=/testdir testXXXXXX

6.3 安裝複製文件 install

install命令格式:

install [OPTION]... [-T] SOURCE DEST 單文件
install [OPTION]... SOURCE... DIRECTORY (拷貝)
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY... 創建空目錄

選項:

  • -m MODE,默認755
  • -o OWNER
  • -g GROUP

範例:

install -m 700 -o wang -g admins srcfile desfile
install -m 770 -d /testdir/installdir

6.4 交互式轉化批處理工具 expect

expect 是由Don Libes基於Tcl(Tool Command Language)語言開發的,主要應用於自動化交互式操作的場景,藉助expect處理交互的命令,可以將交互過程如:ssh登錄,ftp登錄等寫在一個腳本上,使之自動化完成。尤其適用於需要對多臺服務器執行相同操作的環境中,可以大大提高系統管理員的工作效率

expect 語法:

expect [選項] [-c cmds] [[-[f|b]] cmdfile] [args]

常見選項:

  • -c:從命令行執行expect腳本,默認expect是交互式地執行地
  • -d:可以輸出輸出調試信息

示例:

#安裝
yum install expect
#監聽('expect "\n")回車,然後輸出("pressed enter \n")
expect -c 'expect "\n" {send "pressed enter \n"}expect -d ssh.exp

expect中相關命令

  • spawn 啓動新的進程
  • expect 從進程接收字符串
  • send 用於向進程發送字符串
  • interact 允許用戶交互
  • exp_continue 匹配多個字符串在執行動作後加此命令

expect最常用地語法(tcl語言:模式-動作)

單一分支模式語法:

[root@centos7: data]#expect
expect1.1> expect "hi" {send "you said hi \n"}
hhh
hi
you said hi 
expect1.2> 

匹配到hi後,會輸出“you said hi”,並換行

多分支模式語法:

[root@centos7: data]#expect
expect1.1> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
hi
you said hi 
expect1.2> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
hehe
you said hehe 
expect1.3> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
bye
Good bye 
expect1.4>

匹配hi,hehe,bye任意字符串時,執行相應輸出。等同如下:

expect {
	"hi" {send "you said hi \n"}
	"hehe" {send "you said hehe \n"}
	"bye" {send "Good bye \n"}
}

範例1:(注意expect和’{‘有空格;“yes/no” 和’{‘也有空格)

#!/usr/bin/expect
spawn scp /etc/fstab 192.168.2.101:/root 
expect {
	"yes/no" { send "yes\n"; exp_continue }
	"password" { send "soft01\n" }
} 
expect eof

範例2:

#!/usr/bin/expect
spawn ssh 192.168.2.101
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "soft01\n"}
}
interact

範例3:expect變量

#!/usr/bin/expect
set ip 192.168.2.101
set user root
set password soft01
set timeout 10
spawn ssh ${user}@$ip
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "${password}\n"}
}
interact

範例4:expect位置參數

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
	"yes/no" {send "yes\n"; exp_continue}
	"password" {send "$password\n"}
}
interact

[root@centos7: expect]# ./ssh3.exp 192.168.2.101 root soft01

範例5:expect執行多個命令

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
	"yes/no" {send "yes\n"; exp_continue}
	"password" {send "$password\n"}
}
expect "]#" {send "useradd haha\n"}
expect "]#" {send "echo 123456| passwd --stdin haha\n"}
send "exit\n"
expect eof
[root@centos7: expect]# ./ssh4.exp 192.168.2.101 root soft01

範例6:shell腳本調用expect

#!/bin/bash
ip=$1
user=$2
password=$3
expect << EOF
set timeout 20
spawn ssh $user@$ip
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "$password\n"}
}
expect "]#" {send "useradd haha\n"}
expect "]#" {send "echo 123456| passwd --stdin haha\n"}
send "exit\n"
expect eof
EOF
[root@centos7: expect]# ./ssh5.exp 192.168.2.101 root soft01

範例7:shell腳本利用循環調用expect,批量主機創建賬號

#!/bin/bash
NET=192.168.2
user=root
password=123456

for ID in 101 102 103; do
	ip=${NET}.${ID}
	expect << EOF
	set timeout 20
	spawn ssh $user@$ip
	expect {
		"yes/no" {send "yes\n"; exp_continue}
		"password" {send "${password}"}
	}
	expect "]#" {send "useradd test22\n"}
	expect "]#" {send "echo 123456| passwd --stdin test22\n"}
	expect "]#" {send "exit\n"}
	expect eof
	EOF
done

7 數組

7.1 數組介紹

變量:存儲單個元素的內存空間

數組:存儲多個元素的連續內存空間,相當於多個變量的集合

數組名和索引

  • 索引的編號是從0開始,屬於數值索引
  • 索引可支持使用自定義的格式,而不僅是數值格式,即爲關聯索引,bash4.0版本之後開始支持
  • bash的數組支持稀疏格式(索引不連續)

7.2 聲明數組

# 普通數組:可以不事先聲明,直接使用
declare -a ARRAY_NAME
# 關聯數組:必須先聲明,再使用
declare -A ARRAY_NAME 關聯數組

注意:兩者不可相互轉換

7.3 數組賦值

數組元素的賦值

(1)一次只賦值一個元素

ARRAY_NAME[INDEX]=VALUE

範例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"

(2)一次賦值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

範例:

nums=({1..10})
alpha=({a..z})
file=(*.sh)
[root@centos7: ~]#declare -a
declare -a nums='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")'
declare -a student='([0]="xiaoming" [1]="xiaohong" [2]="ailic" [3]="bob")'

(3)只賦值特定元素

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

(4)交互式數組值對賦值

read -a ARRAY

7.4 顯示所有數組

顯示所有數組:

declare -a

範例:

root@centos7: ~]#declare -a
declare -a name='([0]="0mage" [2]="2wang")'
declare -a names='([0]="1" [1]="2")'
declare -a student='([0]="xiaoming" [1]="xiaohong" [2]="ailic" [3]="bob")'
declare -a title='([0]="title1" [1]="title2" [2]="title3")'

7.5 引用數組

引用數組元素

${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下標爲0的元素

範例:

[root@centos7: ~]#declare -a nums=({1..10})
[root@centos7: ~]#echo ${nums[0]}
1
[root@centos7: ~]#echo ${nums}
1

引用數組所有元素

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

範例:

[root@centos7: ~]#echo ${#nums[*]}
10

數組的長度,即數組中元素的個數

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

7.6 刪除數組

刪除數組中的某個元素,會導致稀疏格式

unset ARRAY[INDEX]

範例:

[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10
[root@centos7: ~]#unset nums[1]
[root@centos7: ~]#echo ${nums[*]}
1 3 4 5 6 7 8 9 10

刪除整個數組

unset ARRAY

範例:

[root@centos7: ~]#unset nums
[root@centos7: ~]#echo ${nums[*]}

[root@centos7: ~]#

7.7 數組數據處理

數組切片:

${ARRAY[@]:offset:number}
offset    #要跳過的元素個數
number	  #要取出的元素個數
#取偏移量之後的所有元素
{ARRAY[@]:offset}

範例:

[root@centos7: ~]#echo ${nums[*]:2:3}
3 4 5
[root@centos7: ~]#echo ${nums[@]:5}
6 7 8 9 10

向數組中追加元素:

ARRAY[${#ARRAY[*]}]=VALUE

範例:

[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10
[root@centos7: ~]#nums[${#nums[@]}]=11
[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10 11

7.8 關聯數組

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name]='val1' [idx_name2]='val2' ...)

注意關聯數組必須先聲明再調用

範例:

[root@centos7: ~]#name[ceo]=ceo
[root@centos7: ~]#name[cto]=cto
[root@centos7: ~]#echo ${name[ceo]}
cto
[root@centos7: ~]#unset name
[root@centos7: ~]#declare -A name
[root@centos7: ~]#name[ceo]=ceo
[root@centos7: ~]#name[cto]=cto
[root@centos7: ~]#name[coo]=coo
[root@centos7: ~]#echo ${name[*]}
ceo coo cto
[root@centos7: ~]#

7.9 範例

範例:生成10個隨機數保存於數組中,並找出其最大值和最小值

#!/bin/bash

declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
	nums[$i]=$RANDOM
	[ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]} && continue;
	[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
	[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo ${nums[*]}
echo -e "Min is $min \n Max is $max"

範例:編寫腳本,定義一個數組,數組中的元素對應的值是/var/log目錄下所有以.log結尾的文件;統計出其下標爲偶數的文件中的行數之和

#!/bin/bash
declare -a files
files=(/var/log/*.log)
declare -i lines=0

for i in $(seq 0 $[${#files[*]}-1]);do
	if [ $[$i%2] -eq 0 ];then
		let lines+=$(wc -l ${files[$i]} | cut -d" " -f1)
	fi	

done
echo Lines: $lines

練習

1.輸入若干個數值存入數組中,採用冒泡算法進行升序或降序排序

2.下圖所示,實現轉置矩陣matrix.sh

1 2 3 1 4 7
4 5 6 =====》 2 5 8
7 8 9 3 6 9

3.打印楊輝三角形

8. 字符串處理

8.1 字符串切片

​ 基於偏移量取子串

#返回字符串變量var的長度
${#var }	

#返回字符串變量var中從第 offset個字符後(不包括第offset個字符)的字符開始,到最後的部分,offset的取值在0到${#var}-1 之間(bash4.2後, 允許爲負值)
${var:offset} 

#返回字符串變量var中從第offset個字符後(不包括第offset個字符)的字符開始,長度爲number的部分
${var:offset:number}

#取字符串的最右側幾個字符,取字符串的最右側幾個字符,注意:冒號後必須有一空白字符
${var: -1ength}

#從最左側跳過offset字符,一直向右取到距離最右側1ength個字符之前的內容
${var:offset:-1ength}

#先從最右側向左取到length個字符開始,再向右取到距離最右側offset個字符之間的內容,注意: -1ength前空格
${var: -1ength:-offset}:

​ 基於模式取子串

#其中word可以是指定的任意字符 功能:自左而右,查找var變量所存儲的字符串中,第一次出現的word, 刪除字符串開頭至第一次出現word字符串(含)之間的所有字符
${var#*word}

#同上,貪婪模式,不同的是,刪除的是字符串開頭至最後一次由word指定的字符之間的所有內容
${var##*word}

​ 範例

[root@centos7: ~]#file="var/log/messages"
[root@centos7: ~]#echo ${file#*/}
log/messages
[root@centos7: ~]#echo ${file##*/}
messages
#其中word可以是指定的任意字符.功能:自右而左,查找var變量所存儲的字符串中,第一次出現的word,刪除字符串最後一個字符向左至第一次出現word字符串(含)之間的所有字符
${var%word*}
#同上,只不過刪除字符串最右側的字符向左至最後一次出現word字符之間的所有字符
${var%%word*}

​ 範例:

[root@centos7: ~]#file="var/log/messages"
[root@centos7: ~]#echo ${file%/*}
var/log
[root@centos7: ~]#echo ${file%%/*}
var

8.2查找替換

#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替換之
${var/pattern/substr}

#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替換之
${var//pattern/substr}

#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替換之
${var/#pattern/substr}

#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替換之
${var/%pattern/substr}

8.3 查找並刪除

#刪除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}

#刪除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}

#刪除var表示的字符串中所有以pattern爲行首匹配到的字符串
${var/#pattern}

刪除var所表示的字符串中所有以pattern爲行尾所匹配到的字符串
${var/%pattern}

8.4 字符大小寫替換

#:把var中的所有小寫字母轉換爲大寫
${var^^}

#把var中的所有大寫字母轉換爲小寫
${var,,}

9 高級變量

9.1 高級變量賦值

變量配置方式 str沒有配置 str爲空字符串 str已配置爲非空字符串
var=${str-expr} var=expr var= var=$str
var=${str:-expr} var=expr var=expr var=$str
var=${str+expr} var= var=expr var=expr
var=${str:+expr} var= var= var=expr
var=${str=expr} str=expr
var=expr
str不變
var=
str不變
var=$str
var=${str:=expr} str=expr
var=expr
str=expr
var=expr
str不變
var=$str
var=${str?expr} expr輸出至stderr var= var=$str
var=${str:?expr} expr輸出至stderr expr輸出至stderr var=$str

範例:

[root@centos7: ~]#title=ceo
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
ceo
[root@centos7: ~]#title=
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}

[root@centos7: ~]#unset title
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
test

9.2 高級變量用法-有類型變量

Shell變量一般是無類型的,但是bash Shell提供了declare和typeset兩個命令用於指定變量的類型,兩個命令是等價的

declare[選項]變量名
-r  聲明或顯示只讀變量
-i  將變量定義爲整型數
-a 將變量定義爲數組
-A 將變量定義爲關聯數組
-f   顯示已定義的所有函數名及其內容
-F  僅顯示已定義的所有函數名
-x   聲明或顯示環境變量和環境函數
-l   聲明變量爲小寫字母declare -l var=UPPER
-u  聲明變量爲大寫字母declare -u var=lower

9.3 變量間接引用

9.3.1 eval命令

eval命令將會首先掃描命令行進行所有的置換,然後再執行該命令。該命令適用於那些一次掃描無法實現其功能的變量該命令對變量進行兩次掃描

範例:

root@centos7: ~]#CMD=whoami
[root@centos7: ~]#echo ${CMD}
whoami
[root@centos7: ~]#eval ${CMD}
root
root@centos7: ~]#${CMD}
root

9.3.2 間接變量引用

如果第一個變量的值是第二個變量的名字,從第一個變引用第二個變量的值就稱爲間接變引用
variable1的值是variable2,而variable2又 是變量名,variable2的值爲value, 間接變量引用是指通過variable1獲得變量值value的行爲

variable1=variable2
variable2=value

bash Shell提供了兩種格式實現間接變量引用

#第一種:轉義$
eval tempvar=\$$variable1
#第二種
tempvar=${!variable}

範例:

[root@centos7: ~]#name1=name2
[root@centos7: ~]#name2=變量
[root@centos7: ~]#echo ${name1}
name2
[root@centos7: ~]#echo ${name2}
變量
[root@centos7: ~]#eval tmp=\$$name1
[root@centos7: ~]#echo $tmp
變量

#第二種
[root@centos7: ~]#echo ${!name1}
變量

=


var=str</td></tr><tr><td>var=str </td> </tr> <tr> <td>var={str:?expr}

expr輸出至stderr


expr輸出至stderr


var=$str

範例:

[root@centos7: ~]#title=ceo
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
ceo
[root@centos7: ~]#title=
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}

[root@centos7: ~]#unset title
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
test

9.2 高級變量用法-有類型變量

Shell變量一般是無類型的,但是bash Shell提供了declare和typeset兩個命令用於指定變量的類型,兩個命令是等價的

declare[選項]變量名
-r  聲明或顯示只讀變量
-i  將變量定義爲整型數
-a 將變量定義爲數組
-A 將變量定義爲關聯數組
-f   顯示已定義的所有函數名及其內容
-F  僅顯示已定義的所有函數名
-x   聲明或顯示環境變量和環境函數
-l   聲明變量爲小寫字母declare -l var=UPPER
-u  聲明變量爲大寫字母declare -u var=lower

9.3 變量間接引用

9.3.1 eval命令

eval命令將會首先掃描命令行進行所有的置換,然後再執行該命令。該命令適用於那些一次掃描無法實現其功能的變量該命令對變量進行兩次掃描

範例:

root@centos7: ~]#CMD=whoami
[root@centos7: ~]#echo ${CMD}
whoami
[root@centos7: ~]#eval ${CMD}
root
root@centos7: ~]#${CMD}
root

9.3.2 間接變量引用

如果第一個變量的值是第二個變量的名字,從第一個變引用第二個變量的值就稱爲間接變引用
variable1的值是variable2,而variable2又 是變量名,variable2的值爲value, 間接變量引用是指通過variable1獲得變量值value的行爲

variable1=variable2
variable2=value

bash Shell提供了兩種格式實現間接變量引用

#第一種:轉義$
eval tempvar=\$$variable1
#第二種
tempvar=${!variable}

範例:

[root@centos7: ~]#name1=name2
[root@centos7: ~]#name2=變量
[root@centos7: ~]#echo ${name1}
name2
[root@centos7: ~]#echo ${name2}
變量
[root@centos7: ~]#eval tmp=\$$name1
[root@centos7: ~]#echo $tmp
變量

#第二種
[root@centos7: ~]#echo ${!name1}
變量
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章