隨著程序變得更加龐大和復雜,設計,編碼和維護它們也變得更加困難。對于任意一個大項目而言, 把繁重,復雜的任務分割為細小且簡單的任務,往往是一個好主意。想象一下,我們試圖描述 一個平凡無奇的工作,一位火星人要去市場買食物。我們可能通過下面一系列步驟來形容整個過程:
上車
開車到市場
停車
買食物
回到車中
開車回家
然而,火星人可能需要更詳細的信息。我們可以進一步細化子任務“停車”為這些步驟:
找到停車位
開車到停車位
關閉引擎
拉緊手剎
下車
這個“關閉引擎”子任務可以進一步細化為這些步驟,包括“關閉點火裝置”,“移開點火匙”等等,直到 已經完整定義了要去市場買食物整個過程的每一個步驟。
這種先確定上層步驟,然后再逐步細化這些步驟的過程被稱為自頂向下設計。這種技巧允許我們 把龐大而復雜的任務分割為許多小而簡單的任務。自頂向下設計是一種常見的程序設計方法, 尤其適合 shell 編程。
在這一章中,我們將使用自頂向下的設計方法來進一步開發(fā)我們的報告產生器腳本。
目前我們的腳本執(zhí)行以下步驟來產生這個 HTML 文檔:
打開網頁
打開網頁標頭
設置網頁標題
關閉網頁標頭
打開網頁主體部分
輸出網頁標頭
輸出時間戳
關閉網頁主體
為了下一階段的開發(fā),我們將在步驟7和8之間添加一些額外的任務。這些將包括:
系統(tǒng)正常運行時間和負載。這是自上次關機或重啟之后系統(tǒng)的運行時間,以及在幾個時間間隔內當前運行在處理 中的平均任務量。
磁盤空間。系統(tǒng)中存儲設備的總使用量。
如果對于每一個任務,我們都有相應的命令,那么通過命令替換,我們就能很容易地把它們添加到我們的腳本中:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
<BODY>
<H1>$TITLE</H1>
<P>$TIME_STAMP</P>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</BODY>
</HTML>
_EOF_
我們能夠用兩種方法來創(chuàng)建這些額外的命令。我們可以分別編寫三個腳本,并把它們放置到 環(huán)境變量 PATH 所列出的目錄下,或者我們也可以把這些腳本作為 shell 函數(shù)嵌入到我們的程序中。 我們之前已經提到過,shell 函數(shù)是位于其它腳本中的“微腳本”,作為自主程序。Shell 函數(shù)有兩種語法形式:
function name {
commands
return
}
and
name () {
commands
return
}
這里的 name 是函數(shù)名,commands 是一系列包含在函數(shù)中的命令。
兩種形式是等價的,可以交替使用。下面我們將查看一個說明 shell 函數(shù)使用方法的腳本:
1 #!/bin/bash
2
3 # Shell function demo
4
5 function funct {
6 echo "Step 2"
7 return
8 }
9
10 # Main program starts here
11
12 echo "Step 1"
13 funct
14 echo "Step 3"
隨著 shell 讀取這個腳本,它會跳過第1行到第11行的代碼,因為這些文本行由注釋和函數(shù)定義組成。 從第12行代碼開始執(zhí)行,有一個 echo 命令。第13行會調用 shell 函數(shù) funct,然后 shell 會執(zhí)行這個函數(shù), 就如執(zhí)行其它命令一樣。這樣程序控制權會轉移到第六行,執(zhí)行第二個 echo 命令。然后再執(zhí)行第7行。 這個 return 命令終止這個函數(shù),并把控制權交給函數(shù)調用之后的代碼(第14行),從而執(zhí)行最后一個 echo 命令。注意為了使函數(shù)調用被識別出是 shell 函數(shù),而不是被解釋為外部程序的名字,所以在腳本中 shell 函數(shù)定義必須出現(xiàn)在函數(shù)調用之前。
我們將給腳本添加最小的 shell 函數(shù)定義:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
report_uptime () {
return
}
report_disk_space () {
return
}
report_home_space () {
return
}
cat << _EOF_
<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
<BODY>
<H1>$TITLE</H1>
<P>$TIME_STAMP</P>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</BODY>
</HTML>
_EOF_
Shell 函數(shù)的命名規(guī)則和變量一樣。一個函數(shù)必須至少包含一條命令。這條 return 命令(是可選的)滿足要求。
目前我們所寫的腳本中,所有的變量(包括常量)都是全局變量。全局變量在整個程序中保持存在。 對于許多事情來說,這很好,但是有時候它會使 shell 函數(shù)的使用變得復雜。在 shell 函數(shù)中,經常期望 會有局部變量。局部變量只能在定義它們的 shell 函數(shù)中使用,并且一旦 shell 函數(shù)執(zhí)行完畢,它們就不存在了。
擁有局部變量允許程序員使用的局部變量名,可以與已存在的變量名相同,這些變量可以是全局變量, 或者是其它 shell 函數(shù)中的局部變量,卻不必擔心潛在的名字沖突。
這里有一個實例腳本,其說明了怎樣來定義和使用局部變量:
#!/bin/bash
# local-vars: script to demonstrate local variables
foo=0 # global variable foo
funct_1 () {
local foo # variable foo local to funct_1
foo=1
echo "funct_1: foo = $foo"
}
funct_2 () {
local foo # variable foo local to funct_2
foo=2
echo "funct_2: foo = $foo"
}
echo "global: foo = $foo"
funct_1
echo "global: foo = $foo"
funct_2
echo "global: foo = $foo"
正如我們所看到的,通過在變量名之前加上單詞 local,來定義局部變量。這就創(chuàng)建了一個只對其所在的 shell 函數(shù)起作用的變量。在這個 shell 函數(shù)之外,這個變量不再存在。當我們運行這個腳本的時候, 我們會看到這樣的結果:
[me@linuxbox ~]$ local-vars
global: foo = 0
funct_1: foo = 1
global: foo = 0
funct_2: foo = 2
global: foo = 0
我們看到對兩個 shell 函數(shù)中的局部變量 foo 賦值,不會影響到在函數(shù)之外定義的變量 foo 的值。
這個功能就允許 shell 函數(shù)能保持各自以及與它們所在腳本之間的獨立性。這個非常有價值,因為它幫忙 阻止了程序各部分之間的相互干涉。這樣 shell 函數(shù)也可以移植。也就是說,按照需求, shell 函數(shù)可以在腳本之間進行剪切和粘貼。
當開發(fā)程序的時候,保持程序的可執(zhí)行狀態(tài)非常有用。這樣做,并且經常測試,我們就可以在程序 開發(fā)過程的早期檢測到錯誤。這將使調試問題容易多了。例如,如果我們運行這個程序,做一個小的修改, 然后再次執(zhí)行這個程序,最后發(fā)現(xiàn)一個問題,非常有可能這個最新的修改就是問題的來源。通過添加空函數(shù), 程序員稱之為占位符,我們可以在早期階段證明程序的邏輯流程。當構建一個占位符的時候, 能夠包含一些為程序員提供反饋信息的代碼是一個不錯的主意,這些信息展示了正在執(zhí)行的邏輯流程。 現(xiàn)在看一下我們腳本的輸出結果:
[me@linuxbox ~]$ sys_info_page
<HTML>
<HEAD>
<TITLE>System Information Report For twin2</TITLE>
</HEAD>
<BODY>
<H1>System Information Report For linuxbox</H1>
<P>Generated 03/19/2009 04:02:10 PM EDT, by me</P>
</BODY>
</HTML>
我們看到時間戳之后的輸出結果中有一些空行,但是我們不能確定這些空行產生的原因。如果我們 修改這些函數(shù),讓它們包含一些反饋信息:
report_uptime () {
echo "Function report_uptime executed."
return
}
report_disk_space () {
echo "Function report_disk_space executed."
return
}
report_home_space () {
echo "Function report_home_space executed."
return
}
然后再次運行這個腳本:
[me@linuxbox ~]$ sys_info_page
<HTML>
<HEAD>
<TITLE>System Information Report For linuxbox</TITLE>
</HEAD>
<BODY>
<H1>System Information Report For linuxbox</H1>
<P>Generated 03/20/2009 05:17:26 AM EDT, by me</P>
Function report_uptime executed.
Function report_disk_space executed.
Function report_home_space executed.
</BODY>
</HTML>
現(xiàn)在我們看到,事實上,執(zhí)行了三個函數(shù)。
我們的函數(shù)框架已經各就各位并且能工作,是時候更新一些函數(shù)代碼了。首先,是 report_uptime 函數(shù):
report_uptime () {
cat <<- _EOF_
<H2>System Uptime</H2>
<PRE>$(uptime)</PRE>
_EOF_
return
}
這些代碼相當直截了當。我們使用一個 here 文檔來輸出標題和 uptime 命令的輸出結果,命令結果被 <PRE> 標簽包圍,
為的是保持命令的輸出格式。這個 report_disk_space 函數(shù)類似:
report_disk_space () {
cat <<- _EOF_
<H2>Disk Space Utilization</H2>
<PRE>$(df -h)</PRE>
_EOF_
return
}
這個函數(shù)使用 df -h 命令來確定磁盤空間的數(shù)量。最后,我們將建造 report_home_space 函數(shù):
report_home_space () {
cat <<- _EOF_
<H2>Home Space Utilization</H2>
<PRE>$(du -sh /home/*)</PRE>
_EOF_
return
}
我們使用帶有 -sh 選項的 du 命令來完成這個任務。然而,這并不是此問題的完整解決方案。雖然它會 在一些系統(tǒng)(例如 Ubuntu)中起作用,但是在其它系統(tǒng)中它不工作。這是因為許多系統(tǒng)會設置家目錄的 權限,以此阻止其它用戶讀取它們,這是一個合理的安全措施。在這些系統(tǒng)中,這個 report_home_space 函數(shù), 只有用超級用戶權限執(zhí)行我們的腳本時,才會工作。一個更好的解決方案是讓腳本能根據(jù)用戶的使用權限來 調整自己的行為。我們將在下一章中討論這個問題。
你的 .bashrc 文件中的 shell 函數(shù)
Shell 函數(shù)是更為完美的別名替代物,實際上是創(chuàng)建較小的個人所用命令的首選方法。別名 非常局限于命令的種類和它們支持的 shell 功能,然而 shell 函數(shù)允許任何可以編寫腳本的東西。 例如,如果我們喜歡 為我們的腳本開發(fā)的這個 report_disk_space shell 函數(shù),我們可以為我們的 .bashrc 文件 創(chuàng)建一個相似的名為 ds 的函數(shù):
ds () { echo “Disk Space Utilization For $HOSTNAME” df -h }
這一章中,我們介紹了一種常見的程序設計方法,叫做自頂向下設計,并且我們知道了怎樣 使用 shell 函數(shù)按照要求來完成逐步細化的任務。我們也知道了怎樣使用局部變量使 shell 函數(shù) 獨立于其它函數(shù),以及其所在程序的其它部分。這就有可能使 shell 函數(shù)以可移植的方式編寫, 并且能夠重復使用,通過把它們放置到多個程序中;節(jié)省了大量的時間。
Wikipedia 上面有許多關于軟件設計原理的文章。這里是一些好文章: