鍍金池/ 教程/ Linux/ 啟動一個(gè)項(xiàng)目
網(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: 軟件包管理
進(jìn)程
存儲媒介
格式化輸出
編寫第一個(gè) Shell 腳本
啟動一個(gè)項(xiàng)目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計(jì)
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達(dá)式

啟動一個(gè)項(xiàng)目

從這一章開始,我們將建設(shè)一個(gè)項(xiàng)目。這個(gè)項(xiàng)目的目的是為了了解怎樣使用各種各樣的 shell 功能來 創(chuàng)建程序,更重要的是,創(chuàng)建好程序。

我們將要編寫的程序是一個(gè)報(bào)告生成器。它會顯示系統(tǒng)的各種統(tǒng)計(jì)數(shù)據(jù)和它的狀態(tài),并將產(chǎn)生 HTML 格式的報(bào)告, 所以我們能通過網(wǎng)絡(luò)瀏覽器,比如說 Firefox 或者 Konqueror,來查看這個(gè)報(bào)告。

通常,創(chuàng)建程序要經(jīng)過一系列階段,每個(gè)階段會添加新的特性和功能。我們程序的第一個(gè)階段將會 產(chǎn)生一個(gè)非常小的 HTML 網(wǎng)頁,其不包含系統(tǒng)信息。隨后我們會添加這些信息。

第一階段:最小的文檔

首先我們需要知道的事是一個(gè)規(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

程序的第一個(gè)階段將這個(gè) HTML 文件輸出到標(biāo)準(zhǔn)輸出。我們可以編寫一個(gè)程序,相當(dāng)容易地完成這個(gè)任務(wù)。 啟動我們的文本編輯器,然后創(chuàng)建一個(gè)名為 ~/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>"

我們第一次嘗試解決這個(gè)問題,程序包含了一個(gè) shebang,一條注釋(總是一個(gè)好主意)和一系列的 echo 命令,每個(gè)命令負(fù)責(zé)輸出一行文本。保存文件之后,我們將讓它成為可執(zhí)行文件,再嘗試運(yùn)行它:

[me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page
[me@linuxbox ~]$ sys_info_page

當(dāng)程序運(yùn)行的時(shí)候,我們應(yīng)該看到 HTML 文本在屏幕上顯示出來,因?yàn)槟_本中的 echo 命令會輸出 發(fā)送到標(biāo)準(zhǔn)輸出。我們再次運(yùn)行這個(gè)程序,把程序的輸出重定向到文件 sys_info_page.html 中, 從而我們可以通過網(wǎng)絡(luò)瀏覽器來查看輸出結(jié)果:

[me@linuxbox ~]$ sys_info_page > sys_info_page.html
[me@linuxbox ~]$ firefox sys_info_page.html

到目前為止,一切順利。

在編寫程序的時(shí)候,盡量做到簡單明了,這總是一個(gè)好主意。當(dāng)一個(gè)程序易于閱讀和理解的時(shí)候, 維護(hù)它也就更容易,更不用說,通過減少鍵入量,可以使程序更容易書寫了。我們當(dāng)前的程序版本 工作正常,但是它可以更簡單些。實(shí)際上,我們可以把所有的 echo 命令結(jié)合成一個(gè) echo 命令,當(dāng)然 這樣能更容易地添加更多的文本行到程序的輸出中。那么,把我們的程序修改為:

#!/bin/bash
# Program to output a system information page
echo "<HTML>
    <HEAD>
          <TITLE>Page Title</TITLE>
    </HEAD>
    <BODY>
          Page body.
    </BODY>
</HTML>"

一個(gè)帶引號的字符串可能包含換行符,因此可以包含多個(gè)文本行。Shell 會持續(xù)讀取文本直到它遇到 右引號。它在命令行中也是這樣工作的:

[me@linuxbox ~]$ echo "<HTML>

>         <HEAD>
                <TITLE>Page Title</TITLE>
>         </HEAD>
>         <BODY>
>               Page body.
>         </BODY>
></HTML>"

開頭的 “>” 字符是包含在 PS2shell 變量中的 shell 提示符。每當(dāng)我們在 shell 中鍵入多行語句的時(shí)候, 這個(gè)提示符就會出現(xiàn)?,F(xiàn)在這個(gè)功能有點(diǎn)兒晦澀,但隨后,當(dāng)我們介紹多行編程語句時(shí),它會派上大用場。

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

現(xiàn)在我們的程序能生成一個(gè)最小的文檔,讓我們給報(bào)告添加些數(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>"

我們增加了一個(gè)網(wǎng)頁標(biāo)題,并且在報(bào)告正文部分加了一個(gè)標(biāo)題。

變量和常量

然而,我們的腳本存在一個(gè)問題。請注意字符串 “System Information Report” 是怎樣被重復(fù)使用的?對于這個(gè)微小的腳本而言,它不是一個(gè)問題,但是讓我們設(shè)想一下, 我們的腳本非常冗長,并且我們有許多這個(gè)字符串的實(shí)例。如果我們想要更換一個(gè)標(biāo)題,我們必須 對腳本中的許多地方做修改,這會是很大的工作量。如果我們能整理一下腳本,讓這個(gè)字符串只 出現(xiàn)一次而不是多次,會怎樣呢?這樣會使今后的腳本維護(hù)工作更加輕松。我們可以這樣做:

#!/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)建一個(gè)名為 title 的變量,并把 “System Information Report” 字符串賦值給它,我們就可以利用參數(shù)展開功能,把這個(gè)字符串放到文件中的多個(gè)位置。

那么,我們怎樣來創(chuàng)建一個(gè)變量呢?很簡單,我們只管使用它。當(dāng) shell 碰到一個(gè)變量的時(shí)候,它會 自動地創(chuàng)建它。這不同于許多編程語言,它們中的變量在使用之前,必須顯式的聲明或是定義。關(guān)于 這個(gè)問題,shell 要求非常寬松,這可能會導(dǎo)致一些問題。例如,考慮一下在命令行中發(fā)生的這種情形:

[me@linuxbox ~]$ foo="yes"
[me@linuxbox ~]$ echo $foo
yes
[me@linuxbox ~]$ echo $fool
[me@linuxbox ~]$

首先我們把 “yes” 賦給變量 foo,然后用 echo 命令來顯示變量值。接下來,我們顯示拼寫錯(cuò)誤的變量名 “fool” 的變量值,然后得到一個(gè)空值。這是因?yàn)?shell 很高興地創(chuàng)建了變量 fool,當(dāng) shell 遇到 fool 的時(shí)候, 并且賦給 fool 一個(gè)空的默認(rèn)值。因此,我們必須小心謹(jǐn)慎地拼寫!同樣理解實(shí)例中究竟發(fā)生了什么事情也 很重要。從我們以前學(xué)習(xí) shell 執(zhí)行展開操作,我們知道這個(gè)命令:

[me@linuxbox ~]$ echo $foo

經(jīng)歷了參數(shù)展開操作,然后得到:

[me@linuxbox ~]$ echo yes

然而這個(gè)命令:

[me@linuxbox ~]$ echo $fool

展開為:

[me@linuxbox ~]$ echo

這個(gè)空變量展開值為空!對于需要參數(shù)的命令來說,這會引起混亂。下面是一個(gè)例子:

[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.

我們給兩個(gè)變量賦值,foo 和 foo1。然后我們執(zhí)行 cp 操作,但是拼寫錯(cuò)了第二個(gè)參數(shù)的名字。 參數(shù)展開之后,這個(gè) cp 命令只接受到一個(gè)參數(shù),雖然它需要兩個(gè)。

有一些關(guān)于變量名的規(guī)則:

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

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

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

單詞 “variable” 意味著可變的值,并且在許多應(yīng)用程序當(dāng)中,都是以這種方式來使用變量的。然而, 我們應(yīng)用程序中的變量,title,被用作一個(gè)常量。常量有一個(gè)名字且包含一個(gè)值,在這方面就 像是變量。不同之處是常量的值是不能改變的。在執(zhí)行幾何運(yùn)算的應(yīng)用程序中,我們可以把 PI 定義為 一個(gè)常量,并把 3.1415 賦值給它,用它來代替數(shù)字字面值。shell 不能辨別變量和常量;它們大多數(shù)情況下 是為了方便程序員。一個(gè)常用慣例是指定大寫字母來表示常量,小寫字母表示真正的變量。我們 將修改我們的腳本來遵從這個(gè)慣例:

#!/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>"

我們亦借此機(jī)會,通過在標(biāo)題中添加 shell 變量名 HOSTNAME,讓標(biāo)題變得活潑有趣些。 這個(gè)變量名是這臺機(jī)器的網(wǎng)絡(luò)名稱。


注意:實(shí)際上,shell 確實(shí)提供了一種方法,通過使用帶有-r(只讀)選項(xiàng)的內(nèi)部命令 declare, 來強(qiáng)制常量的不變性。如果我們給 TITLE 這樣賦值:

那么 shell 會阻止之后給 TITLE 的任意賦值。這個(gè)功能極少被使用,但為了很早之前的腳本, 它仍然存在。


給變量和常量賦值

這里是我們真正開始使用參數(shù)擴(kuò)展知識的地方。正如我們所知道的,這樣給變量賦值:

variable=value

這里的variable是變量的名字,value是一個(gè)字符串。不同于一些其它的編程語言,shell 不會 在乎變量值的類型;它把它們都看作是字符串。通過使用帶有-i 選項(xiàng)的 declare 命令,你可以強(qiáng)制 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.

可以在同一行中對多個(gè)變量賦值:

a=5 b="a string"

在參數(shù)展開過程中,變量名可能被花括號 “{}” 包圍著。由于變量名周圍的上下文,其變得不明確的情況下, 這會很有幫助。這里,我們試圖把一個(gè)文件名從 myfile 改為 myfile1,使用一個(gè)變量:

[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.

這種嘗試失敗了,因?yàn)?shell 把 mv 命令的第二個(gè)參數(shù)解釋為一個(gè)新的(并且空的)變量。通過這種方法 可以解決這個(gè)問題:

[me@linuxbox ~]$ mv $filename ${filename}1

通過添加花括號,shell 不再把末尾的1解釋為變量名的一部分。

我們將利用這個(gè)機(jī)會來添加一些數(shù)據(jù)到我們的報(bào)告中,即創(chuàng)建包括的日期和時(shí)間,以及創(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。一個(gè) here document 是另外一種 I/O 重定向形式,我們 在腳本文件中嵌入正文文本,然后把它發(fā)送給一個(gè)命令的標(biāo)準(zhǔn)輸入。它這樣工作:

command << token
text
token

這里的 command 是一個(gè)可以接受標(biāo)準(zhǔn)輸入的命令名,token 是一個(gè)用來指示嵌入文本結(jié)束的字符串。 我們將修改我們的腳本,來使用一個(gè) 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 命令和一個(gè) here document。這個(gè)字符串EOF\(意思是“文件結(jié)尾”, 一個(gè)常見用法)被選作為 token,并標(biāo)志著嵌入文本的結(jié)尾。注意這個(gè) token 必須在一行中單獨(dú)出現(xiàn),并且文本行中 沒有末尾的空格。

那么使用一個(gè) here document 的優(yōu)點(diǎn)是什么呢?它很大程度上和 echo 一樣,除了默認(rèn)情況下,here documents 中的單引號和雙引號會失去它們在 shell 中的特殊含義。這里有一個(gè)命令中的例子:

[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
some text
"some text"
'some text'
$foo

正如我們所見到的,shell 根本沒有注意到引號。它把它們看作是普通的字符。這就允許我們 在一個(gè) here document 中可以隨意的嵌入引號。對于我們的報(bào)告程序來說,這將是非常方便的。

Here documents 可以和任意能接受標(biāo)準(zhǔn)輸入的命令一塊使用。在這個(gè)例子中,我們使用了 一個(gè) here document 將一系列的命令傳遞到這個(gè) ftp 程序中,為的是從一個(gè)遠(yuǎn)端 FTP 服務(wù)器中得到一個(gè)文件:

#!/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 字符。 這就能縮進(jìn)一個(gè) 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é)歸納

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

拓展閱讀