引用:
學習Shell Scripts
如果您真得很想要走信息這條路,并且想要好好的管理好屬于您的主機,那么,別說鳥哥不告訴您,Shell Scripts真的是必須要學習的一項課程!基本上,Shell scripts有點像是早期的批處理文件,亦即是將一些指令匯編起來一次執行,但是Shell scripts擁有更強大的功能,那就是,他可以進行類似程序(program)的撰寫,并且,不需要經過編譯(compiler)就能執行,真得很方便。加上,我們可透過shell script來簡化我們日常的工作管理,而且,整個Linux環境中,一些服務(services)的啟動都是透過shell script的,如果您對于script不了解,嘿嘿!發生問題時,可真是會求助無門的!所以,好好的學一學他吧!
什么是Shell scripts?
這個有趣的問題趕緊回答看看,什么是shell script呢?shell我們在認識bash當中已經提過了,那是一個文字接口底下讓我們與系統溝通的一個工具借口,那么script是啥?字面上的意義,script是[腳本、劇本]的意思。整句話是說,shell script是針對shell所寫的[劇本!] 什么東西啊?呵呵!其實,shell script是利用shell的功能所寫的一個[程序(program)],這個程序是使用純文字文件,將一些shell的語法與指令寫在里面,搭配正規表示法、管線命令與數據流重導向等功能,已達到我們所想要的處理目的。
所以,簡單的說,shell script就像是早期DOS年代的批處理(.bat),最簡單的功能就是將許多指令匯整寫在一起,讓使用者很輕易的就能夠one touch(執行一個檔案“shell script”,就能夠一次執行多個指令),而,shell script 更提供數組、循環、條件與邏輯判斷等重要功能,讓使用者也可以直接以shell 來撰寫程序,而不必使用類似C程序語言等傳統程序撰寫的語法呢!
那,這么說您可以了解了嗎?是的!shell script可以簡單的被看成是批處理,也可以被說成是一個程序語言,且這個程序語言由于都是利用shell與相關工具指令,所以不需要編譯即可執行,且擁有不錯的除錯(debug)工具,所以,他可以幫助系統管理員快速的管理好主機。
---------------------------------------------------------------------------------
干嗎學習shell scripts?
這是一個好問題,我又干嘛一定要學shell script?我又不是信息人,沒有寫程序的概念,那我干嗎還要學shell script呢?不要學可不可以啊?呵呵~如果Linux對您而言,您只是想要[會用]而已,那么,不需要學shell script也還無所謂,這部分先給他跳過去,等到有空的時候,再來好好的瞧一瞧。但是,如果您是真的想要玩清楚Linux的來龍去脈,那么shell script就不可不知,為什么呢?因為:
自動化管理的重要依據:
不用鳥哥說您也知道,管理一部主機真不是簡單的事情,每天要進行的任務就有:查詢登錄檔、追蹤流量、監控使用者使用主機狀態、主機各項硬件設備狀態、主機軟件更新查詢、更不要說得應付其它使用者的突然要求了。而這些工作,您想要自行手動處理,還是寫個簡單的程序來幫助您每日自動處理分析,若有問題才通知您呢?當然是讓系統自動工作比較好,對吧!呵呵~這就得要良好的shell script來幫忙的啦!
追蹤與管理系統的重要工作:
雖然我們還沒有提到服務啟動的方法,不過,這里可以先提一下,我們Linux系統的服務(services)啟動的接口,在/etc/init.d/這個目錄下,所有的檔案都是scriptsl;另外,包括開機(booting)過程也都是利用shell script來幫忙搜尋系統的相關設定數據,然后再代入各個服務的設定參數啊!舉例來說,如果我們想要重新啟動系統登錄文件,可以使用:[/etc/init.d/syslogd restart],那個syslogd檔案就是scripts啦!另外,我曾經在某一代的FC上面發現,啟動MySQL這個數據庫服務時,確實是可以啟動的,但是屏幕上卻老是出現[failure],后來才發現,原來啟動MySQL那個script會主動地以[空的密碼]去嘗試去登錄MySQL,但我修改過MySQL的密碼嘍~當然就登入失敗~后來改了改script,就略去這個問題啦!如此說來,script 確實是需要學習的啊!
簡單入侵偵測功能:
當我們的系統有異狀時,大多會將這些異狀記錄在系統記錄器,也就是我們提到的[系統登錄文件],那么我們可以在固定的幾分鐘內主動地去分析系統登錄文件,若察覺有問題,就立即通報管理員,或者是立刻加強防火墻的設定規則,如此一來,您的主機可就能夠達到[自我保護]的聰明學習功能啦~舉例來說,我們可以通過shell script 去分析[當該封包嘗試幾次還是聯機失敗后,就予以抵擋住該IP]之類的舉動,例如鳥哥寫過一個關于抵擋砍站軟件的shell script,就是用這個想法去達成的呢!
連續指令單一化:
其實,對于新手而言,script最簡單的功能就是:[匯整一些在command line 下達的連續指令,將他寫入scripts當中,而由直接執行scripts來啟動一連串的command line 指令輸出入!]例如:防火墻連續規則(iptables),開機加載程序的項目(就是在/etc/rc.d/rc.local里頭的數據),等等都是相似的功能啦!其實,說穿了,如果不考慮program的部分,那么scripts也可以想成,僅是幫我們把一大串的指令匯編在一個檔案里面,而直接執行該檔案就可以執行那一串又臭有長的指令段!就是這么簡單啦!
簡易的數據處理:
由前一章 正規表示法的awk程序說明中,您可以發現,awk可以用來處理簡單的數據呢!例如薪資簡單的處理啊。shell script的功能更強大,例如鳥哥曾經用shell script直接處理數據的比對啊,文字數據得處理啊等等的,撰寫方便,速度又快(因為在Linux效能較佳),真的是很不錯用的啦!
跨平臺支持與學習歷程較短:
幾乎所有的Unix Like上面都可以跑shell script,連MS Windows系列也有相關的仿真器可以用,此外,shell script的語法是相當親和的,看都看得懂得文字,而不是機器碼,很容易學習~這些都是您可以加以考慮的學習點啊!
上面這些都是您考慮學習shell script的特點~此外,shell script還可以簡單的以vi來直接編寫,實在是很方便的好東西!所以,還是建議您學習一下啦。
不過,雖然shell script號稱是程序(program),但實際上,shell script處理數據的速度上是不太夠的。因為,shell script 用的是外部的指令與bash shell 的一些預設工具,所以,他常常會去呼叫外部的函數庫,因此,運算速度上面當然比不上傳統的程序語言。所以,shell script用在系統管理上面是很好的一項工具,但是用在處理大量數值運算上,就不夠好了,而且還很麻煩,因為:shell scripts 的速度較慢,且使用的CPU資源較多,造成主機資源的分配不良,還好,我們確實很少看到利用shell scripts在進行大量數據運算的,所以,不必擔心的啦!
----------------------------------------------------------------------------------------------------
第一節 script的撰寫與執行
如同前面講到的,shell script其實就是純文字文件(ASCII),我們可以編輯這個檔案,然后讓這個檔案來幫我們一次執行多個命令,或者是利用一些運算與邏輯判斷來幫我們達成某些功能。所以,要編輯這個檔案的內容時,當然就需要具備有bash shell 指令下達的相關知識。我們說過,要下達指令需要注意的事項在bash章節內已經提過,在shell script的撰寫同樣要用到這些注意事項的:
如同前面bash command提到的,指令與參數間的多個空白會被忽略掉:
而空白行也被忽略掉!并且[tab]也是不會被理會的!
如果讀取到一個Enter符號(CR),就嘗試開始執行該行命令;
至于如果一行的內容太多,則可以使用\[Enter]來延伸至下一行;
此外,使用最多的#可做為批注!任何加在#后面的字,將全部被視為批注文字而被忽略!
如此一來,我們在script內所撰寫的程序,就會被一行一行的執行,好了,那么這個程序假設文件名是shell.sh 好了,如何執行這個檔案呢?很簡單,可以由底下幾個方法:
將shell.sh加上可讀與執行(rx)的權限,然后就能夠以./shell.sh來執行了;
直接以sh shell.sh的方式來直接執行即可。
反正重點就是要讓那個shell.sh內的指令可以被執行的意思!那我為何需要使用./shell.sh來下達指令?還記得我們在bash 里面一直強調的,指令是否能夠被執行與PATH這個環境變量有關,所以,要執行[目前這個目錄下的某個檔案]就需要加上 ./這個目錄!另外,其實您也可以將shell.sh放在您的家目錄下的~/bin這個目錄中,然后利用PATH="$PATH"~/bin的設定,就能夠直接執行您的script了
那,為何sh shell.sh也可以執行呢?這是因為/bin/sh其實就是/bin/bash,使用sh shell.sh亦即告訴系統,我想要直接以bash的功能來執行shell.sh這個檔案內的相關指令的意思。而我們也可以利用sh的參數,如 -n 及 -x來檢查與追蹤shell.sh的語法是否正確!
----------------------------------------------------------------------------------------------
撰寫第一個script
不論是那個門派,要學武功要從掃地做起,那么要學程序呢?呵呵,肯定是由[秀出Hello World!]這個字眼開始的!OK!那么鳥哥就先寫一個script給大家瞧一瞧:
[root@linux~]# mkdir scripts; cd scripts
[root@linux scripts]# vi sh01.sh
#!/bin/bash
# Program:
# This program is used to show "Hello World!" in screen.
# History:
# 2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World!\a\n"
exit 0
在我們這個章節當中,請將所有的撰寫的script放置到您家目錄的~/scripts這個目錄內,比較好管理啦!上面的寫法當中,我主要將整個程序的撰寫分成數段,大致是這樣:
第一行 #!/bin/bash在宣告這個script使用的shell 名稱:
因為我們使用的是bash,所以,必須要以[#!/bin/bash]來宣告這個檔案內的語法使用bash的語法!那么當這個程序被執行時,他就能夠加載bash的相關環境設定檔,并且執行bash來使我們底下的指令能夠執行!這很重要的!(在很多狀況中,如果沒有設定好這一行,那么該程序很可能會無法執行,因為系統可能無法判斷該程序需要使用什么shell來執行!)
程序內容的宣告:
整個script當中,除了第一行的#!是用來宣告shell的之外,其他的#就是[批注]用途!所以上面的程序當中,第二行以下就是用來說明整個程序的狀態。一般來說,建議您一定要養成說明該script的:1.內容與功能;2.版本信息;3.作者與聯絡方式;4.建檔日期;5.歷史紀錄等等。這將有助于未來程序的改寫與debug!
主要環境變量的宣告:
建議務必要將一些重要的環境變量設定好,鳥哥個人認為,PATH是當中最重要的!如此一來,則可讓我們這支程序在進行時,可以直接下達指令,而不必寫絕對路徑!比較好!
主要程序部分
就將主要的程序寫好即可!在這個例子當中,就是echo那一行。
執行成果告知
是否記得我們在bash里面要討論一個指令的執行成功與否,可以使用$?這個變量來觀察~那么我們就也可以利用exit這個指令來讓程序中斷,并且回傳一個數值給系統,在我們這個例子當中,我使用exit 0,這代表離開script,并且回傳一個0給系統,所以我執行完這個script后,若接著下達echo $?則可以得到0的值!更聰明的讀者應該也知道了,呵呵!利用這個exit n的功能,我們還可以自訂錯誤訊息,讓這支程序變得更加的smart呢!
接下來執行看看結果是怎樣的?
[root@linux scripts]# sh sh01.sh
Hello World!
您會看到屏幕是這樣,而且應該還會聽到[咚]的一聲,為什么呢?還記得前一章提到的printf吧?用echo接著那些特殊的按鍵也可以發生同樣的事情~不過,echo必須要加上 -e 的參數才行,呵呵,在您寫完這個小script之后,您就可以大聲地說:[我也會寫程序了]!
另外,你也可以利用:[chmod a+x sh01.sh; ./sh01.sh] 來執行這個script呢!
--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------
撰寫 shell script 的良好習慣建立
一個良好習慣的養成是很重要的~大家在剛開始撰寫程序的時候,最容易忽略這部分, 認為程序寫出來就好了,其它的不重要。其實,如果程序的說明能夠更清楚, 那么對您自己是有很大的幫助的。
舉例來說,鳥哥自己為了自己的需求,曾經撰寫了不少的 script 來幫我進行主機 IP 的偵測啊、 登錄檔分析與管理啊、自動上傳下載重要 設定檔啊等等的,不過,早期就是因為太懶了, 管理的主機又太多了,常常同一個程序在不同的主機上面進行更改,到最后,到底哪一支才是最新的都記不起來, 而且,重點是,我到底是改了哪里??為什么做那樣的修改?都忘的一干二凈~真要命~
所以,后來鳥哥在寫程序的時候,通常會比較仔細的將程序的設計過程給他記錄下來, 而且還會記錄一些歷史紀錄,如此一來,好多了~ 至少很容易知道我修改了哪些數據,以及程序修改的理念與邏輯概念等等, 在維護上面是輕松很多很多的喔!
另外,在一些環境的設定上面,畢竟每個人的環境都不相同,為了取得較佳的執行環境, 我都會自行先定義好一些一定會被用到的環境變量,例如 PATH 這個玩意兒! 這樣比較好啦~所以說,建議您一定要養成良好的 script 撰寫習慣, 在每個 script 的文件頭處記錄好:
script 的功能;
script 的版本信息;
script 的作者與聯絡方式;
script 的版權宣告方式;
script 的 History (歷史紀錄);
script 內較特殊的指令,使用絕對路徑的方式來下達;
script 運作時需要的環境變量預先宣告與設定。
--------------------------------------------------------------------------------
簡單的 shell script 練習
在第一支 shell script 撰寫完畢之后,相信您應該具有基本的撰寫功力了。 接下來,在開始更深入的程序概念之前,我們先來玩一些比 較有趣的簡單的小范例好了。 底下的范例中,達成結果的方式相當的多,建議您先自行撰寫看看,寫完之后再與鳥哥寫的內容比對, 這樣才能更加深概念喔! 好!不啰唆,我們就一個一個來玩吧!
--------------------------------------------------------------------------------
變量內容由使用者決定
很多時候我們需要使用者輸入一些內容,好讓程序可以順利運作。 簡單的來說,大家應該都有安裝過軟件的經驗,安裝的時候,他不是會問您『要安裝到那個目錄去?』嗎? 那個讓使用者輸入的數據的動作,就是讓使用者輸入變量內容啦。
你應該還記得在 bash 的時候,我們有學到一個 read 指令吧?忘記的話,請自行回頭去閱讀一番。 現在,請你以 read 指令的用 途,撰寫一個 script ,他可以讓使用者輸入:1 first name 與 2. last name, 最后并且在屏幕上顯示: 『Your full name is: 』的內容:
[root@linux scripts]# vi sh02.sh
#!/bin/bash
# Program:
# Let user keyin their first and last name, and show their full name.
# History:
# 2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input your first name: " firstname
read -p "Please input your last name: " lastname
echo -e "\nYour full name is: $firstname $lastname"
將上面這個 sh02.sh 執行一下,你就能夠發現使用者自己輸入的變量可以被取用的哩! 很不錯吧!加油!
--------------------------------------------------------------------------------
利用 date 進行檔案的建立
想象一個狀況,如果我每天要進行備份,而備份的數據又不想被覆蓋掉,也就是說, 我想要將每天備份的數據放在不同的檔案中。哇!這真困擾啊?難道 要我每天去修改 script ? 不需要啊!因為每天的『日期』并不相同,所以我可以將檔名取成類似: backup.20050802 , 不就可以 每天一個不同檔名了嗎?呵呵!確實如此。好了,接下來出個例子: 我想要建立三個空的檔案,檔名最開頭由使用者輸入決定,假設使用者輸入 filename 好了, 那今天的日期是 2005/08/23 ,我想要以前天、昨天、今天的日期來建立這個檔案,亦即 filename_20050821, filename_20050822, filename_20050823 ,該如何是好?
[root@linux scripts]# vi sh03.sh
#!/bin/bash
# Program:
# User can keyin filename to touch 3 new files.
# History:
# 2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 讓使用者輸入文件名稱,并取得 fileuser 這個變量;
echo -e "I will use 'touch' command to create 3 files."
read -p "Please input the filename what you want: " fileuser
# 2. 為了避免使用者隨意按 Enter ,利用變量功能分析文件名是否有設定?
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
touch $file2
touch $file3
我透過一些簡單的動作,這些動作都可以在 bash 那一章里面找到, 包括小指令 (`) 的取得訊息、變量的設定功能、變量的累加以及利用 touch 指令輔助! 如果您開始執行這個 sh03.sh 之后,你可以進行兩次輸入,一次直接按 [Enter] 來查閱檔名是啥? 一次可以輸 入一些字符,這樣來判斷你的檔案喔!關于 date 的指令應用,請 man date 吧! ^_^
--------------------------------------------------------------------------------
數值運算的方法
各位看官應該還記得,我們可以使用 declare 來定義變量的類型吧?! 這樣才能夠進行加減運算啊!可惜的是, bash shell 里 頭預設僅支持到整數的數據。 OK!那我們來玩玩看,如果我們要使用者輸入兩個變量,然后將兩個變量的內容相乘, 最后輸出相乘的結果,那可以怎么做?
[root@linux scripts]# vi sh04.sh
#!/bin/bash
# Program:
# User can input 2 integer to cross by!
# History:
# 2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "You SHOULD input 2 number, I will cross they! \n"
read -p "first number: " firstnu
read -p "second number: " secnu
total=$(($firstnu*$secnu))
echo -e "\nThe number $firstnu x $secnu is ==> $total"
在數字的運算上,我們可以使用『 declare -i total=$firstnu*$secnu 』 也可以使用上面的方式來進行!基本上,鳥哥比較建議使用這樣的方式來進行運算:
var=$((運算內容))
不但容易記憶,而且也比較方便的多~未來您可以使用這種方式來計算的呀!至于數值運算上的處理, 則有:+, -, *, /, %等等。 那個 % 是取余數啦~舉例來說, 13 對 3 取余數,結果是 13=4*3+1,所以余數是 1 啊!就是:
[root@linux scripts]# nu=$((13%3)); echo $nu
1
這樣了解了吧?!多多學習與應用喔! ^_^
--------------------------------------------------------------------------------
善用判斷式
在 bash 章節中,我們提到過 $? 這個變量所代表的意義, 此外,也透過 && 及 || 來作為前一個指令是否能夠成 功進行的一個參考。 那么,如果我想要知道 /dmtsai 這個目錄是否存在時,難道一定要使用 ls 來執行, 然后再以 $? 來判斷執行成果嗎? 呵呵!當然不需要! 我們可以透過『 test 』這個指令來偵測呢!
--------------------------------------------------------------------------------
利用 test 指令的測試功能
當我要檢測系統上面某些檔案或者是相關的屬性時,利用 test 這個指令來工作, 真是好用得不得了,舉例來說,我要檢查 /dmtsai 是否存在時,使用:
[root@linux ~]# test -e /dmtsai
執行結果并不會顯示任何訊息,但最后我們可以透過 $? 或 && 及 || 來展現整個結果呢! 例如我們在將上面的例子改寫成這樣:
[root@linux ~]# test -e /dmtsai && echo "exist" || echo "Not exist"
最終的結果可以告知我們是『exist』還是『Not exist』呢!那我知道 -e 是測試一個『東西』在不在, 如果還想要測試一下該檔名是啥玩意兒時,還有哪些標志可以來判斷的呢?呵呵!有底下這些東西喔!
測試的標志 代表意義
1. 關于某個檔名的『類型』偵測(存在與否),如 test -e filename
-e 該『檔名』是否存在?(常用)
-f 該『檔名』是否為檔案(file)?(常用)
-d 該『文件名』是否為目錄(directory)?(常用)
-b 該『檔名』是否為一個 block device 裝置?
-c 該『檔名』是否為一個 character device 裝置?
-S 該『檔名』是否為一個 Socket 檔案?
-p 該『檔名』是否為一個 FIFO (pipe) 檔案?
-L 該『檔名』是否為一個連結檔?
2. 關于檔案的權限偵測,如 test -r filename
-r 偵測該檔名是否具有『可讀』的屬性?
-w 偵測該檔名是否具有『可寫』的屬性?
-x 偵測該檔名是否具有『可執行』的屬性?
-u 偵測該文件名是否具有『SUID』的屬性?
-g 偵測該文件名是否具有『SGID』的屬性?
-k 偵測該文件名是否具有『Sticky bit』的屬性?
-s 偵測該檔名是否為『非空白檔案』?
3. 兩個檔案之間的比較,如: test file1 -nt file2
-nt (newer than)判斷 file1 是否比 file2 新
-ot (older than)判斷 file1 是否比 file2 舊
-ef 判斷 file2 與 file2 是否為同一檔案,可用在判斷 hard link 的判定上。 主要意義在判定,兩個檔案是否均指向同一個 inode 哩!
4. 關于兩個整數之間的判定,例如 test n1 -eq n2
-eq 兩數值相等 (equal)
-ne 兩數值不等 (not equal)
-gt n1 大于 n2 (greater than)
-lt n1 小于 n2 (less than)
-ge n1 大于等于 n2 (greater than or equal)
-le n1 小于等于 n2 (less than or equal)
5. 判定字符串的數據
test -z string 判定字符串是否為 0 ?若 string 為空字符串,則為 true
test -n string 判定字符串是否非為 0 ?若 string 為空字符串,則為 false。
注: -n 亦可省略
test str1 = str2 判定 str1 是否等于 str2 ,若相等,則回傳 true
test str1 != str2 判定 str1 是否不等于 str2 ,若相等,則回傳 false
6. 多重條件判定,例如: test -r filename -a -x filename
-a (and)兩狀況同時成立!例如 test -r file -a -x file,則 file 同時具有 r 與 x 權限時,才回傳 true。
-o (or)兩狀況任何一個成立!例如 test -r file -o -x file,則 file 具有 r 或 x 權限時,就可回傳 true。
! 反相狀態,如 test ! -x file ,當 file 不具有 x 時,回傳 true
OK!現在我們就利用 test 來幫我們寫幾個簡單的例子。首先,判斷一下, 讓使用者輸入一個檔名,我們判斷:
這個檔案是否存在,若不存在則給予一個『Filename does not exist』的訊息,并中斷程序;
若這個檔案存在,則判斷他是個檔案或目錄,結果輸出『Filename is regular file』或 『Filename is directory』
判斷一下,執行者的身份對這個檔案或目錄所擁有的權限,并輸出權限數據!
你可以先自行創作看看,然后再跟底下的結果討論討論。注意利用 test 與 && 還有 || 等標志!
[root@linux scripts]# vi sh05.sh
#!/bin/bash
# Program:
# Let user input a filename, the program will search the filename
# 1.) exist? 2.) file/directory? 3.) file permissions
# History:
# 2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 讓使用者輸入檔名,并且判斷使用者是否真的有輸入字符串?
echo -e "The program will show you that filename is exist which input by you.\n\n"
read -p "Input a filename : " filename
test -z $filename && echo "You MUST input a filename." && exit 0
# 2. 判斷檔案是否存在?
test ! -e $filename && echo "The filename $filename DO NOT exist" && exit 0
# 3. 開始判斷檔案類型與屬性
test -f $filename && filetype="regulare file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="$perm writable"
test -x $filename && perm="$perm executable"
# 4. 開始輸出信息!
echo "The filename: $filename is a $filetype"
echo "And the permission are : $perm"
很有趣的例子吧!您可以自行再以其它的案例來撰寫一下可用的功能呢!
--------------------------------------------------------------------------------
利用判斷符號 [ ]
除了我們很喜歡使用的 test 之外,其實,我們還可以利用判斷符號『 [ ] 』來進行數據的判斷呢! 舉例來說,如果我想要知道 $HOME 這個變量是否為空的,可以這樣做:
[root@linux ~]# [ -z "$HOME" ]
但使用 [] 要特別注意的是,在上述的每個組件中間都需要有空格鍵來分隔,假設我空格鍵使用『□』來表示, 那么,在這些地方你都需要有空格鍵:
[ "$HOME" == "$MAIL" ]
[□"$HOME"□==□"$MAIL"□]
↑ ↑ ↑ ↑
上面的例子在說明,兩個字符串 $HOME 與 $MAIL 是否相同的意思,相當于 test $HOME = $MAIL 的意思啦! 而如 果沒有空白分隔,例如 [$HOME==$MAIL] 時,我們的 bash 就會顯示錯誤訊息了!這可要很注意啊! 所以說,您最好要注意:
在中括號 [] 內的每個組件都需要有空格鍵來分隔;
在中括號內的變量,最好都以雙引號來設定;
在中括號內的常數,最好都以單或雙引號來設定。
舉例來說,假如我設定了 name="VBird Tsai" ,然后這樣判定:
[root@linux ~]# name="VBird Tsai"
[root@linux ~]# [ $name == "VBird" ]
bash: [: too many arguments
為什么呢?因為 $name 如果沒有使用雙引號刮起來,那么上面的判定式會變成:
[ VBird Tsai == "VBird" ]
而不是我們要的:
[ "VBird Tsai" == "VBird" ]
這可是差很多的喔!另外,中括號的使用方法與標志與 test 幾乎一模一樣啊~ 只是中括號比較常用在條件判斷式 if ..... then ..... fi 的情況中就是了。 好,那我們也繼續來做一個小案例好了:
當執行一個程序的時候,這個程序會讓使用者選擇 Y 或 N ,
如果使用者輸入 Y 或 y 時,就顯示『 OK, continue 』
如果使用者輸入 n 或 N 時,就顯示『 Oh, interrupt !』
如果不是 Y/y/N/n 之內的其它字符,就顯示『I don't know what is your choise』
利用中括號、 && 與 || 來繼續吧!
[root@linux scripts]# vi sh06.sh
#!/bin/bash
# Program:
# This program will show the user's choice
# History:
# 2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what is your choise" && exit 0
很有趣吧!利用這個字符串判別的方法,我們就可以很輕松的將使用者想要進行的工作分門別類呢! 接下來,我們再來談一些其它有的沒有的東西吧!
Tips:
為什么判斷式里面下達等于要用 == 而不是一個 = 就好了呢?我們在前一章正規表示法里面的 awk 提到, 只有一個 = 用來給予一個變量設定其內容,邏輯判斷時,則會給予兩個等于, 亦即『比較』而非『設定』的意思~這里要好好的分辨一下喔! ^_^
--------------------------------------------------------------------------------
Shell script 的預設變數($0, $1...)
其實,當我們執行一個 shell script 時,在這個 shell script 里面就已將幫我們做好一些可用的變量了。 舉例來說,在不久的將來,您就會發現,當我們要啟動一個系統服務時,可能會下達類似這樣的指令:
[root@linux ~]# /etc/init.d/crond restart
那是啥玩意兒?呵呵!就是『向 /etc/init.d/crond 這個 script 下達 restart 的指令』, 咦!我們不是都使 用 read 來讀取使用者輸入的變量內容嗎?為啥我可以直接在 script 后面接上這個參數? 這是因為 shell script 幫我們設定好 一些指定的變量了!變量的對應是這樣的:
/path/to/scriptname opt1 opt2 opt3 opt4 ...
$0 $1 $2 $3 $4 ...
這樣夠清楚了吧?!執行的文件名為 $0 這個變量,第一個接的參數就是 $1 啊~ 所以,只要我們在 script 里面善用 $1 的話, 就可以很簡單的立即下達某些指令功能了! 好了,來做個例子吧~假設我要執行一個 script ,執行后,該 script 會自動列出自己的檔名, 還有后面接的前三個參數,該如何是好?
[root@linux scripts]# vi sh07.sh
#!/bin/bash
# Program:
# The program will show it's name and first 3 parameters.
# History:
# 2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "The script naem is ==> $0"
[ -n "$1" ] && echo "The 1st paramter is ==> $1" || exit 0
[ -n "$2" ] && echo "The 2nd paramter is ==> $2" || exit 0
[ -n "$3" ] && echo "The 3th paramter is ==> $3" || exit 0
這支程序里面鳥哥加上了一些控制式,亦即利用 && 及 || 來加以判斷 $1 ~ $3 是否存在? 若存在才顯示,若不存在就中斷~執行結果如下:
[root@linux scripts]# sh sh07.sh theone haha quot
The script naem is ==> sh07.sh
The 1st paramter is ==> theone
The 2nd paramter is ==> haha
The 3th paramter is ==> quot
上面這七的例子都很簡單吧?幾乎都是利用 bash 的相關功能而已~ 不難啦~底下我們就要使用條件判斷式來進行一些分別功能的設定了,好好瞧一瞧先~
--------------------------------------------------------------------------------
條件判斷式:
只要講到『程序』的話,那么條件判斷式,亦即是『 if then 』這種判別式肯定一定要學習的! 因為很多時候,我們都必須要依據某些數據來 判斷程序該如何進行。舉例來說,我們在上頭不是有練習當使用者輸入 Y/N 時,必須要執行不同的訊息輸出嗎?簡單的方式可以利用 && 與 || ,但如果我還想要執行一堆指令呢? 那真的得要 if then 來幫忙啰~底下我們就來聊一聊!
--------------------------------------------------------------------------------
利用 if .... then
這個 if .... then 是最常見的條件判斷式了~簡單的說,就是當符合某個條件判斷的時候, 就予以進行某項工作就是了。我們可以簡單的這樣看:
if [ 條件判斷式 ]; then
當條件判斷式成立時,可以進行的指令工作內容;
fi
至于條件判斷式的判斷方法,與前一小節的介紹相同啊!較特別的是,如果我有多個條件要判別時, 除了 sh06.sh 那個案例,也就是將多個條 件寫入一個中括號內的情況之外, 我還可以有多個中括號來隔開喔!而括號與括號之間,則以 && 或 || 來隔開,他們的意義是:
&& 代表 AND ;
|| 代表 or ;
所以,在使用中括號的判斷式中, && 及 || 就與指令下達的狀態不同了。舉例來說, sh06.sh 那個例子我可以改寫成這樣:
[root@linux scripts]# vi sh06-2.sh
#!/bin/bash
# Program:
# This program will show the user's choice
# History:
# 2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
exit 0
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
exit 0
fi
echo "I don't know what is your choise" && exit 0
不過,由這個例子看起來,似乎也沒有什么了不起吧? sh06.sh 還比較簡單呢~ 但是,如果我們考慮底下的狀態,您就會知道 if then 的好處了:
if [ 條件判斷式 ]; then
當條件判斷式成立時,可以進行的指令工作內容;
else
當條件判斷式不成立時,可以進行的指令工作內容;
fi
如果考慮更復雜的情況,則可以使用這個語法:
if [ 條件判斷式一 ]; then
當條件判斷式一成立時,可以進行的指令工作內容;
elif [ 條件判斷式二 ]; then
當條件判斷式二成立時,可以進行的指令工作內容;
else
當條件判斷式一與二均不成立時,可以進行的指令工作內容;
fi
那我就可以將 sh06-2.sh 改寫成這樣:
[root@linux scripts]# vi sh06-3.sh
#!/bin/bash
# Program:
# This program will show the user's choice
# History:
# 2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
else
echo "I don't know what is your choise"
fi
是否程序變得很簡單,而且依序判斷,可以避免掉重復判斷的狀況,這樣真的很容易設計程序的啦! ^_^ 好了,那么如果我要偵測你所輸入的參數是 否為 hello 呢 , 也就是說,如果我想要知道,你在程序后面所接的第一個參數 (就是 $1 啊!) 是否為 hello ,
如果是的話,就顯示 "Hello, how are you ?";
如果沒有加任何參數,就提示使用者必須要使用的參數下達法;
而如果加入的參數不是 hello ,就提醒使用者僅能使用 hello 為參數。
整個程序的撰寫可以是這樣的:
[root@linux scripts]# vi sh08.sh
#!/bin/bash
# Program:
# Show "Hello" from $1....
# History:
# 2005/08/28 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
if [ "$1" == "hello" ]; then
echo "Hello, how are you ?"
elif [ "$1" == "" ]; then
echo "You MUST input parameters, ex> $0 someword"
else
echo "The only parameter is 'hello'"
fi
然后您可以執行這支程序,分別在 $1 的位置輸入 hello, 沒有輸入與隨意輸入, 就可以看到不同的輸出啰~是否還覺得挺簡單的啊! ^_^。事實上, 學到這里,也真的很厲害了~好了,底下我們繼續來玩一些比較大一點的啰~ 我們在前一章已經學會了 grep 這個好用的玩意兒,那 么多學一個叫做 netstat 的指令, 這個指令可以查詢到目前主機有開啟的網絡服務端口口 (service ports), 相關的功能我們會在 服務器架設篇繼續介紹,這里您只要知道,我可以利用『 netstat -tuln 』來取得目前主機有啟動的服務, 而且取得的信息有點像這樣:
[root@linux ~]# netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:199 0.0.0.0:* LISTEN
tcp 0 0 :::80 :::* LISTEN
tcp 0 0 :::22 :::* LISTEN
tcp 0 0 :::25 :::* LISTEN
上面的重點是特殊字體的那個部分,那些特殊字體的部分代表的就是 port 啰~ 那么每個 port 代表的意義為何呢?幾個常見的 port 與相關網絡服務的關系是:
80: WWW
22: ssh
21: ftp
25: mail
那我如何透過 netstat 去偵測我的主機是否有開啟這四個主要的網絡服務端口口呢? 我可以簡單的這樣去寫這個程序喔:
[root@linux scripts]# vi sh09.sh
#!/bin/bash
# Program:
# Using netstat and grep to detect WWW,SSH,FTP and Mail services.
# History:
# 2005/08/28 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 先作一些告知的動作而已~
echo "Now, the services of your Linux system will be detect!"
echo -e "The www, ftp, ssh, and mail will be detect! \n"
# 2. 開始進行一些測試的工作,并且也輸出一些信息啰!
testing=`netstat -tuln | grep ":80 "`
if [ "$testing" != "" ]; then
echo "WWW is running in your system."
fi
testing=`netstat -tuln | grep ":22 "`
if [ "$testing" != "" ]; then
echo "SSH is running in your system."
fi
testing=`netstat -tuln | grep ":21 "`
if [ "$testing" != "" ]; then
echo "FTP is running in your system."
fi
testing=`netstat -tuln | grep ":25 "`
if [ "$testing" != "" ]; then
echo "Mail is running in your system."
fi
這樣又能夠一個一個的檢查啰~是否很有趣啊! ^_^。接下來,我們再來玩更難一點的。 我們知道可以利用 date 來顯示日期與時間,也可以 利用 $((計算式)) 來計算數值運算。 另外, date 也可以用來顯示自 19710101 以來的『總秒數』 (請自行查閱 man date 及 info date) 。那么,您是否可以撰寫一支小程序,用來『計算退伍日期還剩幾天?』也就是說:
先讓使用者輸入他們的退伍日期;
再由現在日期比對退伍日期;
由兩個日期的比較來顯示『還需要幾天』才能夠退伍的字樣。
似乎挺難的樣子?其實也不會啦,利用『 date --date="YYYYMMDD" +%s 』就能夠達到我們所想要的啰~如果您已經寫完了程序,對照底下的寫法試看看:
[root@linux scripts]# vi sh10.sh
#!/bin/bash
# Program:
# Tring to calculate your demobilization date at how many days
# later...
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 告知使用者這支程序的用途,并且告知應該如何輸入日期格式?
echo "This program will try to calculate :"
echo "How many days about your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20050401): " date2
# 2. 測試一下,這個輸入的內容是否正確?利用正規表示法啰~
date_d=`echo $date2 |grep '[0-9]\{8\}'`
if [ "$date_d" == "" ]; then
echo "You input the wrong format of date...."
exit 1
fi
# 3. 開始計算日期啰~
declare -i date_dem=`date --date="$date2" +%s`
declare -i date_now=`date +%s`
declare -i date_total_s=$(($date_dem-$date_now))
declare -i date_d=$(($date_total_s/60/60/24))
if [ "$date_total_s" -lt "0" ]; then
echo "You had been demobilization before: " $((-1*$date_d)) " ago"
else
declare -i date_h=$(($(($date_total_s-$date_d*60*60*24))/60/60))
echo "You will be demobilized after $date_d days and $date_h hours."
fi
瞧一瞧,這支程序可以幫您計算退伍日期呢~如果是已經退伍的朋友, 還可以知道已經退伍多久了~哈哈!很可愛吧~利用 date 算出自 1971/01/01 以來的總秒數, 再與目前的總秒數來比對,然后以一天的總秒數 (60*60*24) 為基數去計算總日數, 就能夠得知兩者的 差異了~瞧~全部的動作都沒有超出我們所學的范圍吧~ ^_^ 還能夠避免使用者輸入錯誤的數字,所以多了一個正規表示法的判斷式呢~ 這個例子比較難, 有興趣想要一探究竟的朋友,可以作一下課后練習題 關于計算生日的那一題喔!~加油!
--------------------------------------------------------------------------------
利用 case ..... esac 判斷
上個小節提到的『 if .... then .... fi 』對于變量的判斷中, 是以比對的方式來分辨的,如果符合狀態就進行某些行為,并 且透過較多層次 ( 就是 elif ... ) 的方式來進行多個變量的程序代碼撰寫,譬如 sh08.sh 那個小程序,就是用這樣的方式來的啰。 好,那么萬一我有多個既定的變量內容,例如 sh08.sh 當中,我所需要的變量就是 "hello" 及空字符串兩個, 那么我只要針對這兩個變量來 設定狀況就好了對吧?!那么可以使用什么方式來設計呢? 呵呵~就用 case ... in .... esac 吧~,他的語法如下:
case $變量名稱 in
"第一個變量內容")
程序段
;;
"第二個變量內容")
程序段
;;
*)
不包含第一個變量內容與第二個變量內容的其它程序執行段
exit 1
;;
esac
要注意的是,這個語法是以 case 為開頭,而以 esac 為結尾,啥?為何是 esac 呢?想一想,既然 if 的結尾是 fi ,那么 case 的結尾當然就是將 case 倒著寫,自然就是 esac 啰~ ^_^,很好記吧~ 另外,每一個變量內容的程序段最后都需要兩個分號 (;;) 來代表該程序段落的結束,這挺重要的喔! 至于為何需要有 * 這個變量內容在最后呢?這是因為,如果使用者不是輸入變量內容一或二時, 我們 可以告知使用者相關的信息啊!舉例來說,我們如果將 sh08.sh 改寫的話, 他應該會變成這樣喔!
[root@linux scripts]# vi sh08-2.sh
#!/bin/bash
# Program:
# Show "Hello" from $1.... by using case .... esac
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
case $1 in
"hello")
echo "Hello, how are you ?"
;;
"")
echo "You MUST input parameters, ex> $0 someword"
;;
*)
echo "Usage $0 {hello}"
;;
esac
在上面這個 sh08-2.sh 的案例當中,如果你輸入『 sh sh08-2.sh test 』來執行, 那么屏幕上就會出現 『Usage sh08-2.sh {hello}』的字樣,告知執行者僅能夠使用 hello 喔~ 這樣的方式對于需要某些固定字符串來執行的變量內 容就顯的更加的方便呢? 這種方式您真的要熟悉喔!這是因為系統的很多服務的啟動 scripts 都是使用這種寫法的, 舉例來說,我們 Linux 的服務啟動放置目錄是在 /etc/init.d/ 當中,我已經知道里頭有個 syslog 的服務,我想要重新啟動這個服務,可以這樣做:
/etc/init.d/syslog restart
重點是那個 restart 啦~如果您進入 /etc/init.d/syslog 就會看到他使用的是 case 語法, 并且會規定某些既 定的變量內容,你可以直接下達 /etc/init.d/syslog , 該 script 就會告知你有哪些后續接的變量可以使用啰~方便吧! ^_^
一般來說,使用『 case $變量 in 』這個語法中,當中的那個 $變量 大致有兩種取得的方式:
直接下達式:例如上面提到的,利用『 script.sh variable 』 的方式來直接給予 $1 這個變量的內容,這也是在 /etc/init.d 目錄下大多數程序的設計方式。
交互式:透過 read 這個指令來讓使用者輸入變量的內容。
這么說或許您的感受性還不高,好,我們直接寫個程序來玩玩:讓使用者能夠輸入 one, two, three , 并且將使用者的變量顯示到屏幕上,如果不是 one, two, three 時,就告知使用者僅有這三種選擇。
[root@linux scripts]# vi sh11.sh
#!/bin/bash
# Program:
# Let user input one, two, three and show in screen.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "This program will print your selection !"
# read -p "Input your choice: " choice
# case $choice in
case $1 in
"one")
echo "Your choice is ONE"
;;
"two")
echo "Your choice is TWO"
;;
"three")
echo "Your choice is THREE"
;;
*)
echo "Usage {one|two|three}"
;;
esac
此時,您可以使用『 sh sh11.sh two 』的方式來下達指令,就可以收到相對應的響應了。 上面使用的是直接下達的方式,而如果使用 的是交互式時,那么將上面第 10, 11 行的 "#" 拿掉, 并將 12 行加上批注 (#),就可以讓使用者輸入參數啰~這樣是否很有趣啊?!
--------------------------------------------------------------------------------
利用 function 功能
什么是『函數 (function)』功能啊?簡單的說,其實, 函數可以在 shell script 當中做出一個類似自訂執行指令的東西, 最大的功能是, 可以簡化我們很多的程序代碼~舉例來說,上面的 sh11.sh 當中,每個輸入結果 one, two, three 其實輸出的內容 都一樣啊~那么我就可以使用 function 來簡化了! function 的語法是這樣的:
function fname() {
程序段
}
那個 fname 就是我們的自訂的執行指令名稱~而程序段就是我們要他執行的內容了。 要注意的是,在 shell script 當中, function 的設定一定要在程序的最前面, 這樣才能夠在執行時被找到可用的程序段喔!好~我們將 sh11.sh 改寫一下:
[root@linux scripts]# vi sh11-2.sh
#!/bin/bash
# Program:
# Let user input one, two, three and show in screen.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
function printit(){
echo -n "Your choice is "
}
echo "This program will print your selection !"
case $1 in
"one")
printit; echo $1 | tr 'a-z' 'A-Z'
;;
"two")
printit; echo $1 | tr 'a-z' 'A-Z'
;;
"three")
printit; echo $1 | tr 'a-z' 'A-Z'
;;
*)
echo "Usage {one|two|three}"
;;
esac
以上面的例子來說,我做了一個函數名稱為 printif ,所以,當我在后續的程序段里面, 只要執行 printit 的話,就表示我的 shell script 要去執行『 function printit .... 』 里面的那幾個程序段落啰! 當然啰,上面這個例子舉得太簡單 了,所以您不會覺得 function 有什么好厲害的, 不過,如果某些程序代碼一再地在 script 當中重復時,這個 function 可就重 要的多啰~ 不但可以簡化程序代碼,而且可以做成類似『模塊』的玩意兒,真的很棒啦!
另外, function 也是擁有內建變量的~他的內建變量與 shell script 很類似, 函數名稱代表示 $0 ,而后續接的變量 也是以 $1, $2... 來取代的~ 這里很容易搞錯喔~因為『 function fname() { 程序段 } 』內的 $0, $1... 等等與 shell script 的 $0 是不同的。以上面 sh11-2.sh 來說,假如我下達:『 sh sh11-2.sh one 』 這 表示在 shell script 內的 $1 為 "one" 這個字符串。但是在 printit() 內的 $1 則與這個 one 無關。 我們 將上面的例子再次的改寫一下,讓您更清楚!
[root@linux scripts]# vi sh11-3.sh
#!/bin/bash
# Program:
# Let user input one, two, three and show in screen.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
function printit(){
echo "Your choice is $1"
}
echo "This program will print your selection !"
case $1 in
"one")
printit 1
;;
"two")
printit 2
;;
"three")
printit 3
;;
*)
echo "Usage {one|two|three}"
;;
esac
在上面的例子當中,如果您輸入『 sh sh11-3.sh one 』就會出現『 Your choice is 1 』的字樣~ 為什么是 1 呢?因為在程序段落當中,我們是寫了『 printit 1 』那個 1 就會成為 function 當中的 $1 喔~ 這樣是否理解呢? function 本身其實比較困難一點,如果您還想要進行其它的撰寫的話。 不過,我們僅是想要更加了解 shell script 而已,所以,這 里看看即可~了解原理就好啰~ ^_^
--------------------------------------------------------------------------------
循環 (loop)
除了 if...then...fi 這種條件判斷式之外,循環可能是程序當中最重要的一環了~ 循環可以不斷的執行某個程序段落,直到使用者設定的條件達成為止。 所以,重點是那個『條件的達成』是什么。底下我們就來談一談:
--------------------------------------------------------------------------------
while do done, until do done
一般來說,循環最常見的就是底下這兩種狀態了:
while [ condition ]
do
程序段落
done
這種方式中, while 是『當....時』,所以,這種方式說的是『當 condition 條件成立時,就進行循環,直到 condition 的條件不成立才停止』的意思。
until [ condition ]
do
程序段落
done
這種方式恰恰與 while 相反,它說的是『當 condition 條件成立時,就終止循環, 否則就持續進行循環的程序段。』是否剛好相反 啊~我們以 while 來做個簡單的練習好了。 假設我要讓使用者輸入 yes 或者是 YES 才結束程序的執行,否則就一直進行告知使用者輸入字符 串。
[root@linux scripts]# vi sh12.sh
#!/bin/bash
# Program:
# Use loop to try find your input.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
while [ "$yn" != "yes" ] && [ "$yn" != "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
上面這個例題的說明是『當 $yn 這個變量不是 "yes" 且 $yn 也不是 "YES" 時,才進行循環內的程序。』 而如果 $yn 是 "yes" 或 "YES" 時,就會離開循環啰~那如果使用 until 呢?呵呵有趣啰~ 他的條件會變成這樣:
[root@linux scripts]# vi sh12-2.sh
#!/bin/bash
# Program:
# Use loop to try find your input.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
until [ "$yn" == "yes" ] || [ "$yn" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
仔細比對一下這兩個東西有啥不同喔! ^_^再來,如果我想要計算 1+2+3+....+100 這個數據呢? 利用循環啊~他是這樣的:
[root@linux scripts]# vi sh13.sh
#!/bin/bash
# Program:
# Try to use loop to calculate the result "1+2+3...+100"
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
s=0
i=0
while [ "$i" != "100" ]
do
i=$(($i+1))
s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s"
嘿嘿!當您執行了『 sh sh13.sh 』之后,就可以得到 5050 這個數據才對啊!這樣瞭呼~ 那么讓您自行做一下,如果想要讓使用者 自行輸入一個數字,讓程序由 1+2+... 直到您輸入的數字為止, 該如何撰寫呢?應該很簡單吧?!答案可以參考一下習題練習里面的一題喔!
--------------------------------------------------------------------------------
for...do....done
相對于 while, until 的循環方式是必須要『符合某個條件』的狀態, for 這種語法,則是『 已經知道要進行幾次循環』的狀態!他的語法是:
for (( 初始值; 限制值; 執行步階 ))
do
程序段
done
這種語法適合于數值方式的運算當中,在 for 后面的括號內的三串內容意義為:
初始值:某個變量在循環當中的起始值,直接以類似 i=1 設定好;
限制值:當變量的值在這個限制值的范圍內,就繼續進行循環。例如 i<=100;
執行步階:每作一次循環時,變量的變化量。例如 i=i+1。
值得注意的是,在『執行步階』的設定上,如果每次增加 1 ,則可以使用類似『i++』的方式,亦即是 i 每次循環都會增加一的意思。好,我們以這種方式來進行 1 累加到 100 的循環吧!
[root@linux scripts]# vi sh14.sh
#!/bin/bash
# Program:
# Try do calculate 1+2+....+100
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
s=0
for (( i=1; i<=100; i=i+1 ))
do
s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s"
一樣也是很簡單吧!利用這個 for 則可以直接限制循環要進行幾次呢!這么好用的東西難道只能在數值方面動作? 當然不是啦~我們還可以利用底下的方式來進行非數字方面的循環運作喔!
for $var in con1 con2 con3 ...
do
程序段
done
以上面的例子來說,這個 $var 的變量內容在循環工作時:
第一次循環時, $var 的內容為 con1 ;
第二次循環時, $var 的內容為 con2 ;
第三次循環時, $var 的內容為 con3 ;
....
我們可以做個簡單的練習。假設我有三種動物,分別是 dog, cat, elephant 三種, 我想每一行都輸出這樣:『There are dogs...』之類的字樣,則可以:
[root@linux scripts]# vi sh15.sh
#!/bin/bash
# Program:
# Using for .... loop to print 3 animal
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
for animal in dog cat elephant
do
echo "There are ""$animal""s.... "
done
很簡單是吧! ^_^。好了,那么如果我想要讓使用者輸入某個目錄, 然后我找出某目錄內的文件名的權限呢?又該如何是好?呵呵!可以這樣做啦~
[root@linux scripts]# vi sh16.sh
#!/bin/bash
# Program:
# let user input a directory and find the whole file's permission.
# History:
# 2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 先看看這個目錄是否存在啊?
read -p "Please input a directory: " dir
if [ "$dir" == "" ] || [ ! -d "$dir" ]; then
echo "The $dir is NOT exist in your system."
exit 1
fi
# 2. 開始測試檔案啰~
filelist=`ls $dir`
for filename in $filelist
do
perm=""
test -r "$dir/$filename" && perm="$perm readable"
test -w "$dir/$filename" && perm="$perm writable"
test -x "$dir/$filename" && perm="$perm executable"
echo "The file $dir/$filename's permission is $perm "
done
呵呵!很有趣的例子吧~利用這種方式,您可以很輕易的來處理一些檔案的特性呢~ 我們循環就介紹到這里了~其它更多的應用,就得視您的需求來玩啰~。
--------------------------------------------------------------------------------
shell script 的追蹤與 debug
scripts 在執行之前,最怕的就是出現問題了!那么我們如何 debug 呢?有沒有辦法不需要透過直接執行該 scripts 就可以來判斷是否有問題呢!?呵呵! 當然是有的!我們就直接以 bash 的相關參數來進行判斷吧!
[root@linux ~]# sh [-nvx] scripts.sh
參數:
-n :不要執行 script,僅查詢語法的問題;
-v :再執行 sccript 前,先將 scripts 的內容輸出到屏幕上;
-x :將使用到的 script 內容顯示到屏幕上,這是很有用的參數!
范例:
范例一:測試 sh16.sh 有無語法的問題?
[root@linux ~]# sh -n sh16.sh
# 若語法沒有問題,則不會顯示任何信息!
范例二:將 sh15.sh 的執行過程全部列出來~
[root@linux ~]# sh -x sh15.sh
+ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/vbird/bin
+ export PATH
+ for animal in dog cat elephant
+ echo 'There are dogs.... '
There are dogs....
+ for animal in dog cat elephant
+ echo 'There are cats.... '
There are cats....
+ for animal in dog cat elephant
+ echo 'There are elephants.... '
There are elephants....
# 使用 -x 真的是追蹤 script 的好方法,他可以將所有有執行的程序段在執行前列出來,
# 如果是程序段落,則輸出時,最前面會加上 + 字號,表示他是程序代碼而已,
# 實際的輸出則與 standard output 有關啊~如上所示。
在上面的范例二當中,我們可以透過這個簡單的參數 -x 來達成 debug 的目的,這可是一個不可多得的參數, 通常如果您執行 script 卻發生問題時,利用這個 -x 參數,就可以知道問題是發生在哪一行上面了!
熟悉 sh 的用法,將可以使您在管理 Linux 的過程中得心應手!至于在 Shell scripts 的學習方法上面,需要『多看、多模 仿、并加以修改成自己的樣式!』 是最快的學習手段了!網絡上有相當多的朋友在開發一些相當有用的 scripts ,若是您可以將對方的 scripts 拿來,并且改成適合自己主機的樣子!那么學習的效果會是最快的呢!
另外,我們 Linux 系統本來就有很多的啟動 script ,如果您想要知道每個 script 所代表的功能是什么? 可以直接以 vi 進入該 script 去查閱一下,通常立刻就知道該 script 的目的了。 舉例來說,我們的 Linux 里頭有個文件名稱為: /etc/init.d/portmap ,這個 script 是干嘛用的? 利用 vi 去查閱最前面的幾行字,他出現如下信息:
# description: The portmapper manages RPC connections, which are used by \
# protocols such as NFS and NIS. The portmap server must be \
# running on machines which act as servers for protocols which \
# make use of the RPC mechanism.
# processname: portmap
簡單的說,他是被用在 NFS 與 NIS 上面的一個啟動 RPC 的 script , 然后我們再利用 http: //www.google.com.tw 去搜尋一下 NFS, NIS 與 RPC , 立刻就能夠知道這個 script 的功能啰~所以,下次您發 現不明的 script 時, 如果是系統提供的,那么利用這個檢查的方式,一定可以約略了解的啦! 加油的啰~ ^_^
另外,本章所有的范例都可以在 http://linux.vbird.org/linux_basic/0340bashshell-scripts/scripts.tgz 里頭找到喔!加油~
--------------------------------------------------------------------------------
本章習題練習
( 要看答案請將鼠標移動到『答:』底下的空白處,按下左鍵圈選空白處即可察看 )
請建立一支 script ,當你執行該 script 的時候,該 script 可以顯示: 1. 你目前的身份 (用 whoami ) 2. 你目前所在的目錄 (用 pwd)
#!/bin/bash
echo -e "Your name is ==> `whoami`"
echo -e "The current directory is ==> `pwd`"
請自行建立一支程序,該程序可以用來計算『您還有幾天可以過生日』啊??
#!/bin/bash
read -p "Pleas input your birthday (MMDD, ex> 0709): " bir
now=`date +%m%d`
if [ "$bir" == "$now" ]; then
echo "Happy Birthday to you!!!"
elif [ "$bir" -gt "$now" ]; then
year=`date +%Y`
total_d=$(($((`date --date="$year$bir" +%s`-`date +%s`))/60/60/24))
echo "Your birthday will be $total_d later"
else
year=$((`date +%Y`+1))
total_d=$(($((`date --date="$year$bir" +%s`-`date +%s`))/60/60/24))
echo "Your birthday will be $total_d later"
fi
讓使用者輸入一個數字,程序可以由 1+2+3... 一直累加到使用者輸入的數字為止。
#!/bin/bash
read -p "Please input an integer number: " number
i=0
s=0
while [ "$i" != "$number" ]
do
i=$(($i+1))
s=$(($s+$i))
done
echo "the result of '1+2+3+...$number' is ==> $s"
撰寫一支程序,他的作用是: 1.) 先查看一下 /root/test/logical 這個名稱是否存在; 2.) 若不存在,則建立一個檔 案,使用 touch 來建立,建立完成后離開; 3.) 如果存在的話,判斷該名稱是否為檔案,若為檔案則將之刪除后建立一個檔案,檔名為 logical ,之后離開; 4.) 如果存在的話,而且該名稱為目錄,則移除此目錄!
#!/bin/bash
if [ ! -e logical ]; then
touch logical
echo "Just make a file logical"
exit 1
elif [ -e logical ] && [ -f logical ]; then
rm logical
mkdir logical
echo "remove file ==> logical"
echo "and make directory logical"
exit 1
elif [ -e logical ] && [ -d logical ]; then
rm -rf logical
echo "remove directory ==> logical"
exit 1
else
echo "Does here have anything?"
fi
我們知道 /etc/passwd 里面以 : 來分隔,第一欄為賬號名稱。請寫一只程序,可以將 /etc/passwd 的第一欄取出,而且每一欄都以一行字符串『The 1 account is "root" 』來顯示,那個 1 表示行數。
#!/bin/bash
accounts=`cat /etc/passwd | cut -d':' -f1`
for account in $accounts
do
declare -i i=$i+1
echo "The $i account is \"$account\" "
done