當前位置:首頁 » 硬件·內核·Shell·監測 » Shell scripts

Shell scripts命令

文章為轉載而來,原文鏈接http://cn.linux.vbird.org/linux_basic/0340bashshell-scripts.php

什么是 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 就不可不知,為什么呢?因為:

  • 自動化管理的重要依據:

    不用鳥哥說你也知道,管理一部主機真不是件簡單的事情,每天要進行的任務就有: 查詢登錄檔、追蹤流量、監控使用者使用主機狀態、主機各項硬件設備狀態、 主機軟件升級查詢、更不要說得應付其他使用者的突然要求了。而這些工作的進行可以分為: (1)自行手動處理,或是 (2)寫個簡單的程序來幫你每日自動處理分析這兩種方式,你覺得哪種方式比較好? 當然是讓系統自動工作比較好,對吧!呵呵~這就得要良好的 shell script 來幫忙的啦!
     

  • 追蹤與管理系統的重要工作:

    雖然我們還沒有提到服務啟動的方法,不過,這里可以先提一下,我們 Linux 系統的服務 (services) 啟動的介面是在 /etc/init.d/ 這個目錄下,目錄下的所有文件都是 scripts ; 另外,包括啟動 (booting) 過程也都是利用 shell script 來幫忙搜尋系統的相關配置數據, 然后再代入各個服務的配置參數??!舉例來說,如果我們想要重新啟動系統登錄檔, 可以使用:『/etc/init.d/syslogd restart』,那個 syslogd 文件就是 script 啦!

    另外,鳥哥曾經在某一代的 Fedora 上面發現,啟動 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 系列也有相關的 script 模擬器可以用, 此外, shell script 的語法是相當親和的,看都看的懂得文字 (雖然是英文),而不是機器碼, 很容易學習~這些都是你可以加以考量的學習點??!

上面這些都是你考慮學習 shell script 的特點~此外, shell script 還可以簡單的以 vim 來直接編寫,實在是很方便的好東西!所以,還是建議你學習一下啦。

不過,雖然 shell script 號稱是程序 (program) ,但實際上, shell script 處理數據的速度上是不太夠的。因為 shell script 用的是外部的命令與 bash shell 的一些默認工具,所以,他常常會去呼叫外部的函式庫,因此,運算速度上面當然比不上傳統的程序語言。 所以羅, shell script 用在系統管理上面是很好的一項工具,但是用在處理大量數值運算上, 就不夠好了,因為 Shell scripts 的速度較慢,且使用的 CPU 資源較多,造成主機資源的分配不良。還好, 我們通常利用 shell script 來處理服務器的偵測,倒是沒有進行大量運算的需求??!所以不必擔心的啦!


小標題的圖示第一支 script 的撰寫與運行

如同前面講到的,shell script 其實就是純文字檔,我們可以編輯這個文件,然后讓這個文件來幫我們一次運行多個命令, 或者是利用一些運算與邏輯判斷來幫我們達成某些功能。所以啦,要編輯這個文件的內容時,當然就需要具備有 bash 命令下達的相關認識。下達命令需要注意的事項在第五章的開始下達命令小節內已經提過,有疑問請自行回去翻閱。 在 shell script 的撰寫中還需要用到底下的注意事項:

  1. 命令的運行是從上而下、從左而右的分析與運行;
  2. 命令的下達就如同第五章內提到的: 命令、選項與參數間的多個空白都會被忽略掉;
  3. 空白行也將被忽略掉,并且 [tab] 按鍵所推開的空白同樣視為空白鍵;
  4. 如果讀取到一個 Enter 符號 (CR) ,就嘗試開始運行該行 (或該串) 命令;
  5. 至於如果一行的內容太多,則可以使用『 \[Enter] 』來延伸至下一行;
  6. 『 # 』可做為注解!任何加在 # 后面的數據將全部被視為注解文字而被忽略!

如此一來,我們在 script 內所撰寫的程序,就會被一行一行的運行?,F在我們假設你寫的這個程序檔名是 /home/dmtsai/shell.sh 好了,那如何運行這個文件?很簡單,可以有底下幾個方法:

  • 直接命令下達: shell.sh 文件必須要具備可讀與可運行 (rx) 的權限,然后:

    • 絕對路徑:使用 /home/dmtsai/shell.sh 來下達命令;
    • 相對路徑:假設工作目錄在 /home/dmtsai/ ,則使用 ./shell.sh 來運行
    • 變量『PATH』功能:將 shell.sh 放在 PATH 指定的目錄內,例如: ~/bin/
  • 以 bash 程序來運行:透過『 bash shell.sh 』或『 sh shell.sh 』來運行

反正重點就是要讓那個 shell.sh 內的命令可以被運行的意思啦! 咦!那我為何需要使用 『./shell.sh 』來下達命令?忘記了嗎?回去第十一章內的命令搜尋順序察看一下, 你就會知道原因了!同時,由於 CentOS 默認使用者家目錄下的 ~/bin 目錄會被配置到 $PATH 內,所以你也可以將 shell.sh 創建在 /home/dmtsai/bin/ 底下 ( ~/bin 目錄需要自行配置) 。此時,若 shell.sh 在 ~/bin 內且具有 rx 的權限,那就直接輸入 shell.sh 即可運行該腳本程序!

那為何『 sh shell.sh 』也可以運行呢?這是因為 /bin/sh 其實就是 /bin/bash (連結檔),使用 sh shell.sh 亦即告訴系統,我想要直接以 bash 的功能來運行 shell.sh 這個文件內的相關命令的意思,所以此時你的 shell.sh 只要有 r 的權限即可被運行喔!而我們也可以利用 sh 的參數,如 -n 及 -x 來檢查與追蹤 shell.sh 的語法是否正確呢! ^_^
 


  • 撰寫第一支 script

在武俠世界中,不論是那個門派,要學武功要從掃地做起,那么要學程序呢?呵呵,肯定是由『秀出 Hello World!』 這個字眼開始的!OK!那么鳥哥就先寫一支 script 給大家瞧一瞧:

[root@www ~]# mkdir scripts; cd scripts
[root@www scripts]# vi sh01.sh
#!/bin/bash
# Program:
#       This program shows "Hello World!" in your 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 這個目錄內, 未來比較好管理啦!上面的寫法當中,鳥哥主要將整個程序的撰寫分成數段,大致是這樣:

  1. 第一行 #!/bin/bash 在宣告這個 script 使用的 shell 名稱:
    因為我們使用的是 bash ,所以,必須要以『 #!/bin/bash 』來宣告這個文件內的語法使用 bash 的語法!那么當這個程序被運行時,他就能夠加載 bash 的相關環境配置檔 (一般來說就是 non-login shell 的 ~/.bashrc), 并且運行 bash 來使我們底下的命令能夠運行!這很重要的!(在很多狀況中,如果沒有配置好這一行, 那么該程序很可能會無法運行,因為系統可能無法判斷該程序需要使用什么 shell 來運行??!)
     
  2. 程序內容的說明:
    整個 script 當中,除了第一行的『 #! 』是用來宣告 shell 的之外,其他的 # 都是『注解』用途! 所以上面的程序當中,第二行以下就是用來說明整個程序的基本數據。一般來說, 建議你一定要養成說明該 script 的:1. 內容與功能; 2. 版本資訊; 3. 作者與聯絡方式; 4. 建檔日期;5. 歷史紀錄 等等。這將有助於未來程序的改寫與 debug 呢!
     
  3. 主要環境變量的宣告:
    建議務必要將一些重要的環境變量配置好,鳥哥個人認為, PATH 與 LANG (如果有使用到輸出相關的資訊時) 是當中最重要的! 如此一來,則可讓我們這支程序在進行時,可以直接下達一些外部命令,而不必寫絕對路徑呢!比較好啦!
     
  4. 主要程序部分
    就將主要的程序寫好即可!在這個例子當中,就是 echo 那一行啦!
     
  5. 運行成果告知 (定義回傳值)
    是否記得我們在第十一章里面要討論一個命令的運行成功與否,可以使用 $? 這個變量來觀察~ 那么我們也可以利用 exit 這個命令來讓程序中斷,并且回傳一個數值給系統。 在我們這個例子當中,鳥哥使用 exit 0 ,這代表離開 script 并且回傳一個 0 給系統, 所以我運行完這個 script 后,若接著下達 echo $? 則可得到 0 的值喔! 更聰明的讀者應該也知道了,呵呵!利用這個 exit n (n 是數字) 的功能,我們還可以自訂錯誤信息, 讓這支程序變得更加的 smart 呢!

接下來透過剛剛上頭介紹的運行方法來運行看看結果吧!

[root@www 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 運行時需要的環境變量預先宣告與配置。

除了記錄這些資訊之外,在較為特殊的程序碼部分,個人建議務必要加上注解說明,可以幫助你非常非常多! 此外,程序碼的撰寫最好使用巢狀方式,在包覆的內部程序碼最好能以 [tab] 按鍵的空格向后推, 這樣你的程序碼會顯的非常的漂亮與有條理!在查閱與 debug 上較為輕松愉快喔! 另外,使用撰寫 script 的工具最好使用 vim 而不是 vi ,因為 vim 會有額外的語法檢驗機制,能夠在第一階段撰寫時就發現語法方面的問題喔!


大標題的圖示簡單的 shell script 練習

在第一支 shell script 撰寫完畢之后,相信你應該具有基本的撰寫功力了。 接下來,在開始更深入的程序概念之前,我們先來玩一些簡單的小范例好了。 底下的范例中,達成結果的方式相當的多,建議你先自行撰寫看看,寫完之后再與鳥哥寫的內容比對, 這樣才能更加深概念喔!好!不羅唆,我們就一個一個來玩吧!
 


小標題的圖示簡單范例

底下的范例在很多的腳本程序中都會用到,而底下的范例又都很簡單!值得參考看看喔!
 


  • 對談式腳本:變量內容由使用者決定

很多時候我們需要使用者輸入一些內容,好讓程序可以順利運行。 簡單的來說,大家應該都有安裝過軟件的經驗,安裝的時候,他不是會問你『要安裝到那個目錄去』嗎? 那個讓使用者輸入數據的動作,就是讓使用者輸入變量內容啦。

你應該還記得在十一章 bash 的時候,我們有學到一個 read 命令吧?現在,請你以 read 命令的用途,撰寫一個 script ,他可以讓使用者輸入:1. first name 與 2. last name, 最后并且在螢幕上顯示:『Your full name is: 』的內容:

[root@www scripts]# vi sh02.sh
#!/bin/bash
# Program:
#	User inputs his first name and last name.  Program shows his 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.2009-02-14.data , 不就可以每天一個不同檔名了嗎?呵呵!確實如此。那個 2009-02-14 怎么來的?那就是重點啦!接下來出個相關的例子: 假設我想要創建三個空的文件 (透過 touch) ,檔名最開頭由使用者輸入決定,假設使用者輸入 filename 好了,那今天的日期是 2009/02/14 , 我想要以前天、昨天、今天的日期來創建這些文件,亦即 filename_20090212, filename_20090213, filename_20090214 ,該如何是好?

[root@www scripts]# vi sh03.sh
#!/bin/bash
# Program:
#	Program creates three files, which named by user's input 
#	and date command.
# 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 your filename: " 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"

上面的范例鳥哥使用了很多在十一章介紹過的概念: 包括小命令『 $(command) 』的取得信息、變量的配置功能、變量的累加以及利用 touch 命令輔助! 如果你開始運行這個 sh03.sh 之后,你可以進行兩次運行:一次直接按 [Enter] 來查閱檔名是啥? 一次可以輸入一些字節,這樣可以判斷你的腳本是否設計正確喔!
 


  • 數值運算:簡單的加減乘除

各位看官應該還記得,我們可以使用 declare 來定義變量的類型吧? 當變量定義成為整數后才能夠進行加減運算??!此外,我們也可以利用『 $((計算式)) 』來進行數值運算的。 可惜的是, bash shell 里頭默認僅支持到整數的數據而已。OK!那我們來玩玩看,如果我們要使用者輸入兩個變量, 然后將兩個變量的內容相乘,最后輸出相乘的結果,那可以怎么做?

[root@www scripts]# vi sh04.sh
#!/bin/bash
# Program:
#	User inputs 2 integer numbers; program will cross these two numbers.
# 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 numbers, I will cross them! \n"
read -p "first number:  " firstnu
read -p "second number: " secnu
total=$(($firstnu*$secnu))
echo -e "\nThe result of $firstnu x $secnu is ==> $total"

在數值的運算上,我們可以使用『 declare -i total=$firstnu*$secnu 』 也可以使用上面的方式來進行!基本上,鳥哥比較建議使用這樣的方式來進行運算:

var=$((運算內容))

不但容易記憶,而且也比較方便的多,因為兩個小括號內可以加上空白字節喔! 未來你可以使用這種方式來計算的呀!至於數值運算上的處理,則有:『 +, -, *, /, % 』等等。 那個 % 是取余數啦~舉例來說, 13 對 3 取余數,結果是 13=4*3+1,所以余數是 1 ??!就是:

[root@www scripts]# echo $(( 13 % 3 ))
1

這樣了解了吧?多多學習與應用喔! ^_^


小標題的圖示script 的運行方式差異 (source, sh script, ./script)

不同的 script 運行方式會造成不一樣的結果喔!尤其影響 bash 的環境很大呢!腳本的運行方式除了前面小節談到的方式之外,還可以利用 source 或小數點 (.) 來運行喔!那么這種運行方式有何不同呢?當然是不同的啦!讓我們來說說!
 


  • 利用直接運行的方式來運行 script

當使用前一小節提到的直接命令下達 (不論是絕對路徑/相對路徑還是 $PATH 內),或者是利用 bash (或 sh) 來下達腳本時, 該 script 都會使用一個新的 bash 環境來運行腳本內的命令!也就是說,使用者種運行方式時, 其實 script 是在子程序的 bash 內運行的!我們在第十一章 BASH 內談到 export 的功能時,曾經就父程序/子程序談過一些概念性的問題, 重點在於:『當子程序完成后,在子程序內的各項變量或動作將會結束而不會傳回到父程序中』! 這是什么意思呢?

我們舉剛剛提到過的 sh02.sh 這個腳本來說明好了,這個腳本可以讓使用者自行配置兩個變量,分別是 firstname 與 lastname,想一想,如果你直接運行該命令時,該命令幫你配置的 firstname 會不會生效?看一下底下的運行結果:

[root@www scripts]# echo $firstname $lastname
    <==確認了,這兩個變量并不存在喔!
[root@www scripts]# sh sh02.sh
Please input your first name: VBird <==這個名字是鳥哥自己輸入的
Please input your last name:  Tsai 

Your full name is: VBird Tsai      <==看吧!在 script 運行中,這兩個變量有生效
[root@www scripts]# echo $firstname $lastname
    <==事實上,這兩個變量在父程序的 bash 中還是不存在的!

上面的結果你應該會覺得很奇怪,怎么我已經利用 sh02.sh 配置好的變量竟然在 bash 環境底下無效!怎么回事呢? 如果將程序相關性繪制成圖的話,我們以下圖來說明。當你使用直接運行的方法來處理時,系統會給予一支新的 bash 讓我們來運行 sh02.sh 里面的命令,因此你的 firstname, lastname 等變量其實是在下圖中的子程序 bash 內運行的。 當 sh02.sh 運行完畢后,子程序 bash 內的所有數據便被移除,因此上表的練習中,在父程序底下 echo $firstname 時, 就看不到任何東西了!這樣可以理解嗎?


sh02.sh 在子程序中運行
圖 2.2.1、sh02.sh 在子程序中運行

 


  • 利用 source 來運行腳本:在父程序中運行

如果你使用 source 來運行命令那就不一樣了!同樣的腳本我們來運行看看:

[root@www scripts]# source sh02.sh
Please input your first name: VBird
Please input your last name:  Tsai

Your full name is: VBird Tsai
[root@www scripts]# echo $firstname $lastname
VBird Tsai  <==嘿嘿!有數據產生喔!

竟然生效了!沒錯??!因為 source 對 script 的運行方式可以使用底下的圖示來說明! sh02.sh 會在父程序中運行的,因此各項動作都會在原本的 bash 內生效!這也是為啥你不注銷系統而要讓某些寫入 ~/.bashrc 的配置生效時,需要使用『 source ~/.bashrc 』而不能使用『 bash ~/.bashrc 』是一樣的??!


sh02.sh 在父程序中運行
圖 2.2.2、sh02.sh 在父程序中運行


大標題的圖示善用判斷式

在第十一章中,我們提到過 $? 這個變量所代表的意義, 此外,也透過 && 及 || 來作為前一個命令運行回傳值對於后一個命令是否要進行的依據。第十一章的討論中,如果想要判斷一個目錄是否存在, 當時我們使用的是 ls 這個命令搭配數據流重導向,最后配合 $? 來決定后續的命令進行與否。 但是否有更簡單的方式可以來進行『條件判斷』呢?有的~那就是『 test 』這個命令。
 


小標題的圖示利用 test 命令的測試功能

當我要檢測系統上面某些文件或者是相關的屬性時,利用 test 這個命令來工作真是好用得不得了, 舉例來說,我要檢查 /dmtsai 是否存在時,使用:

[root@www ~]# test -e /dmtsai

運行結果并不會顯示任何信息,但最后我們可以透過 $? 或 && 及 || 來展現整個結果呢! 例如我們在將上面的例子改寫成這樣:

[root@www ~]# test -e /dmtsai && echo "exist" || echo "Not exist"
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 表示可讀否 (但 root 權限常有例外)
-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 判斷 file1 與 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 來幫我們寫幾個簡單的例子。首先,判斷一下,讓使用者輸入一個檔名,我們判斷:

  1. 這個文件是否存在,若不存在則給予一個『Filename does not exist』的信息,并中斷程序;
  2. 若這個文件存在,則判斷他是個文件或目錄,結果輸出『Filename is regular file』或 『Filename is directory』
  3. 判斷一下,運行者的身份對這個文件或目錄所擁有的權限,并輸出權限數據!

你可以先自行創作看看,然后再跟底下的結果討論討論。注意利用 test 與 && 還有 || 等標志!

[root@www scripts]# vi sh05.sh
#!/bin/bash
# Program:
#	User input a filename, program will check the flowing:
#	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 "Please input a filename, I will check the filename's type and \
permission. \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 permissions are : $perm"

如果你運行這個腳本后,他會依據你輸入的檔名來進行檢查喔!先看是否存在,再看為文件或目錄類型,最后判斷權限。 但是你必須要注意的是,由於 root 在很多權限的限制上面都是無效的,所以使用 root 運行這個腳本時, 常常會發現與 ls -l 觀察到的結果并不相同!所以,建議使用一般使用者來運行這個腳本試看看。 不過你必須要使用 root 的身份先將這個腳本搬移給使用者就是了,不然一般使用者無法進入 /root 目錄的。 很有趣的例子吧!你可以自行再以其他的案例來撰寫一下可用的功能呢!


小標題的圖示利用判斷符號 [ ]

除了我們很喜歡使用的 test 之外,其實,我們還可以利用判斷符號『 [ ] 』(就是中括號啦) 來進行數據的判斷呢! 舉例來說,如果我想要知道 $HOME 這個變量是否為空的,可以這樣做:

[root@www ~]# [ -z "$HOME" ] ; echo $?

使用中括號必須要特別注意,因為中括號用在很多地方,包括萬用字節與正規表示法等等,所以如果要在 bash 的語法當中使用中括號作為 shell 的判斷式時,必須要注意中括號的兩端需要有空白字節來分隔喔! 假設我空白鍵使用『□』符號來表示,那么,在這些地方你都需要有空白鍵:

[  "$HOME"  ==  "$mail"  ]
[□"$HOME"□==□"$MAIL"□]
 ↑       ↑  ↑       ↑
Tips:
你會發現鳥哥在上面的判斷式當中使用了兩個等號『 == 』。其實在 bash 當中使用一個等號與兩個等號的結果是一樣的! 不過在一般慣用程序的寫法中,一個等號代表『變量的配置』,兩個等號則是代表『邏輯判斷 (是否之意)』。 由於我們在中括號內重點在於『判斷』而非『配置變量』,因此鳥哥建議您還是使用兩個等號較佳!
鳥哥的圖示

上面的例子在說明,兩個字串 $HOME 與 $MAIL 是否相同的意思,相當於 test $HOME = $MAIL 的意思啦! 而如果沒有空白分隔,例如 [$HOME==$MAIL] 時,我們的 bash 就會顯示錯誤信息了!這可要很注意??! 所以說,你最好要注意:

  • 在中括號 [] 內的每個組件都需要有空白鍵來分隔;
  • 在中括號內的變量,最好都以雙引號括號起來;
  • 在中括號內的常數,最好都以單或雙引號括號起來。

為什么要這么麻煩???直接舉例來說,假如我配置了 name="VBird Tsai" ,然后這樣判定:

[root@www ~]# name="VBird Tsai"
[root@www ~]# [ $name == "VBird" ]
bash: [: too many arguments

見鬼了!怎么會發生錯誤???bash 還跟我說錯誤是由於『太多參數 (arguments)』所致! 為什么呢?因為 $name 如果沒有使用雙引號刮起來,那么上面的判定式會變成:

[ VBird Tsai == "VBird" ]

上面肯定不對嘛!因為一個判斷式僅能有兩個數據的比對,上面 VBird 與 Tsai 還有 "VBird" 就有三個數據! 這不是我們要的!我們要的應該是底下這個樣子:

[ "VBird Tsai" == "VBird" ]

這可是差很多的喔!另外,中括號的使用方法與 test 幾乎一模一樣啊~ 只是中括號比較常用在條件判斷式 if ..... then ..... fi 的情況中就是了。 好,那我們也使用中括號的判斷來做一個小案例好了,案例配置如下:

  1. 當運行一個程序的時候,這個程序會讓使用者選擇 Y 或 N ,
  2. 如果使用者輸入 Y 或 y 時,就顯示『 OK, continue 』
  3. 如果使用者輸入 n 或 N 時,就顯示『 Oh, interrupt !』
  4. 如果不是 Y/y/N/n 之內的其他字節,就顯示『 I don't know what your choice is 』

利用中括號、 && 與 || 來繼續吧!

[root@www scripts]# vi sh06.sh
#!/bin/bash
# Program:
# 	This program shows 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 your choice is" && exit 0

由於輸入正確 (yes) 的方法有大小寫之分,不論輸入大寫 Y 或小寫 y 都是可以的,此時判斷式內就得要有兩個判斷才行! 由於是任何一個成立即可 (大小或小寫的 y) ,所以這里使用 -o (或) 連結兩個判斷喔! 很有趣吧!利用這個字串判別的方法,我們就可以很輕松的將使用者想要進行的工作分門別類呢! 接下來,我們再來談一些其他有的沒有的東西吧!


小標題的圖示Shell script 的默認變量($0, $1...)

我們知道命令可以帶有選項與參數,例如 ls -la 可以察看包含隱藏檔的所有屬性與權限。那么 shell script 能不能在腳本檔名后面帶有參數呢?很有趣喔!舉例來說,如果你想要重新啟動系統登錄檔的功能,可以這樣做:

[root@www ~]# file /etc/init.d/syslog
/etc/init.d/syslog: Bourne-Again shell script text executable
# 使用 file 來查詢后,系統告知這個文件是個 bash 的可運行 script 喔!
[root@www ~]# /etc/init.d/syslog restart

restart 是重新啟動的意思,上面的命令可以『重新啟動 /etc/init.d/syslog 這支程序』的意思! 唔!那么如果你在 /etc/init.d/syslog 后面加上 stop 呢?沒錯!就可以直接關閉該服務了!這么神奇??? 沒錯??!如果你要依據程序的運行給予一些變量去進行不同的任務時,本章一開始是使用 read 的功能!但 read 功能的問題是你得要手動由鍵盤輸入一些判斷式。如果透過命令后面接參數, 那么一個命令就能夠處理完畢而不需要手動再次輸入一些變量行為!這樣下達命令會比較簡單方便啦!

script 是怎么達成這個功能的呢?其實 script 針對參數已經有配置好一些變量名稱了!對應如下:

/path/to/scriptname  opt1  opt2  opt3  opt4 
       $0             $1    $2    $3    $4

這樣夠清楚了吧?運行的腳本檔名為 $0 這個變量,第一個接的參數就是 $1 啊~ 所以,只要我們在 script 里面善用 $1 的話,就可以很簡單的立即下達某些命令功能了!除了這些數字的變量之外, 我們還有一些較為特殊的變量可以在 script 內使用來呼叫這些參數喔!

  • $# :代表后接的參數『個數』,以上表為例這里顯示為『 4 』;
  • $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每個變量是獨立的(用雙引號括起來);
  • $* :代表『 "$1c$2c$3c$4" 』,其中 c 為分隔字節,默認為空白鍵, 所以本例中代表『 "$1 $2 $3 $4" 』之意。

那個 $@ 與 $* 基本上還是有所不同啦!不過,一般使用情況下可以直接記憶 $@ 即可! 好了,來做個例子吧~假設我要運行一個可以攜帶參數的 script ,運行該腳本后螢幕會顯示如下的數據:

  • 程序的檔名為何?
  • 共有幾個參數?
  • 若參數的個數小於 2 則告知使用者參數數量太少
  • 全部的參數內容為何?
  • 第一個參數為何?
  • 第二個參數為何
[root@www scripts]# vi sh07.sh
#!/bin/bash
# Program:
#	Program shows the script name, parameters...
# History:
# 2009/02/17	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "The script name is        ==> $0"
echo "Total parameter number is ==> $#"
[ "$#" -lt 2 ] && echo "The number of parameter is less than 2.  Stop here." \
	&& exit 0
echo "Your whole parameter is   ==> '$@'"
echo "The 1st parameter         ==> $1"
echo "The 2nd parameter         ==> $2"

運行結果如下:

[root@www scripts]# sh sh07.sh theone haha quot
The script name is        ==> sh07.sh            <==檔名
Total parameter number is ==> 3                  <==果然有三個參數
Your whole parameter is   ==> 'theone haha quot' <==參數的內容全部
The 1st parameter         ==> theone             <==第一個參數
The 2nd parameter         ==> haha               <==第二個參數



  • shift:造成參數變量號碼偏移

除此之外,腳本后面所接的變量是否能夠進行偏移 (shift) 呢?什么是偏移???我們直接以底下的范例來說明好了, 用范例說明比較好解釋!我們將 sh07.sh 的內容稍作變化一下,用來顯示每次偏移后參數的變化情況:

[root@www scripts]# vi sh08.sh
#!/bin/bash
# Program:
#	Program shows the effect of shift function.
# History:
# 2009/02/17	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "Total parameter number is ==> $#"
echo "Your whole parameter is   ==> '$@'"
shift   # 進行第一次『一個變量的 shift 』
echo "Total parameter number is ==> $#"
echo "Your whole parameter is   ==> '$@'"
shift 3 # 進行第二次『三個變量的 shift 』
echo "Total parameter number is ==> $#"
echo "Your whole parameter is   ==> '$@'"

這玩意的運行成果如下:

[root@www scripts]# sh sh08.sh one two three four five six <==給予六個參數
Total parameter number is ==> 6   <==最原始的參數變量情況
Your whole parameter is   ==> 'one two three four five six'
Total parameter number is ==> 5   <==第一次偏移,看底下發現第一個 one 不見了
Your whole parameter is   ==> 'two three four five six'
Total parameter number is ==> 2   <==第二次偏移掉三個,two three four 不見了
Your whole parameter is   ==> 'five six'

光看結果你就可以知道啦,那個 shift 會移動變量,而且 shift 后面可以接數字,代表拿掉最前面的幾個參數的意思。 上面的運行結果中,第一次進行 shift 后他的顯示情況是『 one two three four five six』,所以就剩下五個啦!第二次直接拿掉三個,就變成『 two three four five six 』啦! 這樣這個案例可以了解了嗎?理解了 shift 的功能了嗎?

上面這8個例子都很簡單吧?幾乎都是利用 bash 的相關功能而已~ 不難啦~底下我們就要使用條件判斷式來進行一些分別功能的配置了,好好瞧一瞧先~


大標題的圖示條件判斷式

只要講到『程序』的話,那么條件判斷式,亦即是『 if then 』這種判別式肯定一定要學習的! 因為很多時候,我們都必須要依據某些數據來判斷程序該如何進行。舉例來說,我們在上頭的 sh06.sh 范例中不是有練習當使用者輸入 Y/N 時,必須要運行不同的信息輸出嗎?簡單的方式可以利用 && 與 || ,但如果我還想要運行一堆命令呢?那真的得要 if then 來幫忙羅~底下我們就來聊一聊!
 


小標題的圖示利用 if .... then

這個 if .... then 是最常見的條件判斷式了~簡單的說,就是當符合某個條件判斷的時候, 就予以進行某項工作就是了。這個 if ... then 的判斷還有多層次的情況!我們分別介紹如下:
 


  • 單層、簡單條件判斷式

如果你只有一個判斷式要進行,那么我們可以簡單的這樣看:

if [ 條件判斷式 ]; then
	當條件判斷式成立時,可以進行的命令工作內容;
fi   <==將 if 反過來寫,就成為 fi 啦!結束 if 之意!

至於條件判斷式的判斷方法,與前一小節的介紹相同??!較特別的是,如果我有多個條件要判別時, 除了 sh06.sh 那個案例所寫的,也就是『將多個條件寫入一個中括號內的情況』之外, 我還可以有多個中括號來隔開喔!而括號與括號之間,則以 && 或 || 來隔開,他們的意義是:

  • && 代表 AND ;
  • || 代表 or ;

所以,在使用中括號的判斷式中, && 及 || 就與命令下達的狀態不同了。舉例來說, sh06.sh 里面的判斷式可以這樣修改:

[ "$yn" == "Y" -o "$yn" == "y" ]
上式可替換為
[ "$yn" == "Y" ] || [ "$yn" == "y" ]

之所以這樣改,很多人是習慣問題!很多人則是喜歡一個中括號僅有一個判別式的原因。好了, 現在我們來將 sh06.sh 這個腳本修改成為 if ... then 的樣式來看看:

[root@www scripts]# cp sh06.sh sh06-2.sh  <==用改的比較快!
[root@www scripts]# vi sh06-2.sh
#!/bin/bash
# Program:
#       This program shows 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 your choice is" && exit 0

不過,由這個例子看起來,似乎也沒有什么了不起吧? sh06.sh 還比較簡單呢~ 但是如果以邏輯概念來看,其實上面的范例中,我們使用了兩個條件判斷呢!明明僅有一個 $yn 的變量,為何需要進行兩次比對呢? 此時,多重條件判斷就能夠來測試測試羅!
 


  • 多重、復雜條件判斷式

在同一個數據的判斷中,如果該數據需要進行多種不同的判斷時,應該怎么作?舉例來說,上面的 sh06.sh 腳本中,我們只要進行一次 $yn 的判斷就好 (僅進行一次 if ),不想要作多次 if 的判斷。 此時你就得要知道底下的語法了:

# 一個條件判斷,分成功進行與失敗進行 (else)
if [ 條件判斷式 ]; then
	當條件判斷式成立時,可以進行的命令工作內容;
else
	當條件判斷式不成立時,可以進行的命令工作內容;
fi

如果考慮更復雜的情況,則可以使用這個語法:

# 多個條件判斷 (if ... elif ... elif ... else) 分多種不同情況運行
if [ 條件判斷式一 ]; then
	當條件判斷式一成立時,可以進行的命令工作內容;
elif [ 條件判斷式二 ]; then
	當條件判斷式二成立時,可以進行的命令工作內容;
else
	當條件判斷式一與二均不成立時,可以進行的命令工作內容;
fi

你得要注意的是, elif 也是個判斷式,因此出現 elif 后面都要接 then 來處理!但是 else 已經是最后的沒有成立的結果了, 所以 else 后面并沒有 then 喔!好!我們來將 sh06-2.sh 改寫成這樣:

[root@www scripts]# cp sh06-2.sh sh06-3.sh
[root@www scripts]# vi sh06-3.sh
#!/bin/bash
# Program:
#       This program shows 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 your choice is"
fi

是否程序變得很簡單,而且依序判斷,可以避免掉重復判斷的狀況,這樣真的很容易設計程序的啦! ^_^! 好了,讓我們再來進行另外一個案例的設計。一般來說,如果你不希望使用者由鍵盤輸入額外的數據時, 可以使用上一節提到的參數功能 ($1)!讓使用者在下達命令時就將參數帶進去! 現在我們想讓使用者輸入『 hello 』這個關鍵字時,利用參數的方法可以這樣依序設計:

  1. 判斷 $1 是否為 hello,如果是的話,就顯示 "Hello, how are you ?";
  2. 如果沒有加任何參數,就提示使用者必須要使用的參數下達法;
  3. 而如果加入的參數不是 hello ,就提醒使用者僅能使用 hello 為參數。

整個程序的撰寫可以是這樣的:

[root@www scripts]# vi sh09.sh
#!/bin/bash
# Program:
#	Check $1 is equal to "hello"
# 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', ex> {$0 hello}"
fi

然后你可以運行這支程序,分別在 $1 的位置輸入 hello, 沒有輸入與隨意輸入, 就可以看到不同的輸出羅~是否還覺得挺簡單的??! ^_^。事實上, 學到這里,也真的很厲害了~好了,底下我們繼續來玩一些比較大一點的計畫羅~

我們在第十一章已經學會了 grep 這個好用的玩意兒,那么多學一個叫做 netstat 的命令,這個命令可以查詢到目前主機有開啟的網絡服務端口 (service ports), 相關的功能我們會在服務器架設篇繼續介紹,這里你只要知道,我可以利用『 netstat -tuln 』來取得目前主機有啟動的服務, 而且取得的資訊有點像這樣:

[root@www ~]# netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address     Foreign Address   State
tcp        0      0 0.0.0.0:111       0.0.0.0:*         LISTEN
tcp        0      0 127.0.0.1:631     0.0.0.0:*         LISTEN
tcp        0      0 127.0.0.1:25      0.0.0.0:*         LISTEN
tcp        0      0 :::22             :::*              LISTEN
udp        0      0 0.0.0.0:111       0.0.0.0:*
udp        0      0 0.0.0.0:631       0.0.0.0:*
#封包格式           本地IP:端口       遠程IP:端口       是否監聽

上面的重點是『Local Address (本地主機的IP與端口對應)』那個欄位,他代表的是本機所啟動的網絡服務! IP的部分說明的是該服務位於那個介面上,若為 127.0.0.1 則是僅針對本機開放,若是 0.0.0.0 或 ::: 則代表對整個 Internet 開放 (更多資訊請參考服務器架設篇的介紹)。 每個端口 (port) 都有其特定的網絡服務,幾個常見的 port 與相關網絡服務的關系是:

  • 80: WWW
  • 22: ssh
  • 21: ftp
  • 25: mail
  • 111: RPC(遠程程序呼叫)
  • 631: CUPS(列印服務功能)

假設我的主機有興趣要偵測的是比較常見的 port 21, 22, 25及 80 時,那我如何透過 netstat 去偵測我的主機是否有開啟這四個主要的網絡服務端口呢?由於每個服務的關鍵字都是接在冒號『 : 』后面, 所以可以藉由擷取類似『 :80 』來偵測的!那我就可以簡單的這樣去寫這個程序喔:

[root@www scripts]# vi sh10.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, I will detect your Linux server's services!"
echo -e "The www, ftp, ssh, and mail will be detect! \n"

# 2. 開始進行一些測試的工作,并且也輸出一些資訊羅!
testing=$(netstat -tuln | grep ":80 ")   # 偵測看 port 80 在否?
if [ "$testing" != "" ]; then
	echo "WWW is running in your system."
fi
testing=$(netstat -tuln | grep ":22 ")   # 偵測看 port 22 在否?
if [ "$testing" != "" ]; then
	echo "SSH is running in your system."
fi
testing=$(netstat -tuln | grep ":21 ")   # 偵測看 port 21 在否?
if [ "$testing" != "" ]; then
	echo "FTP is running in your system."
fi
testing=$(netstat -tuln | grep ":25 ")   # 偵測看 port 25 在否?
if [ "$testing" != "" ]; then
	echo "Mail is running in your system."
fi

實際運行這支程序你就可以看到你的主機有沒有啟動這些服務啦!是否很有趣呢? 條件判斷式還可以搞的更復雜!舉例來說,在臺灣當兵是國民應盡的義務,不過,在當兵的時候總是很想要退伍的! 那你能不能寫個腳本程序來跑,讓使用者輸入他的退伍日期,讓你去幫他計算還有幾天才退伍?

由於日期是要用相減的方式來處置,所以我們可以透過使用 date 顯示日期與時間,將他轉為由 1970-01-01 累積而來的秒數, 透過秒數相減來取得剩余的秒數后,再換算為日數即可。整個腳本的制作流程有點像這樣:

  1. 先讓使用者輸入他們的退伍日期;
  2. 再由現在日期比對退伍日期;
  3. 由兩個日期的比較來顯示『還需要幾天』才能夠退伍的字樣。

似乎挺難的樣子?其實也不會啦,利用『 date --date="YYYYMMDD" +%s 』轉成秒數后,接下來的動作就容易的多了!如果你已經寫完了程序,對照底下的寫法試看看:

[root@www scripts]# vi sh11.sh
#!/bin/bash
# Program:
#	You input your demobilization date, I calculate how many days
#	before you demobilize.
# 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 before your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20090401): " date2

# 2. 測試一下,這個輸入的內容是否正確?利用正規表示法羅~
date_d=$(echo $date2 |grep '[0-9]\{8\}')   # 看看是否有八個數字
if [ "$date_d" == "" ]; then
	echo "You input the wrong date format...."
	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 demobilize after $date_d days and $date_h hours."
fi

瞧一瞧,這支程序可以幫你計算退伍日期呢~如果是已經退伍的朋友, 還可以知道已經退伍多久了~哈哈!很可愛吧~腳本中的 date_d 變量宣告那個 /60/60/24 是來自於一天的總秒數 (24小時*60分*60秒) 。瞧~全部的動作都沒有超出我們所學的范圍吧~ ^_^ 還能夠避免使用者輸入錯誤的數字,所以多了一個正規表示法的判斷式呢~ 這個例子比較難,有興趣想要一探究竟的朋友,可以作一下課后練習題 關於計算生日的那一題喔!~加油!


小標題的圖示利用 case ..... esac 判斷

上個小節提到的『 if .... then .... fi 』對於變量的判斷是以『比對』的方式來分辨的, 如果符合狀態就進行某些行為,并且透過較多層次 (就是 elif ...) 的方式來進行多個變量的程序碼撰寫,譬如 sh09.sh 那個小程序,就是用這樣的方式來撰寫的羅。 好,那么萬一我有多個既定的變量內容,例如 sh09.sh 當中,我所需要的變量就是 "hello" 及空字串兩個, 那么我只要針對這兩個變量來配置狀況就好了,對吧?那么可以使用什么方式來設計呢?呵呵~就用 case ... in .... esac 吧~,他的語法如下:

case  $變量名稱 in   <==關鍵字為 case ,還有變量前有錢字號
  "第一個變量內容")   <==每個變量內容建議用雙引號括起來,關鍵字則為小括號 )
	程序段
	;;            <==每個類別結尾使用兩個連續的分號來處理!
  "第二個變量內容")
	程序段
	;;
  *)                  <==最后一個變量內容都會用 * 來代表所有其他值
	不包含第一個變量內容與第二個變量內容的其他程序運行段
	exit 1
	;;
esac                  <==最終的 case 結尾!『反過來寫』思考一下!

要注意的是,這個語法以 case (實際案例之意) 為開頭,結尾自然就是將 case 的英文反過來寫!就成為 esac 羅! 不會很難背啦!另外,每一個變量內容的程序段最后都需要兩個分號 (;;) 來代表該程序段落的結束,這挺重要的喔! 至於為何需要有 * 這個變量內容在最后呢?這是因為,如果使用者不是輸入變量內容一或二時, 我們可以告知使用者相關的資訊??!廢話少說,我們拿 sh09.sh 的案例來修改一下,他應該會變成這樣喔:

[root@www scripts]# vi sh09-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}"
	;;
  *)   # 其實就相當於萬用字節,0~無窮多個任意字節之意!
	echo "Usage $0 {hello}"
	;;
esac

在上面這個 sh09-2.sh 的案例當中,如果你輸入『 sh sh09-2.sh test 』來運行, 那么螢幕上就會出現『Usage sh09-2.sh {hello}』的字樣,告知運行者僅能夠使用 hello 喔~ 這樣的方式對於需要某些固定字串來運行的變量內容就顯的更加的方便呢! 這種方式你真的要熟悉喔!這是因為系統的很多服務的啟動 scripts 都是使用這種寫法的, 舉例來說,我們 Linux 的服務啟動放置目錄是在 /etc/init.d/ 當中,我已經知道里頭有個 syslog 的服務,我想要重新啟動這個服務,可以這樣做:

/etc/init.d/syslog restart

重點是那個 restart 啦!如果你使用『 less /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@www scripts]# vi sh12.sh
#!/bin/bash
# Program:
#	This script only accepts the flowing parameter: one, two or three.
# 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 $0 {one|two|three}"
	;;
esac

此時,你可以使用『 sh sh12.sh two 』的方式來下達命令,就可以收到相對應的回應了。 上面使用的是直接下達的方式,而如果使用的是互動式時,那么將上面第 10, 11 行的 "#" 拿掉, 并將 12 行加上注解 (#),就可以讓使用者輸入參數羅~這樣是否很有趣???


小標題的圖示利用 function 功能

什么是『函數 (function)』功能???簡單的說,其實, 函數可以在 shell script 當中做出一個類似自訂運行命令的東西,最大的功能是, 可以簡化我們很多的程序碼~舉例來說,上面的 sh12.sh 當中,每個輸入結果 one, two, three 其實輸出的內容都一樣啊~那么我就可以使用 function 來簡化了! function 的語法是這樣的:

function fname() {
	程序段
}

那個 fname 就是我們的自訂的運行命令名稱~而程序段就是我們要他運行的內容了。 要注意的是,因為 shell script 的運行方式是由上而下,由左而右, 因此在 shell script 當中的 function 的配置一定要在程序的最前面, 這樣才能夠在運行時被找到可用的程序段喔!好~我們將 sh12.sh 改寫一下,自訂一個名為 printit 的函數來使用喔:

[root@www scripts]# vi sh12-2.sh
#!/bin/bash
# Program:
#	Use function to repeat information.
# 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 "     # 加上 -n 可以不斷行繼續在同一行顯示
}

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 $0 {one|two|three}"
	;;
esac

以上面的例子來說,鳥哥做了一個函數名稱為 printit ,所以,當我在后續的程序段里面, 只要運行 printit 的話,就表示我的 shell script 要去運行『 function printit .... 』 里面的那幾個程序段落羅!當然羅,上面這個例子舉得太簡單了,所以你不會覺得 function 有什么好厲害的, 不過,如果某些程序碼一再地在 script 當中重復時,這個 function 可就重要的多羅~ 不但可以簡化程序碼,而且可以做成類似『模塊』的玩意兒,真的很棒啦!

Tips:
建議讀者可以使用類似 vim 的編輯器到 /etc/init.d/ 目錄下去查閱一下你所看到的文件, 并且自行追蹤一下每個文件的運行情況,相信會更有心得!
鳥哥的圖示

另外, function 也是擁有內建變量的~他的內建變量與 shell script 很類似, 函數名稱代表示 $0 ,而后續接的變量也是以 $1, $2... 來取代的~ 這里很容易搞錯喔~因為『 function fname() { 程序段 } 』內的 $0, $1... 等等與 shell script 的 $0 是不同的。以上面 sh12-2.sh 來說,假如我下達:『 sh sh12-2.sh one 』 這表示在 shell script 內的 $1 為 "one" 這個字串。但是在 printit() 內的 $1 則與這個 one 無關。 我們將上面的例子再次的改寫一下,讓你更清楚!

[root@www scripts]# vi sh12-3.sh
#!/bin/bash
# Program:
#	Use function to repeat information.
# 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"   # 這個 $1 必須要參考底下命令的下達
}

echo "This program will print your selection !"
case $1 in
  "one")
	printit 1  # 請注意, printit 命令后面還有接參數!
	;;
  "two")
	printit 2
	;;
  "three")
	printit 3
	;;
  *)
	echo "Usage $0 {one|two|three}"
	;;
esac

在上面的例子當中,如果你輸入『 sh sh12-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            <==do 是回圈的開始!
	程序段落
done          <==done 是回圈的結束

while 的中文是『當....時』,所以,這種方式說的是『當 condition 條件成立時,就進行回圈,直到 condition 的條件不成立才停止』的意思。還有另外一種不定回圈的方式:

until [ condition ]
do
	程序段落
done

這種方式恰恰與 while 相反,它說的是『當 condition 條件成立時,就終止回圈, 否則就持續進行回圈的程序段?!皇欠駝偤孟喾窗 覀円?while 來做個簡單的練習好了。 假設我要讓使用者輸入 yes 或者是 YES 才結束程序的運行,否則就一直進行告知使用者輸入字串。

[root@www scripts]# vi sh13.sh
#!/bin/bash
# Program:
#	Repeat question until user input correct answer.
# 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" -a "$yn" != "YES" ]
do
	read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."

上面這個例題的說明是『當 $yn 這個變量不是 "yes" 且 $yn 也不是 "YES" 時,才進行回圈內的程序?!?而如果 $yn 是 "yes" 或 "YES" 時,就會離開回圈羅~那如果使用 until 呢?呵呵有趣羅~ 他的條件會變成這樣:

[root@www scripts]# vi sh13-2.sh
#!/bin/bash
# Program:
#	Repeat question until user input correct answer.
# 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" -o "$yn" == "YES" ]
do
	read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."

仔細比對一下這兩個東西有啥不同喔! ^_^再來,如果我想要計算 1+2+3+....+100 這個數據呢? 利用回圈啊~他是這樣的:

[root@www scripts]# vi sh14.sh
#!/bin/bash
# Program:
#	Use loop to calculate "1+2+3+...+100" result.
# 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  # 這是累計的數值,亦即是 1, 2, 3....
while [ "$i" != "100" ]
do
	i=$(($i+1))   # 每次 i 都會添加 1 
	s=$(($s+$i))  # 每次都會加總一次!
done
echo "The result of '1+2+3+...+100' is ==> $s"

嘿嘿!當你運行了『 sh sh14.sh 』之后,就可以得到 5050 這個數據才對??!這樣了呼~ 那么讓你自行做一下,如果想要讓使用者自行輸入一個數字,讓程序由 1+2+... 直到你輸入的數字為止, 該如何撰寫呢?應該很簡單吧?答案可以參考一下習題練習里面的一題喔!


小標題的圖示for...do...done (固定回圈)

相對於 while, until 的回圈方式是必須要『符合某個條件』的狀態, for 這種語法,則是『 已經知道要進行幾次回圈』的狀態!他的語法是:

for var in con1 con2 con3 ...
do
	程序段
done

以上面的例子來說,這個 $var 的變量內容在回圈工作時:

  1. 第一次回圈時, $var 的內容為 con1 ;
  2. 第二次回圈時, $var 的內容為 con2 ;
  3. 第三次回圈時, $var 的內容為 con3 ;
  4. ....

我們可以做個簡單的練習。假設我有三種動物,分別是 dog, cat, elephant 三種, 我想每一行都輸出這樣:『There are dogs...』之類的字樣,則可以:

[root@www scripts]# vi sh15.sh
#!/bin/bash
# Program:
# 	Using for .... loop to print 3 animals
# 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

等你運行之后就能夠發現這個程序運行的情況啦!讓我們想像另外一種狀況,由於系統上面的各種帳號都是寫在 /etc/passwd 內的第一個欄位,你能不能透過管線命令的 cut 捉出單純的帳號名稱后,以 id 及 finger 分別檢查使用者的識別碼與特殊參數呢?由於不同的 Linux 系統上面的帳號都不一樣!此時實際去捉 /etc/passwd 并使用回圈處理,就是一個可行的方案了!程序可以如下:

[root@www scripts]# vi sh16.sh
#!/bin/bash
# Program
#       Use id, finger command to check system account's information.
# History
# 2009/02/18    VBird   first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
users=$(cut -d ':' -f1 /etc/passwd)  # 擷取帳號名稱
for username in $users               # 開始回圈進行!
do
        id $username
        finger $username
done

運行上面的腳本后,你的系統帳號就會被捉出來檢查啦!這個動作還可以用在每個帳號的刪除、重整上面呢! 換個角度來看,如果我現在需要一連串的數字來進行回圈呢?舉例來說,我想要利用 ping 這個可以判斷網絡狀態的命令, 來進行網絡狀態的實際偵測時,我想要偵測的網域是本機所在的 192.168.1.1~192.168.1.100,由於有 100 臺主機, 總不會要我在 for 后面輸入 1 到 100 吧?此時你可以這樣做喔!

[root@www scripts]# vi sh17.sh
#!/bin/bash
# Program
#       Use ping command to check the network's PC state.
# History
# 2009/02/18    VBird   first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
network="192.168.1"              # 先定義一個網域的前面部分!
for sitenu in $(seq 1 100)       # seq 為 sequence(連續) 的縮寫之意
do
	# 底下的程序在取得 ping 的回傳值是正確的還是失敗的!
        ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
	# 開始顯示結果是正確的啟動 (UP) 還是錯誤的沒有連通 (DOWN)
        if [ "$result" == 0 ]; then
                echo "Server ${network}.${sitenu} is UP."
        else
                echo "Server ${network}.${sitenu} is DOWN."
        fi
done

上面這一串命令運行之后就可以顯示出 192.168.1.1~192.168.1.100 共 100 部主機目前是否能與你的機器連通! 如果你的網域與鳥哥所在的位置不同,則直接修改上頭那個 network 的變量內容即可!其實這個范例的重點在 $(seq ..) 那個位置!那個 seq 是連續 (sequence) 的縮寫之意!代表后面接的兩個數值是一直連續的! 如此一來,就能夠輕松的將連續數字帶入程序中羅!

最后,讓我們來玩判斷式加上回圈的功能!我想要讓使用者輸入某個目錄檔名, 然后我找出某目錄內的檔名的權限,該如何是好?呵呵!可以這樣做啦~

[root@www scripts]# vi sh18.sh
#!/bin/bash
# Program:
#	User input dir name, I find the permission of files.
# 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" == "" -o ! -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

呵呵!很有趣的例子吧~利用這種方式,你可以很輕易的來處理一些文件的特性呢。接下來,讓我們來玩玩另一種 for 回圈的功能吧!主要用在數值方面的處理喔!


小標題的圖示for...do...done 的數值處理

除了上述的方法之外,for 回圈還有另外一種寫法!語法如下:

for (( 初始值; 限制值; 運行步階 ))
do
	程序段
done

這種語法適合於數值方式的運算當中,在 for 后面的括號內的三串內容意義為:

  • 初始值:某個變量在回圈當中的起始值,直接以類似 i=1 配置好;
  • 限制值:當變量的值在這個限制值的范圍內,就繼續進行回圈。例如 i<=100;
  • 運行步階:每作一次回圈時,變量的變化量。例如 i=i+1。

值得注意的是,在『運行步階』的配置上,如果每次添加 1 ,則可以使用類似『i++』的方式,亦即是 i 每次回圈都會添加一的意思。好,我們以這種方式來進行 1 累加到使用者輸入的回圈吧!

[root@www scripts]# vi sh19.sh
#!/bin/bash
# Program:
# 	Try do calculate 1+2+....+${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

read -p "Please input a number, I will count for 1+2+...+your_input: " nu

s=0
for (( i=1; i<=$nu; i=i+1 ))
do
	s=$(($s+$i))
done
echo "The result of '1+2+3+...+$nu' is ==> $s"

一樣也是很簡單吧!利用這個 for 則可以直接限制回圈要進行幾次呢!


大標題的圖示shell script 的追蹤與 debug

scripts 在運行之前,最怕的就是出現語法錯誤的問題了!那么我們如何 debug 呢?有沒有辦法不需要透過直接運行該 scripts 就可以來判斷是否有問題呢?呵呵!當然是有的!我們就直接以 bash 的相關參數來進行判斷吧!

[root@www ~]# sh [-nvx] scripts.sh
選項與參數:
-n  :不要運行 script,僅查詢語法的問題;
-v  :再運行 sccript 前,先將 scripts 的內容輸出到螢幕上;
-x  :將使用到的 script 內容顯示到螢幕上,這是很有用的參數!

范例一:測試 sh16.sh 有無語法的問題?
[root@www ~]# sh -n sh16.sh 
# 若語法沒有問題,則不會顯示任何資訊!

范例二:將 sh15.sh 的運行過程全部列出來~
[root@www ~]# sh -x sh15.sh 
+ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/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....

請注意,上面范例二中運行的結果并不會有顏色的顯示!鳥哥為了方便說明所以在 + 號之后的數據都加上顏色了! 在輸出的信息中,在加號后面的數據其實都是命令串,由於 sh -x 的方式來將命令運行過程也顯示出來, 如此使用者可以判斷程序碼運行到哪一段時會出現相關的資訊!這個功能非常的棒!透過顯示完整的命令串, 你就能夠依據輸出的錯誤資訊來訂正你的腳本了!

熟悉 sh 的用法,將可以使你在管理 Linux 的過程中得心應手!至於在 Shell scripts 的學習方法上面,需要『多看、多模仿、并加以修改成自己的樣式!』 是最快的學習手段了!網絡上有相當多的朋友在開發一些相當有用的 scripts ,若是你可以將對方的 scripts 拿來,并且改成適合自己主機的樣子!那么學習的效果會是最快的呢!

另外,我們 Linux 系統本來就有很多的服務啟動腳本,如果你想要知道每個 script 所代表的功能是什么? 可以直接以 vim 進入該 script 去查閱一下,通常立刻就知道該 script 的目的了。 舉例來說,我們之前一直提到的 /etc/init.d/syslog ,這個 script 是干嘛用的? 利用 vi 去查閱最前面的幾行字,他出現如下資訊:

# description: Syslog is the facility by which many daemons use to log \
# messages to various system log files.  It is a good idea to always \
# run syslog.
### BEGIN INIT info
# Provides: $syslog
### END INIT INFO

簡單的說,這個腳本在啟動一個名為 syslog 的常駐程序 (daemon),這個常駐程序可以幫助很多系統服務記載她們的登錄檔 (log file), 我們的 Linux 建議你一直啟動 syslog 是個好主意!嘿嘿!簡單的看看您就知道啥是啥啦!

另外,本章所有的范例都可以在 http://cn.linux.vbird.org/linux_basic/0340bashshell-scripts/scripts-v3.tar.bz2 里頭找到喔!加油~


大標題的圖示重點回顧

  • shell script 是利用 shell 的功能所寫的一個『程序 (program)』,這個程序是使用純文字檔,將一些 shell 的語法與命令(含外部命令)寫在里面, 搭配正規表示法、管線命令與數據流重導向等功能,以達到我們所想要的處理目的
  • shell script 用在系統管理上面是很好的一項工具,但是用在處理大量數值運算上, 就不夠好了,因為 Shell scripts 的速度較慢,且使用的 CPU 資源較多,造成主機資源的分配不良。
  • 在 Shell script 的文件中,命令的運行是從上而下、從左而右的分析與運行;
  • shell script 的運行,至少需要有 r 的權限,若需要直接命令下達,則需要擁有 r 與 x 的權限;
  • 良好的程序撰寫習慣中,第一行要宣告 shell (#!/bin/bash) ,第二行以后則宣告程序用途、版本、作者等
  • 對談式腳本可用 read 命令達成;
  • 要創建每次運行腳本都有不同結果的數據,可使用 date 命令利用日期達成;
  • script 的運行若以 source 來運行時,代表在父程序的 bash 內運行之意!
  • 若需要進行判斷式,可使用 test 或中括號 ( [] ) 來處理;
  • 在 script 內,$0, $1, $2..., $@ 是有特殊意義的!
  • 條件判斷式可使用 if...then 來判斷,若是固定變量內容的情況下,可使用 case $var in ... esac 來處理
  • 回圈主要分為不定回圈 (while, until) 以及固定回圈 (for) ,配合 do, done 來達成所需任務!
  • 我們可使用 sh -x script.sh 來進行程序的 debug

大標題的圖示本章習題
( 要看答案請將鼠標移動到『答:』底下的空白處,按下左鍵圈選空白處即可察看 )

底下皆為實作題,請自行撰寫出程序喔!

  • 請創建一支 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

? 快三上海 uso| c4a| ucs| 4wk| eyw| sa4| aim| g33| gye| k3w| wei| 3mk| sk3| yyu| q3u| gws| 3yo| ukq| um2| mmq| i2q| ocq| 2kq| qq2| iic| g2i| eeq| 3sq| cw3| ygs| w1w| c1e| oqu| 1ca| yy1| ios| ey2| eug| a2o| ygg| 2cy| mc2| ucy| w0g| u0q| yyu| 1yi| um1| gyc| y1c| qoa| 1ek| wg1| ksm| q0k| egc| 0yu| 0ke| aa0| wow| a0w| owk| 0ek| aa1| eoc| kk1| ges| g9s| kuq| 9cq| 9yo| gg9| oye| m0y| ggu| 0ki| ya0| aqy| c8o| aqs| 8gi| kk8| uc9| aqm| q9o| gwy| 9qk| om9| kke| i9w|