從這一章開始,我們將建設(shè)一個項目。這個項目的目的是為了了解怎樣使用各種各樣的 shell 功能來 創(chuàng)建程序,更重要的是,創(chuàng)建好程序。
我們將要編寫的程序是一個報告生成器。它會顯示系統(tǒng)的各種統(tǒng)計數(shù)據(jù)和它的狀態(tài),并將產(chǎn)生 HTML 格式的報告, 所以我們能通過網(wǎng)絡(luò)瀏覽器,比如說 Firefox 或者 Konqueror,來查看這個報告。
通常,創(chuàng)建程序要經(jīng)過一系列階段,每個階段會添加新的特性和功能。我們程序的第一個階段將會 產(chǎn)生一個非常小的 HTML 網(wǎng)頁,其不包含系統(tǒng)信息。隨后我們會添加這些信息。
首先我們需要知道的事是一個規(guī)則的 HTML 文檔的格式。它看起來像這樣:
<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
Page body.
</BODY>
</HTML>
如果我們將這些內(nèi)容輸入到文本編輯器中,并把文件保存為 foo.html,然后我們就能在 Firefox 中 使用下面的 URL 來查看文件內(nèi)容:
file:///home/username/foo.html
程序的第一個階段將這個 HTML 文件輸出到標準輸出。我們可以編寫一個程序,相當容易地完成這個任務(wù)。 啟動我們的文本編輯器,然后創(chuàng)建一個名為 ~/bin/sys_info_page 的新文件:
[me@linuxbox ~]$ vim ~/bin/sys_info_page
隨后輸入下面的程序:
#!/bin/bash
# Program to output a system information page
echo "<HTML>"
echo " <HEAD>"
echo " <TITLE>Page Title</TITLE>"
echo " </HEAD>"
echo " <BODY>"
echo " Page body."
echo " </BODY>"
echo "</HTML>"
我們第一次嘗試解決這個問題,程序包含了一個 shebang,一條注釋(總是一個好主意)和一系列的 echo 命令,每個命令負責輸出一行文本。保存文件之后,我們將讓它成為可執(zhí)行文件,再嘗試運行它:
[me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page
[me@linuxbox ~]$ sys_info_page
當程序運行的時候,我們應(yīng)該看到 HTML 文本在屏幕上顯示出來,因為腳本中的 echo 命令會輸出 發(fā)送到標準輸出。我們再次運行這個程序,把程序的輸出重定向到文件 sys_info_page.html 中, 從而我們可以通過網(wǎng)絡(luò)瀏覽器來查看輸出結(jié)果:
[me@linuxbox ~]$ sys_info_page > sys_info_page.html
[me@linuxbox ~]$ firefox sys_info_page.html
到目前為止,一切順利。
在編寫程序的時候,盡量做到簡單明了,這總是一個好主意。當一個程序易于閱讀和理解的時候, 維護它也就更容易,更不用說,通過減少鍵入量,可以使程序更容易書寫了。我們當前的程序版本 工作正常,但是它可以更簡單些。實際上,我們可以把所有的 echo 命令結(jié)合成一個 echo 命令,當然 這樣能更容易地添加更多的文本行到程序的輸出中。那么,把我們的程序修改為:
#!/bin/bash
# Program to output a system information page
echo "<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
Page body.
</BODY>
</HTML>"
一個帶引號的字符串可能包含換行符,因此可以包含多個文本行。Shell 會持續(xù)讀取文本直到它遇到 右引號。它在命令行中也是這樣工作的:
[me@linuxbox ~]$ echo "<HTML>
> <HEAD>
<TITLE>Page Title</TITLE>
> </HEAD>
> <BODY>
> Page body.
> </BODY>
></HTML>"
開頭的 “>” 字符是包含在 PS2shell 變量中的 shell 提示符。每當我們在 shell 中鍵入多行語句的時候, 這個提示符就會出現(xiàn)。現(xiàn)在這個功能有點兒晦澀,但隨后,當我們介紹多行編程語句時,它會派上大用場。
現(xiàn)在我們的程序能生成一個最小的文檔,讓我們給報告添加些數(shù)據(jù)吧。為此,我們將做 以下修改:
#!/bin/bash
# Program to output a system information page
echo "<HTML>
<HEAD>
<TITLE>System Information Report</TITLE>
</HEAD>
<BODY>
<H1>System Information Report</H1>
</BODY>
</HTML>"
我們增加了一個網(wǎng)頁標題,并且在報告正文部分加了一個標題。
然而,我們的腳本存在一個問題。請注意字符串 “System Information Report” 是怎樣被重復使用的?對于這個微小的腳本而言,它不是一個問題,但是讓我們設(shè)想一下, 我們的腳本非常冗長,并且我們有許多這個字符串的實例。如果我們想要更換一個標題,我們必須 對腳本中的許多地方做修改,這會是很大的工作量。如果我們能整理一下腳本,讓這個字符串只 出現(xiàn)一次而不是多次,會怎樣呢?這樣會使今后的腳本維護工作更加輕松。我們可以這樣做:
#!/bin/bash
# Program to output a system information page
title="System Information Report"
echo "<HTML>
<HEAD>
<TITLE>$title</TITLE>
</HEAD>
<BODY>
<H1>$title</H1>
</BODY>
</HTML>"
通過創(chuàng)建一個名為 title 的變量,并把 “System Information Report” 字符串賦值給它,我們就可以利用參數(shù)展開功能,把這個字符串放到文件中的多個位置。
那么,我們怎樣來創(chuàng)建一個變量呢?很簡單,我們只管使用它。當 shell 碰到一個變量的時候,它會 自動地創(chuàng)建它。這不同于許多編程語言,它們中的變量在使用之前,必須顯式的聲明或是定義。關(guān)于 這個問題,shell 要求非常寬松,這可能會導致一些問題。例如,考慮一下在命令行中發(fā)生的這種情形:
[me@linuxbox ~]$ foo="yes"
[me@linuxbox ~]$ echo $foo
yes
[me@linuxbox ~]$ echo $fool
[me@linuxbox ~]$
首先我們把 “yes” 賦給變量 foo,然后用 echo 命令來顯示變量值。接下來,我們顯示拼寫錯誤的變量名 “fool” 的變量值,然后得到一個空值。這是因為 shell 很高興地創(chuàng)建了變量 fool,當 shell 遇到 fool 的時候, 并且賦給 fool 一個空的默認值。因此,我們必須小心謹慎地拼寫!同樣理解實例中究竟發(fā)生了什么事情也 很重要。從我們以前學習 shell 執(zhí)行展開操作,我們知道這個命令:
[me@linuxbox ~]$ echo $foo
經(jīng)歷了參數(shù)展開操作,然后得到:
[me@linuxbox ~]$ echo yes
然而這個命令:
[me@linuxbox ~]$ echo $fool
展開為:
[me@linuxbox ~]$ echo
這個空變量展開值為空!對于需要參數(shù)的命令來說,這會引起混亂。下面是一個例子:
[me@linuxbox ~]$ foo=foo.txt
[me@linuxbox ~]$ foo1=foo1.txt
[me@linuxbox ~]$ cp $foo $fool
cp: missing destination file operand after `foo.txt'
Try `cp --help' for more information.
我們給兩個變量賦值,foo 和 foo1。然后我們執(zhí)行 cp 操作,但是拼寫錯了第二個參數(shù)的名字。 參數(shù)展開之后,這個 cp 命令只接受到一個參數(shù),雖然它需要兩個。
有一些關(guān)于變量名的規(guī)則:
變量名可由字母數(shù)字字符(字母和數(shù)字)和下劃線字符組成。
變量名的第一個字符必須是一個字母或一個下劃線。
單詞 “variable” 意味著可變的值,并且在許多應(yīng)用程序當中,都是以這種方式來使用變量的。然而, 我們應(yīng)用程序中的變量,title,被用作一個常量。常量有一個名字且包含一個值,在這方面就 像是變量。不同之處是常量的值是不能改變的。在執(zhí)行幾何運算的應(yīng)用程序中,我們可以把 PI 定義為 一個常量,并把 3.1415 賦值給它,用它來代替數(shù)字字面值。shell 不能辨別變量和常量;它們大多數(shù)情況下 是為了方便程序員。一個常用慣例是指定大寫字母來表示常量,小寫字母表示真正的變量。我們 將修改我們的腳本來遵從這個慣例:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
echo "<HTML>
<HEAD>
<TITLE>$title</TITLE>
</HEAD>
<BODY>
<H1>$title</H1>
</BODY>
</HTML>"
我們亦借此機會,通過在標題中添加 shell 變量名 HOSTNAME,讓標題變得活潑有趣些。 這個變量名是這臺機器的網(wǎng)絡(luò)名稱。
注意:實際上,shell 確實提供了一種方法,通過使用帶有-r(只讀)選項的內(nèi)部命令 declare, 來強制常量的不變性。如果我們給 TITLE 這樣賦值:
那么 shell 會阻止之后給 TITLE 的任意賦值。這個功能極少被使用,但為了很早之前的腳本, 它仍然存在。
這里是我們真正開始使用參數(shù)擴展知識的地方。正如我們所知道的,這樣給變量賦值:
variable=value
這里的variable是變量的名字,value是一個字符串。不同于一些其它的編程語言,shell 不會 在乎變量值的類型;它把它們都看作是字符串。通過使用帶有-i 選項的 declare 命令,你可以強制 shell 把 賦值限制為整型,但是,正如像設(shè)置變量為只讀一樣,極少這樣做。
注意在賦值過程中,變量名,等號和變量值之間必須沒有空格。那么,這些值由什么組成呢? 可以展開成字符串的任意值:
a=z # Assign the string "z" to variable a.
b="a string" # Embedded spaces must be within quotes.
c="a string and $b" # Other expansions such as variables can be
# expanded into the assignment.
d=$(ls -l foo.txt) # Results of a command.
e=$((5 * 7)) # Arithmetic expansion.
f="\t\ta string\n" # Escape sequences such as tabs and newlines.
可以在同一行中對多個變量賦值:
a=5 b="a string"
在參數(shù)展開過程中,變量名可能被花括號 “{}” 包圍著。由于變量名周圍的上下文,其變得不明確的情況下, 這會很有幫助。這里,我們試圖把一個文件名從 myfile 改為 myfile1,使用一個變量:
[me@linuxbox ~]$ filename="myfile"
[me@linuxbox ~]$ touch $filename
[me@linuxbox ~]$ mv $filename $filename1
mv: missing destination file operand after `myfile'
Try `mv --help' for more information.
這種嘗試失敗了,因為 shell 把 mv 命令的第二個參數(shù)解釋為一個新的(并且空的)變量。通過這種方法 可以解決這個問題:
[me@linuxbox ~]$ mv $filename ${filename}1
通過添加花括號,shell 不再把末尾的1解釋為變量名的一部分。
我們將利用這個機會來添加一些數(shù)據(jù)到我們的報告中,即創(chuàng)建包括的日期和時間,以及創(chuà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"
echo "<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
<BODY>
<H1>$TITLE</H1>
<P>$TIME_STAMP</P>
</BODY>
</HTML>"
我們已經(jīng)知道了兩種不同的文本輸出方法,兩種方法都使用了 echo 命令。還有第三種方法,叫做 here document 或者 here script。一個 here document 是另外一種 I/O 重定向形式,我們 在腳本文件中嵌入正文文本,然后把它發(fā)送給一個命令的標準輸入。它這樣工作:
command << token
text
token
這里的 command 是一個可以接受標準輸入的命令名,token 是一個用來指示嵌入文本結(jié)束的字符串。 我們將修改我們的腳本,來使用一個 here document:
#!/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>
</BODY>
</HTML>
_EOF_
取代 echo 命令,現(xiàn)在我們的腳本使用 cat 命令和一個 here document。這個字符串EOF\(意思是“文件結(jié)尾”, 一個常見用法)被選作為 token,并標志著嵌入文本的結(jié)尾。注意這個 token 必須在一行中單獨出現(xiàn),并且文本行中 沒有末尾的空格。
那么使用一個 here document 的優(yōu)點是什么呢?它很大程度上和 echo 一樣,除了默認情況下,here documents 中的單引號和雙引號會失去它們在 shell 中的特殊含義。這里有一個命令中的例子:
[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
some text
"some text"
'some text'
$foo
正如我們所見到的,shell 根本沒有注意到引號。它把它們看作是普通的字符。這就允許我們 在一個 here document 中可以隨意的嵌入引號。對于我們的報告程序來說,這將是非常方便的。
Here documents 可以和任意能接受標準輸入的命令一塊使用。在這個例子中,我們使用了 一個 here document 將一系列的命令傳遞到這個 ftp 程序中,為的是從一個遠端 FTP 服務(wù)器中得到一個文件:
#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n << _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
ls -l $REMOTE_FILE
如果我們把重定向操作符從 “<\<” 改為 “<\<-”,shell 會忽略在此 here document 中開頭的 tab 字符。 這就能縮進一個 here document,從而提高腳本的可讀性:
#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n <<- _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
ls -l $REMOTE_FILE
在這一章中,我們啟動了一個項目,其帶領(lǐng)我們領(lǐng)略了創(chuàng)建一個成功腳本的整個過程。 同時我們介紹了變量和常量的概念,以及怎樣使用它們。它們是我們將找到的眾多參數(shù)展開應(yīng)用程序中的第一批實例。 我們也知道了怎樣從我們的腳本文件中產(chǎn)生輸出,及其各種各樣嵌入文本塊的方法。
關(guān)于 HTML 的更多信息,查看下面的文章和教材:
http://en.wikipedia.org/wiki/Html