鍍金池/ 教程/ Linux/ 啟動一個項目
網(wǎng)絡(luò)系統(tǒng)
打印
重定向
使用命令
位置參數(shù)
權(quán)限
文本處理
疑難排解
layout: book-zh title: 自定制 shell 提示符
查找文件
layout: book-zh title: vi 簡介
shell 環(huán)境
什么是 shell
編譯程序
鍵盤高級操作技巧
流程控制:case 分支
流程控制:if 分支結(jié)構(gòu)
layout: book-zh title: 軟件包管理
進程
存儲媒介
格式化輸出
編寫第一個 Shell 腳本
啟動一個項目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達式

啟動一個項目

從這一章開始,我們將建設(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)在這個功能有點兒晦澀,但隨后,當我們介紹多行編程語句時,它會派上大用場。

第二階段:添加一點兒數(shù)據(jù)

現(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ī)則:

  1. 變量名可由字母數(shù)字字符(字母和數(shù)字)和下劃線字符組成。

  2. 變量名的第一個字符必須是一個字母或一個下劃線。

  3. 變量名中不允許出現(xiàn)空格和標點符號。

單詞 “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>"

Here Documents

我們已經(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

總結(jié)歸納

在這一章中,我們啟動了一個項目,其帶領(lǐng)我們領(lǐng)略了創(chuàng)建一個成功腳本的整個過程。 同時我們介紹了變量和常量的概念,以及怎樣使用它們。它們是我們將找到的眾多參數(shù)展開應(yīng)用程序中的第一批實例。 我們也知道了怎樣從我們的腳本文件中產(chǎn)生輸出,及其各種各樣嵌入文本塊的方法。

拓展閱讀