1.了解shell scripts
1.1 為什么要學(xué)習(xí)shell script?
自動(dòng)化管理
我們管理主機(jī)不是一件簡(jiǎn)單的事情:查詢登錄檔,追蹤流量,監(jiān)控用戶使用主機(jī)狀態(tài),主機(jī)各項(xiàng)硬設(shè)狀態(tài),主機(jī)軟件更新查詢等等。這些工作其實(shí)可以用shell scripts自動(dòng)完成。
追蹤與管理系統(tǒng)的重要工作
我們開機(jī)自啟的接口是在/etc/init.d目錄下,目錄下所有文件都是scripts,開機(jī)過程、參數(shù)設(shè)定也是。
簡(jiǎn)單入侵檢測(cè)
當(dāng)系統(tǒng)有異狀時(shí),一般這些異狀都記錄在"系統(tǒng)注冊(cè)表"里,我們可以在固定的時(shí)間段內(nèi)主動(dòng)去分析"系統(tǒng)注冊(cè)表"。
連續(xù)指令一體化
script最簡(jiǎn)單的功能就是匯集一些下達(dá)的連續(xù)指令,將它寫入script中。
1.5 簡(jiǎn)單的數(shù)據(jù)處理
就如前一章正規(guī)表示法的awk程序說明,可以發(fā)現(xiàn)awk用來處理數(shù)據(jù)。撰寫方便,速度又快。
1.6 跨平臺(tái)支持
幾乎所有的Unix Like上面都可以跑shell scripts的。
注意:shell scripts用的是外部的指令與bash shell的一些默認(rèn)工具,經(jīng)常去調(diào)用外部的函數(shù)庫,所以處理數(shù)據(jù)的速度上是不太夠的。shell scripts用在系統(tǒng)管理上面是很好用的一項(xiàng)工具,但是在處理大量數(shù)值運(yùn)算上就不好用了。
1.2 scripts的撰寫
shell scripts的注意事項(xiàng):
1.指令的執(zhí)行是從上而下、從左到右的分析與執(zhí)行;
2.指令、選項(xiàng)與參數(shù)間的多個(gè)空白都會(huì)被忽略掉;
3.空白行也會(huì)被忽略掉,并且tab鍵的空白也同樣視為空格鍵;
4.如果讀取到一個(gè)"Enter"符號(hào)(CR),就嘗試開始執(zhí)行該行或者該串命令;
5.如果一行的內(nèi)容太多,可以使用"\Enter"來延伸到下一行;
6."#" 可以作為批注;
假設(shè)寫的程序文件名是shell.sh,那么如何執(zhí)行這個(gè)文件?
直接下達(dá)指令:shell.sh文件必須要具備可讀與可執(zhí)行的權(quán)限;
以bash程序來執(zhí)行:通過"bash shell.sh"或"sh shell.sh"執(zhí)行;
shell scripts 的編寫
[~]$ vim hello.sh
#!/bin/bash
# Program:
# This program shows hello world in your screen
# History:
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e 'hello world! \a \n'
exit 0
解釋:
第一行: #!/bin/bash在聲明scripts使用的shll名稱;
因?yàn)槲覀兪褂玫氖莃ash,所以必須要以"#!/bin/bash"來聲明文件內(nèi)的語法使用bash的語法。
程序內(nèi)容說明:
建議一定要養(yǎng)成說明的習(xí)慣:1.標(biāo)注內(nèi)容與功能;2.版本信息;3.作者與聯(lián)絡(luò)方式;4.建檔日期等等。
主要環(huán)境變量的聲明。
主要程序部分
返回執(zhí)行結(jié)果:
一個(gè)指令的執(zhí)行成功與否,可以使用$?這個(gè)變量來觀察。那么我們也可以利用"exit"指令讓程序中斷,并且回傳一個(gè)數(shù)值給系統(tǒng)。"exit 0"表示離開script并回傳一個(gè)0給系統(tǒng),所以在執(zhí)行完script后,可以使用"echo $?"來得到0的值。
2.簡(jiǎn)單的shell scripts練習(xí)
2.1 簡(jiǎn)單示例
對(duì)談式腳步:變量?jī)?nèi)容由用戶決定
[~]$ vim showname.sh
#!/bin/bash
# Program:
# User inputs his first name and last name. Program shows his full name.
read -p "Please input your first name: " firstname
read -p "Please input your last name: " lastname
echo -e "\nYour full name is: " ${firstname} ${lastname}
隨日期變化,利用date進(jìn)行文件的建立
[~]$ vim create_3_file.sh
#!/bin/bash
# Program:
# Program creates three files,which named by user's input and date command.
# 1.讓使用者輸入文件名,并取得fileuser這個(gè)變量
echo -e "I will use 'touch' command to create three files."
read -p "Please input your filename: " fileuser
# 2.為了避免使用者隨意按enter,利用變量功能分析檔名是否有設(shè)定
filename=${fileuser:-"filename"}
# 3.開始利用date指令來獲取所需要的檔名:
date1=${date --date='2 days ago' +%Y%m%d}
date2=${date --date='1 days ago' +%Y%m%d}
date3=${date +%Y%m%d}
file1=${filename}${date1}
file2=${filename}${date2}
file3=${filename}${date3}
# 4.建立檔名
touch "${file1} ${file2} ${file3}"
數(shù)值運(yùn)算:簡(jiǎn)單的加減乘除
[~]$ vim multiplying.sh
#!/bin/bash
# Program:
# User inputs 2 integer numbers:program will cross these two numbers.
echo -e "You SHOULD input 2 numbers,i will multiplying them! \n"
read -p "first number:" firstnum
read -p "second numbser:" secondnum
total=$((${firstnum}*${secondnum}))
echo -e "\n The result of ${firstnum} x ${secondnum} is ==> ${total}"
在數(shù)值運(yùn)算上,可以使用"declare -i total={secondnum}"實(shí)現(xiàn),也可以使用上面的方式進(jìn)行:var=$((運(yùn)算內(nèi)容))。
數(shù)值運(yùn)算:通過bc計(jì)算pi
[~]$ vim cal_pi.sh
#!/bin/bash
# Program:
# User input a scale number to calculate pi number
echo -e "This program will calculate pi value.\n"
echo -e "You should input a float number to calculate pi value.\n"
read -p "The scale number(10-10000)?" checking
num=${checking:-"10"} # 開始判斷是否有輸入數(shù)值
echo -e "Starting calculate pi value.Be patient."
time echo "scale=${num};4*a(1)" | bc -lq
上面部分4*a(1)是bc提供的計(jì)算pi的函數(shù),scale就是表示bc計(jì)算的pi有幾位小數(shù)點(diǎn)的意思。
2.2script的執(zhí)行方式差異(source,sh script,./script)
利用直接執(zhí)行的方式來執(zhí)行script
我們前面使用bash(sh)執(zhí)行腳步時(shí),script是在子程序的bash內(nèi)執(zhí)行的。重點(diǎn)是"當(dāng)子程序完成后,在子程序內(nèi)的各項(xiàng)變量或動(dòng)作將會(huì)結(jié)束而不會(huì)傳回到父程序中"。
例如上面的showname.sh,設(shè)定好的變量在退出showname.sh后,使用"echo ${firstname}"是無效的。因此你的firstname變量其實(shí)是在子程序的bash內(nèi)執(zhí)行的。當(dāng)showname.sh執(zhí)行完后,子程序bash內(nèi)的所有數(shù)據(jù)會(huì)被移除。
利用source執(zhí)行腳本:在父程序中執(zhí)行
使用source執(zhí)行的化,在退出showname.sh后,使用"echo ${firstname}"還是會(huì)得到數(shù)據(jù)的!
3.判斷式的使用
3.1 利用test指令的測(cè)試功能
看一個(gè)示例:
[~]$ test -e showname.sh
## 什么都沒顯示,但是可以通過$?、&&、||來展現(xiàn)效果!
[~]$ test -e showname.sh && echo "exist" || echo "not exist"
功能簡(jiǎn)介:
1.關(guān)于某個(gè)檔名的"文件類型"判斷:
-e :該"檔名"是否存在?(常用)
-f :該"檔名"是否存在切為文件(file)?(常用)
-d :該"文件名"是否存在且為目錄(directory)?(常用)
-b :該"檔名"是否存在且為一個(gè)block device裝置?
-c :該"檔名"是否存在且為一個(gè)character device裝置?
-S :該"檔名"是否存在且為一個(gè)socket文件?
-p :該"檔名"是否存在且為一個(gè)FIFO文件?
-L :該"檔名"是否存在且為一個(gè)連結(jié)檔?
2.關(guān)于文件的權(quán)限檢測(cè),如"test -r filename"表示是否可讀。
-r :檢測(cè)該"檔名"是否存在且具有可讀權(quán)限?
-w :檢測(cè)該"檔名"是否存在且具有可寫權(quán)限?
-x :檢測(cè)該"檔名"是否存在且具有可執(zhí)行權(quán)限?
-u :檢測(cè)該"檔名"是否存在且具有SUID權(quán)限?
-g :檢測(cè)該"檔名"是否存在且具有SGID權(quán)限?
-k :檢測(cè)該"檔名"是否存在且具有Sticky Bit權(quán)限?
-s :檢測(cè)該"檔名"是否存在且為非空白文件?
3.兩個(gè)文件之間的比較,如test file1 -nt file2
-nt :(newer than)判斷file1是否比file2新;
-ot :(older than)判斷file1是否比file2舊;
-ef :判斷file1和file2是否為同一個(gè)文件。主要是判斷是否均指向同一個(gè)inode;
4.關(guān)于兩個(gè)整數(shù)之間的判定,例如test n1 -eq n2
-eq :兩數(shù)值是否相等;
-ne :兩數(shù)值是否不想等;
-gt :n1大于n2?
-lt :n1小于n2?
-ge :n1大于等于n2?
-le :n1小于等于n2?
5.判斷字符串的數(shù)據(jù)
test -z str :判斷字符串是否為空字符串;
test -n str :判斷字符串是否為非空字符串;
test str1 == str2 :判斷str1是否等于str2;
test str1 != str2 :判斷str1是否不等于str2;
6.多重條件判定,例如:test -r filename -a -x filename
-a :相當(dāng)于&&,file同時(shí)具有r和x權(quán)限時(shí)為true;
-o :相當(dāng)于||,file具有r或x權(quán)限時(shí)為true;
! :反向,如test ! -x file,表示當(dāng)file不具有x權(quán)限是為true;
3.2 判斷符號(hào)[]的使用
假設(shè)我們想判斷一個(gè)${HOME}是否為空可以這樣做:
[~]$ [ -z "${HOME}" ] ; echo $?
需要注意的地方;
1.在中括號(hào)[]內(nèi)的每個(gè)部分都需要有空格鍵來分隔;
2.在中括號(hào)內(nèi)的變量,最好都以雙引號(hào)括起來;
3.在中括號(hào)內(nèi)的常量,最好都以單引號(hào)括起來;
3.3 Shell script的默認(rèn)變量(
1.....)
在我們執(zhí)行"/etc/inti.d/network restart"時(shí),就可以重啟網(wǎng)絡(luò)服務(wù)了。那么script是怎樣獲取我們指定的命令呢?
其實(shí),script針對(duì)參數(shù)已經(jīng)有設(shè)定好的一些變量名稱了,對(duì)應(yīng)如下:
/path opt1 opt2 opt3
$0 $1 $2 $3
$# :表示后面接參數(shù)的"個(gè)數(shù)",以上面為例值為"3"。
$@ :表示"$1" "$2" "$3",每個(gè)變量是獨(dú)立的;
$* :表示"$1[c]$2[c]$3",其中[c]為分隔符,默認(rèn)為空格鍵;
shift:參數(shù)變量號(hào)碼偏移
#!/bin/bash
...
shift n ##n為數(shù)字
...
執(zhí)行腳本中寫入shift n后,表示拿掉最前面的n個(gè)數(shù)。
4.條件判斷
4.1 利用if...then
單層、簡(jiǎn)單條件判斷
if [ 條件判斷 ] ; then
...................
fi
多重、復(fù)雜條件判斷
if [ 條件判斷 ] ; then
.......................
else
.......................
fi
或者
if [ 條件一 ] ; then
.......
elif [ 條件二 ] ;then
.......
else
.......
fi
下面練習(xí)一個(gè)示例:假設(shè)我們要檢測(cè)端口號(hào)為:22、21、25、80的是否開啟。
[~]$ vim netstat.sh
#!/bin/bash
#Program:
# Using netstat and grep to detect WWW,SSH,FTP,MAIL services.
testfile=netstat_check.txt
netstat -tuln > ${testfile}
str=$(grep ':80' ${testfile})
if [ "${str}"!="" ] ; then
echo "WWW is running"
fi
str=$(grep ':21' ${testfile})
if [ "${str}"!="" ] ; then
echo "FTP is running"
fi
str=$(grep ':22' ${testfile})
if [ "${str}"!="" ] ; then
echo "SSH is running"
fi
str=$(grep ':25' ${testfile})
if [ "${str}"!="" ] ; then
echo "Mail is running"
fi
4.2 case...esac的使用
case $變量名稱 in
"第一個(gè)變量?jī)?nèi)容")
程序段
;;
"第二個(gè)變量?jī)?nèi)容")
程序段
;;
*)
不包含前面條件所執(zhí)行的程序段
exit 1
;;
esac
接下來看一個(gè)示例:
[~]$ vim hello-2.sh
#!/bin/bash
# Program:
# Show "hello" from $1
case ${1} in
"hello")
echo '你好!'
;;
"")
echo '請(qǐng)輸入字符!'
;;
*)
echo ${0} '{的參數(shù)必須是hello}'
exit 1
;;
esac
4.3利用function功能
語法:
fuction fname(){
代碼段.....
}
需要注意的是:Shell Script的執(zhí)行方式是由上而下,從左到右,所以,function的設(shè)定一定要在程序的最前面。
看下面一個(gè)示例:
[~]$ vim show123.sh
#!/bin/bash
# Program:
# Use function to repeat information.
function printit(){
echo -n "Your choice is" # 加上 -n 可以不斷行繼續(xù)在同一行顯示
}
echo 'This program will print your selecton.'
case ${1} in
"one")
printit; echo ${1} | tr 'a-z' 'A-Z' # 將參數(shù)做大小寫轉(zhuǎn)換
;;
"two")
printit; echo ${1} | tr 'a-z' 'A-Z'
;;
"three")
printit; echo ${1} | tr 'a-z' 'A-Z'
;;
*)
echo "Usage" ${0} "{one|two|three}"
;;
esac
另外,function也擁有內(nèi)建的變量,和shell script類似。函數(shù)名稱代表1,$2.....,看看下面一個(gè)例子:
[~]$ vim show123-2.sh
#/bin/bash
# Program:
# Use funciton to repeat information.
function printit(){
echo "Your choice is ${1}" #這個(gè)${1}必須參考下面指令的
}
echo "This program will print your selection."
case ${1} in
"one")
printit 1 #printit后面接參數(shù)!
;;
"two")
printit 2
;;
"three")
printit 3
;;
*)
echo "Usage "${0}"one|two|three"
;;
esac
5.循環(huán)(loop)
5.1 while do done,until do done(不定循環(huán))
一般來說,不定循環(huán)就是以下兩種語法:
while [ condition ] ## 括號(hào)內(nèi)是條件,達(dá)到條件繼續(xù)循環(huán)
do
程序段落
done
或者
until [ condition ] ## 括號(hào)內(nèi)是條件,達(dá)到條件時(shí)終止循環(huán)
do
程序段落
done
假設(shè)我們要讓使用者輸入yes/YES才結(jié)束執(zhí)行,否則就一致進(jìn)行告知用戶輸入字符串。
[~]$ vim yes_to_stop.sh
#!/bin/bash
# Program:
# Repeat question until user input correct answer.
while [ "${str}" != "yes" ] && [ "${str}" != "YES" ]
do
read -p 'Please input yes to stop looping! ' str
done
echo 'program is shutdown!'
5.2 for...do...don(固定循環(huán))
語法:
for var in con1 con2 con3 ...
do
程序段
done
假設(shè)有三個(gè)動(dòng)物:狗、貓、豬,我們想每一行都輸出她們?cè)趺醋觯?/p>
[~]$ vim show_animals.sh
#!/bin/bash
# Program:
# Using for ... loop to print 3 animals.
for animal in dog cat pig
do
echo "this is a" ${animal}
done
再假設(shè)局域網(wǎng)有192.168.1.1~192.168.1.100,100臺(tái)主機(jī),我們想要進(jìn)行網(wǎng)絡(luò)檢測(cè)是否可以連接:
[~]$ vim pingip.sh
#!/bin/bash
# Program:
# Use ping command to check the network's PC state
network=192.168.4
for site in $(seq 1 100)
do
ping -c 1 -W 1 ${network}.${site} &> /dev/null && result=0 || result=1
if [ ${result} == 0 ] ; then
echo ${network}.${site} 'UP'
else
echo ${network}.${site} 'DOWN'
fi
done
5.3 for...do...done的數(shù)值處理
for (( 初始值; 限制值; 執(zhí)行步驟 ))
do
程序段
done
5.4 搭配隨機(jī)數(shù)與數(shù)組
假設(shè)我們要寫個(gè)腳本來決定你們團(tuán)隊(duì)今天中午吃什么菜怎么做?
[~]$ vim lunch.sh
eat[1]="漢堡"
eat[2]="雞翅"
eat[3]="可樂"
eat[4]="冰糕"
eat[5]="烤肉"
eat[6]="火鍋"
eat[7]="便當(dāng)"
eat[8]="爆米花"
eat[9]="薯?xiàng)l"
eat[10]="雪花"
eatnum=10
eated=0
while [ "${eated}" -lt 3 ]
do
check=$(( ${RANDOM} * ${eatnum} / 32767 + 1 ))
mycheck=0
if [ ${eated} -ge 1 ] ; then
for i in $(seq 1 ${eated})
do
if [ ${eatedcon[$i]} == ${check} ] ; then
mycheck=1
fi
done
fi
if [ ${mycheck} == 0 ] ; then
echo 'you may eat' ${eat[${check}]}
eated=$((${eated}+1))
eatedcon[${eated}]=${check}
fi
done
6.shell script 的追蹤與debug
scripts執(zhí)行前,如何debug呢?我們就以bash相關(guān)參數(shù)入手:
[~]$ sh [ -nvx ] scripts.sh
選項(xiàng)與參數(shù):
-n :不要執(zhí)行script,僅查詢語法問題;
-v :在執(zhí)行script前,先將script的內(nèi)容輸出到屏幕上;
-x :將使用到的script內(nèi)容顯示到屏幕上;
7.重點(diǎn)
shell script是利用shell的功能所寫的程序,這個(gè)程序使用純文本文件,將一些shell的語法和指令寫在里面,搭配正規(guī)表示法、管線命令與數(shù)據(jù)流重導(dǎo)向等功能;
shell script用在系統(tǒng)管理上面是很好的工具,但用在處理大量數(shù)值運(yùn)算上,就不好用了!因?yàn)閟hell script 速度較慢,且使用CPU資源多;
shell script的執(zhí)行至少要有r的權(quán)限,若下達(dá)指令,需要有r和x權(quán)限;
良好的程序編寫習(xí)慣,第一行要有聲明shell(#!/bin/bash),第二行則聲明程序用途等等;
要建立每次執(zhí)行腳本都有不同結(jié)果的數(shù)據(jù),可使用date指令利用日期達(dá)成;
script的執(zhí)行若以source來執(zhí)行時(shí),代表在父程序的bash內(nèi)執(zhí)行之意;
若要進(jìn)行判斷式,可用test或中括號(hào) [] 處理;