終于到shell 腳本這章了,在以前筆者賣了好多關(guān)子說(shuō)shell腳本怎么怎么重要,確實(shí)shell腳本在linux系統(tǒng)管理員的運(yùn)維工作中非常非常重要。下面筆者就帶你正式進(jìn)入shell腳本的世界吧。
到現(xiàn)在為止,你明白什么是shell腳本嗎?如果明白最好了,不明白也沒(méi)有關(guān)系,相信隨著學(xué)習(xí)的深入你就會(huì)越來(lái)越了解到底什么是shell腳本。首先它是一個(gè)腳本,并不能作為正式的編程語(yǔ)言。因?yàn)槭桥茉?/span>linux的shell中,所以叫shell腳本。說(shuō)白了,shell腳本就是一些命令的集合。舉個(gè)例子,我想實(shí)現(xiàn)這樣的操作:1)進(jìn)入到/tmp/目錄;2)列出當(dāng)前目錄中所有的文件名;3)把所有當(dāng)前的文件拷貝到/root/目錄下;4)刪除當(dāng)前目錄下所有的文件。簡(jiǎn)單的4步在shell窗口中需要你敲4次命令,按4次回車。這樣是不是很麻煩?當(dāng)然這4步操作非常簡(jiǎn)單,如果是更加復(fù)雜的命令設(shè)置需要幾十次操作呢?那樣的話一次一次敲鍵盤會(huì)很麻煩。所以不妨把所有的操作都記錄到一個(gè)文檔中,然后去調(diào)用文檔中的命令,這樣一步操作就可以完成。其實(shí)這個(gè)文檔呢就是shell腳本了,只是這個(gè)shell腳本有它特殊的格式。
Shell腳本能幫助我們很方便的去管理服務(wù)器,因?yàn)槲覀兛梢灾付ㄒ粋€(gè)任務(wù)計(jì)劃定時(shí)去執(zhí)行某一個(gè)shell腳本實(shí)現(xiàn)我們想要需求。這對(duì)于linux系統(tǒng)管理員來(lái)說(shuō)是一件非常值得自豪的事情。現(xiàn)在的139郵箱很好用,發(fā)郵件的同時(shí)還可以發(fā)一條郵件通知的短信給用戶,利用這點(diǎn),我們就可以在我們的linux服務(wù)器上部署監(jiān)控的shell腳本,比如網(wǎng)卡流量有異常了或者服務(wù)器web服務(wù)器停止了就可以發(fā)一封郵件給管理員,同時(shí)發(fā)送給管理員一個(gè)報(bào)警短信這樣可以讓我們及時(shí)的知道服務(wù)器出問(wèn)題了。
有一個(gè)問(wèn)題需要約定一下,凡是自定義的腳本建議放到/usr/local/sbin/目錄下,這樣做的目的是,一來(lái)可以更好的管理文檔;二來(lái)以后接管你的管理員都知道自定義腳本放在哪里,方便維護(hù)。
【shell腳本的基本結(jié)構(gòu)以及如何執(zhí)行】
http://wiki.jikexueyuan.com/project/linux/images/14_1.png.jpg" alt="14_1.png.jpg" />
Shell腳本通常都是以.sh 為后綴名的,這個(gè)并不是說(shuō)不帶.sh這個(gè)腳本就不能執(zhí)行,只是大家的一個(gè)習(xí)慣而已。所以,以后你發(fā)現(xiàn)了.sh為后綴的文件那么它一定會(huì)是一個(gè)shell腳本了。test.sh中第一行一定是 “#! /bin/bash” 它代表的意思是,該文件使用的是bash語(yǔ)法。如果不設(shè)置該行,那么你的shell腳本就不能被執(zhí)行?!?’表示注釋,在前面講過(guò)的。后面跟一些該腳本的相關(guān)注釋內(nèi)容以及作者和創(chuàng)建日期或者版本等等。當(dāng)然這些注釋并非必須的,如果你懶的很,可以省略掉,但是筆者不建議省略。因?yàn)殡S著你工作時(shí)間的增加,你寫的shell腳本也會(huì)越來(lái)越多,如果有一天你回頭查看你寫的某個(gè)腳本時(shí),很有可能忘記該腳本是用來(lái)干什么的以及什么時(shí)候?qū)懙?/span>。所以寫上注釋是有必要的。另外系統(tǒng)管理員并非你一個(gè),如果是其他管理員查看你的腳本,他看不懂豈不是很郁悶。該腳本再往下面則為要運(yùn)行的命令了。
http://wiki.jikexueyuan.com/project/linux/images/14_7.png.jpg" alt="14_7.png.jpg" />
Shell腳本的執(zhí)行很簡(jiǎn)單,直接”sh filename “ 即可,另外你還可以這樣執(zhí)行
http://wiki.jikexueyuan.com/project/linux/images/14_8.png.jpg" alt="14_8.png.jpg" />
默認(rèn)我們用vim編輯的文檔是不帶有執(zhí)行權(quán)限的,所以需要加一個(gè)執(zhí)行權(quán)限,那樣就可以直接使用’./filename’ 執(zhí)行這個(gè)腳本了。另外使用sh命令去執(zhí)行一個(gè)shell腳本的時(shí)候是可以加-x選項(xiàng)來(lái)查看這個(gè)腳本執(zhí)行過(guò)程的,這樣有利于我們調(diào)試這個(gè)腳本哪里出了問(wèn)題。
http://wiki.jikexueyuan.com/project/linux/images/14_9.png.jpg" alt="14_9.png.jpg" />
該shell腳本中用到了’date’這個(gè)命令,它的作用就是用來(lái)打印當(dāng)前系統(tǒng)的時(shí)間。其實(shí)在shell腳本中date使用率非常高。有幾個(gè)選項(xiàng)筆者常常在shell腳本中用到:
http://wiki.jikexueyuan.com/project/linux/images/14_10.png.jpg" alt="14_10.png.jpg" />
%Y表示年,%m表示月,%d表示日期,%H表示小時(shí),%M表示分鐘,%S表示秒
http://wiki.jikexueyuan.com/project/linux/images/14_11.png.jpg" alt="14_11.png.jpg" />
注意%y和%Y的區(qū)別。
http://wiki.jikexueyuan.com/project/linux/images/14_21.png.jpg" alt="14_21.png.jpg" />
-d 選項(xiàng)也是經(jīng)常要用到的,它可以打印n天前或者n天后的日期,當(dāng)然也可以打印n個(gè)月/年前或者后的日期。
http://wiki.jikexueyuan.com/project/linux/images/14_22.png.jpg" alt="14_22.png.jpg" />
另外星期幾也是常用的
http://wiki.jikexueyuan.com/project/linux/images/14_23.png.jpg" alt="14_23.png.jpg" />
【shell腳本中的變量】
在shell腳本中使用變量顯得我們的腳本更加專業(yè)更像是一門語(yǔ)言,開(kāi)個(gè)玩笑,變量的作用當(dāng)然不是為了專業(yè)。如果你寫了一個(gè)長(zhǎng)達(dá)1000行的shell腳本,并且腳本中出現(xiàn)了某一個(gè)命令或者路徑幾百次。突然你覺(jué)得路徑不對(duì)想換一下,那豈不是要更改幾百次?你固然可以使用批量替換的命令,但是也是很麻煩,并且腳本顯得臃腫了很多。變量的作用就是用來(lái)解決這個(gè)問(wèn)題的。
http://wiki.jikexueyuan.com/project/linux/images/14_24.png.jpg" alt="14_24.png.jpg" />
在test2.sh中使用到了反引號(hào),你是否還記得它的作用?’d’和’d1’在腳本中作為變量出現(xiàn),定義變量的格式為 “變量名=變量的值”。當(dāng)在腳本中引用變量時(shí)需要加上’$’符號(hào),這跟前面講的在shell中自定義變量是一致的。下面看看腳本執(zhí)行結(jié)果吧。
http://wiki.jikexueyuan.com/project/linux/images/14_25.png.jpg" alt="14_25.png.jpg" />
下面我們用shell計(jì)算兩個(gè)數(shù)的和。
http://wiki.jikexueyuan.com/project/linux/images/14_26.png.jpg" alt="14_26.png.jpg" />
數(shù)學(xué)計(jì)算要用’[ ]’括起來(lái)并且外頭要帶一個(gè)’$’。腳本結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_27.png.jpg" alt="14_27.png.jpg" />
Shell腳本還可以和用戶交互。
http://wiki.jikexueyuan.com/project/linux/images/14_28.png.jpg" alt="14_28.png.jpg" />
這就用到了read命令了,它可以從標(biāo)準(zhǔn)輸入獲得變量的值,后跟變量名。”read x”表示x變量的值需要用戶通過(guò)鍵盤輸入得到。腳本執(zhí)行過(guò)程如下:
http://wiki.jikexueyuan.com/project/linux/images/14_29.png.jpg" alt="14_29.png.jpg" />
我們不妨加上-x選項(xiàng)再來(lái)看看這個(gè)執(zhí)行過(guò)程:
http://wiki.jikexueyuan.com/project/linux/images/14_44.png.jpg" alt="14_44.png.jpg" />
在test4.sh中還有更加簡(jiǎn)潔的方式。
http://wiki.jikexueyuan.com/project/linux/images/14_45.png.jpg" alt="14_45.png.jpg" />
read -p 選項(xiàng)類似echo的作用。執(zhí)行如下:
http://wiki.jikexueyuan.com/project/linux/images/14_46.png.jpg" alt="14_46.png.jpg" />
你有沒(méi)有用過(guò)這樣的命令”/etc/init.d/iptables restart “ 前面的/etc/init.d/iptables 文件其實(shí)就是一個(gè)shell腳本,為什么后面可以跟一個(gè)”restart”? 這里就涉及到了shell腳本的預(yù)設(shè)變量。實(shí)際上,shell腳本在執(zhí)行的時(shí)候后邊是可以跟變量的,而且還可以跟多個(gè)。不妨筆者寫一個(gè)腳本,你就會(huì)明白了。
http://wiki.jikexueyuan.com/project/linux/images/14_47.png.jpg" alt="14_47.png.jpg" />
執(zhí)行過(guò)程如下:
http://wiki.jikexueyuan.com/project/linux/images/14_48.png.jpg" alt="14_48.png.jpg" />
在腳本中,你會(huì)不會(huì)奇怪,哪里來(lái)的$1和$2,這其實(shí)就是shell腳本的預(yù)設(shè)變量,其中$1的值就是在執(zhí)行的時(shí)候輸入的1,而$2的值就是執(zhí)行的時(shí)候輸入的$2,當(dāng)然一個(gè)shell腳本的預(yù)設(shè)變量是沒(méi)有限制的,這回你明白了吧。另外還有一個(gè)$0,不過(guò)它代表的是腳本本身的名字。不妨把腳本修改一下。
http://wiki.jikexueyuan.com/project/linux/images/14_49.png.jpg" alt="14_49.png.jpg" />
執(zhí)行結(jié)果想必你也猜到了吧。
http://wiki.jikexueyuan.com/project/linux/images/14_50.png.jpg" alt="14_50.png.jpg" />
【shell腳本中的邏輯判斷】
如果你學(xué)過(guò)C或者其他語(yǔ)言,相信你不會(huì)對(duì)if 陌生,在shell腳本中我們同樣可以使用if邏輯判斷。在shell中if判斷的基本語(yǔ)法為:
1)不帶else
if 判斷語(yǔ)句; then
command
fi
http://wiki.jikexueyuan.com/project/linux/images/14_51.png.jpg" alt="14_51.png.jpg" />
在if1.sh中出現(xiàn)了 ((a<60))這樣的形式,這是shell腳本中特有的格式,用一個(gè)小括號(hào)或者不用都會(huì)報(bào)錯(cuò),請(qǐng)記住這個(gè)格式,即可。執(zhí)行結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_52.png.jpg" alt="14_52.png.jpg" />
2)帶有else
if 判斷語(yǔ)句 ; then
command
else
command
fi
http://wiki.jikexueyuan.com/project/linux/images/14_53.png.jpg" alt="14_53.png.jpg" />
執(zhí)行結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_54.png.jpg" alt="14_54.png.jpg" />
3)帶有elif
if 判斷語(yǔ)句一 ; then
command
elif 判斷語(yǔ)句二; then
command
else
command
fi
http://wiki.jikexueyuan.com/project/linux/images/14_55.png.jpg" alt="14_55.png.jpg" />
這里的 && 表示“并且”的意思,當(dāng)然你也可以使用 || 表示“或者”,執(zhí)行結(jié)果:
http://wiki.jikexueyuan.com/project/linux/images/14_56.png.jpg" alt="14_56.png.jpg" />
以上只是簡(jiǎn)單的介紹了if語(yǔ)句的結(jié)構(gòu)。在判斷數(shù)值大小除了可以用”(( ))”的形式外,還可以使用”[ ]”。但是就不能使用>, < , = 這樣的符號(hào)了,要使用 -lt (小于),-gt (大于),-le (小于等于),-ge (大于等于),-eq (等于),-ne (不等于)。
http://wiki.jikexueyuan.com/project/linux/images/14_57.png.jpg" alt="14_57.png.jpg" />
再看看if中使用 && 和 ||的情況。
http://wiki.jikexueyuan.com/project/linux/images/14_71.png.jpg" alt="14_71.png.jpg" />
shell 腳本中if還經(jīng)常判斷關(guān)于檔案屬性,比如判斷是普通文件還是目錄,判斷文件是否有讀寫執(zhí)行權(quán)限等。常用的也就幾個(gè)選項(xiàng):
-e :判斷文件或目錄是否存在
-d :判斷是不是目錄,并是否存在
-f :判斷是否是普通文件,并存在
-r :判斷文檔是否有讀權(quán)限
-w :判斷是否有寫權(quán)限
-x :判斷是否可執(zhí)行
使用if判斷時(shí),具體格式為: if [ -e filename ] ; then
http://wiki.jikexueyuan.com/project/linux/images/14_72.png.jpg" alt="14_72.png.jpg" />
在shell 腳本中,除了用if來(lái)判斷邏輯外,還有一種常用的方式,那就是case了。具體格式為:
case 變量 in
value1)
command
;;
value2)
command
;;
value3)
command
;;
*)
command
;;
esac
上面的結(jié)構(gòu)中,不限制value的個(gè)數(shù),*則代表除了上面的value外的其他值。下面筆者寫一個(gè)判斷輸入數(shù)值是奇數(shù)或者偶數(shù)的腳本。
http://wiki.jikexueyuan.com/project/linux/images/14_73.png.jpg" alt="14_73.png.jpg" />
$a 的值或?yàn)?/span>1或?yàn)?/span>0,執(zhí)行結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_74.png.jpg" alt="14_74.png.jpg" />
也可以看一下執(zhí)行過(guò)程:
http://wiki.jikexueyuan.com/project/linux/images/14_75.png.jpg" alt="14_75.png.jpg" />
case腳本常用于編寫系統(tǒng)服務(wù)的啟動(dòng)腳本,例如/etc/init.d/iptables中就用到了,你不妨去查看一下。
【shell腳本中的循環(huán)】
Shell腳本中也算是一門簡(jiǎn)易的編程語(yǔ)言了,當(dāng)然循環(huán)是不能缺少的。常用到的循環(huán)有for循環(huán)和while循環(huán)。下面就分別介紹一下兩種循環(huán)的結(jié)構(gòu)。
http://wiki.jikexueyuan.com/project/linux/images/14_76.png.jpg" alt="14_76.png.jpg" />
腳本中的seq 1 5 表示從1到5的一個(gè)序列。你可以直接運(yùn)行這個(gè)命令試下。腳本執(zhí)行結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_77.png.jpg" alt="14_77.png.jpg" />
通過(guò)這個(gè)腳本就可以看到for循環(huán)的基本結(jié)構(gòu):
for 變量名 in 循環(huán)的條件; do
command
done
http://wiki.jikexueyuan.com/project/linux/images/14_78.png.jpg" alt="14_78.png.jpg" />
循環(huán)的條件那一部分也可以寫成這樣的形式,中間用空格隔開(kāi)即可。你也可以試試,for i in ls
; do echo $i; done 和 for i in cat test.txt
; do echo $i; done
http://wiki.jikexueyuan.com/project/linux/images/14_79.png.jpg" alt="14_79.png.jpg" />
再來(lái)看看這個(gè)while循環(huán),基本格式為:
while 條件; do
command
done
腳本的執(zhí)行結(jié)果為:
http://wiki.jikexueyuan.com/project/linux/images/14_80.png.jpg" alt="14_80.png.jpg" />
另外你可以把循環(huán)條件忽略掉,筆者常常這樣寫監(jiān)控腳本。
while :; do
command
done
【shell腳本中的函數(shù)】
如果你學(xué)過(guò)開(kāi)發(fā),肯定知道函數(shù)的作用。如果你是剛剛接觸到這個(gè)概念的話,也沒(méi)有關(guān)系,其實(shí)很好理解的。函數(shù)就是把一段代碼整理到了一個(gè)小單元中,并給這個(gè)小單元起一個(gè)名字,當(dāng)用到這段代碼時(shí)直接調(diào)用這個(gè)小單元的名字即可。有時(shí)候腳本中的某段代總是重復(fù)使用,如果寫成函數(shù),每次用到時(shí)直接用函數(shù)名代替即可,這樣就節(jié)省了時(shí)間還節(jié)省了空間。
http://wiki.jikexueyuan.com/project/linux/images/14_81.png.jpg" alt="14_81.png.jpg" />
fun.sh 中的sum() 為自定義的函數(shù),在shell腳本中要用
function 函數(shù)名() {
command
}
這樣的格式去定義函數(shù)。
上個(gè)腳本執(zhí)行過(guò)程如下:
http://wiki.jikexueyuan.com/project/linux/images/14_82.png.jpg" alt="14_82.png.jpg" />
有一點(diǎn)筆者要提醒你一下,在shell腳本中,函數(shù)一定要寫在最前面,不能出現(xiàn)在中間或者最后,因?yàn)楹瘮?shù)是要被調(diào)用的,如果還沒(méi)有出現(xiàn)就被調(diào)用,肯定是會(huì)出錯(cuò)的。
Shell腳本大體上就介紹這么多了,筆者所舉的例子都是最基礎(chǔ)的,所以即使你把所有例子完全掌握也不代表你的shell腳本編寫能力有多么好。所以剩下的日子里你盡量要多練習(xí),多寫腳本,你寫的腳本越多,你的能力就越強(qiáng)。希望你能夠找專門介紹shell腳本的書籍深入的去研究一下它。隨后筆者將給你留幾個(gè)shell腳本的練習(xí)題,你最好不要偷懶。
1. 編寫shell腳本,計(jì)算1-100的和;
2. 編寫shell腳本,要求輸入一個(gè)數(shù)字,然后計(jì)算出從1到輸入數(shù)字的和,要求,如果輸入的數(shù)字小于1,則重新輸入,直到輸入正確的數(shù)字為止;
3. 編寫shell腳本,把/root/目錄下的所有目錄(只需要一級(jí))拷貝到/tmp/目錄下;
4. 編寫shell腳本,批量建立用戶user_00, user_01, … ,user_100并且所有用戶同屬于users組;
5. 編寫shell腳本,截取文件test.log中包含關(guān)鍵詞’abc’的行中的第一列(假設(shè)分隔符為”:”),然后把截取的數(shù)字排序(假設(shè)第一列為數(shù)字),然后打印出重復(fù)次數(shù)超過(guò)10次的列;
6. 編寫shell腳本,判斷輸入的IP是否正確(IP的規(guī)則是,n1.n2.n3.n4,其中1<n1<255, 0<n2<255, 0<n3<255, 0<n4<255)。
以下為練習(xí)題答案:
1. #! /bin/bash
sum=0
for i in seq 1 100
; do
sum=$[$i+$sum]
done
echo $sum
2. #! /bin/bash
n=0
while [ $n -lt "1" ]; do
read -p "Please input a number, it must greater than "1":" n
done
?
sum=0
for i in seq 1 $n
; do
sum=$[$i+$sum]
done
echo $sum
?
3. #! /bin/bash
for f in ls /root/
; do
if [ -d $f ] ; then
cp -r $f /tmp/
fi
done
?
4. #! /bin/bash
groupadd users
for i in seq 0 9
; do
useradd -g users user_0$i
done
?
for j in seq 10 100
; do
useradd -g users user_$j
done
?
5. #! /bin/bash
awk -F':' '$0~/abc/ ' test.log >/tmp/n.txt
sort -n n.txt |uniq -c |sort -n >/tmp/n2.txt
awk '$1>10 ' /tmp/n2.txt
?
6. #! /bin/bash
checkip() {
if echo $1 |egrep -q '^[0-9].[0-9].[0-9].[0-9]/span> ; then
a=echo $1 | awk -F. ''
b=echo $1 | awk -F. ''
c=echo $1 | awk -F. ''
d=echo $1 | awk -F. ''
?
for n in $a $b $c $d; do
if [ $n -ge 255 ] || [ $n -le 0 ]; then
echo "the number of the IP should less than 255 and greate than 0"
return 2
fi
done
else
echo "The IP you input is something wrong, the format is like 192.168.100.1"
return 1
fi
}
?
rs=1
while [ $rs -gt 0 ]; do
read -p "Please input the ip:" ip
checkip $ip
rs=echo $?
done
echo "The IP is right!"