启动一个项目

    我们将要编写的程序是一个报告生成器。它会显示系统的各种统计数据和它的状态,并将产生 HTML 格式的报告,所以我们能通过网络浏览器,比如说 Firefox 或者 Konqueror,来查看这个报告。

    通常,创建程序要经过一系列阶段,每个阶段会添加新的特性和功能。我们程序的第一个阶段将会产生一个非常小的 HTML 网页,其不包含系统信息。随后我们会添加这些信息。

    首先我们需要知道的事是一个规则的 HTML 文档的格式。它看起来像这样:

    如果我们将这些内容输入到文本编辑器中,并把文件保存为 foo.html,然后我们就能在 Firefox 中使用下面的 URL 来查看文件内容:

    程序的第一个阶段将这个 HTML 文件输出到标准输出。我们可以编写一个程序,相当容易地完成这个任务。启动我们的文本编辑器,然后创建一个名为 ~/bin/sys_info_page 的新文件:

    1. [me@linuxbox ~]$ vim ~/bin/sys_info_page

    随后输入下面的程序:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. echo "<HTML>"
    4. echo " <HEAD>"
    5. echo " <TITLE>Page Title</TITLE>"
    6. echo " </HEAD>"
    7. echo " <BODY>"
    8. echo " Page body."
    9. echo " </BODY>"
    10. echo "</HTML>"

    我们第一次尝试解决这个问题,程序包含了一个 shebang,一条注释(总是一个好主意)和一系列的 echo 命令,每个命令负责输出一行文本。保存文件之后,我们将让它成为可执行文件,再尝试运行它:

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

    当程序运行的时候,我们应该看到 HTML 文本在屏幕上显示出来,因为脚本中的 echo 命令会将输出发送到标准输出。我们再次运行这个程序,把程序的输出重定向到文件 sys_info_page.html 中,从而我们可以通过网络浏览器来查看输出结果:

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

    到目前为止,一切顺利。

    在编写程序的时候,尽量做到简单明了,这总是一个好主意。当一个程序易于阅读和理解的时候,维护它也就更容易,更不用说,通过减少键入量,可以使程序更容易书写了。我们当前的程序版本工作正常,但是它可以更简单些。实际上,我们可以把所有的 echo 命令结合成一个 echo 命令,当然这样能更容易地添加更多的文本行到程序的输出中。那么,把我们的程序修改为:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. echo "<HTML>
    4. <HEAD>
    5. <TITLE>Page Title</TITLE>
    6. </HEAD>
    7. <BODY>
    8. Page body.
    9. </BODY>
    10. </HTML>"

    一个带引号的字符串可能包含换行符,因此可以包含多个文本行。Shell 会持续读取文本直到它遇到右引号。它在命令行中也是这样工作的:

    1. [me@linuxbox ~]$ echo "<HTML>
    2. > <HEAD>
    3. <TITLE>Page Title</TITLE>
    4. > </HEAD>
    5. > <BODY>
    6. > Page body.
    7. > </BODY>
    8. ></HTML>"

    开头的 “>” 字符是包含在 PS2shell 变量中的 shell 提示符。每当我们在 shell 中键入多行语句的时候,这个提示符就会出现。现在这个功能有点儿晦涩,但随后,当我们介绍多行编程语句时,它会派上大用场。

    26.2 第二阶段:添加一点儿数据

    现在我们的程序能生成一个最小的文档,让我们给报告添加些数据吧。为此,我们将做以下修改:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. echo "<HTML>
    4. <HEAD>
    5. <TITLE>System Information Report</TITLE>
    6. </HEAD>
    7. <BODY>
    8. </BODY>
    9. </HTML>"

    我们增加了一个网页标题,并且在报告正文部分加了一个标题。

    然而,我们的脚本存在一个问题。请注意字符串 “System Information Report” 是怎样被重复使用的?对于这个微小的脚本而言,它不是一个问题,但是让我们设想一下,我们的脚本非常冗长,并且我们有许多这个字符串的实例。如果我们想要更换一个标题,我们必须对脚本中的许多地方做修改,这会是很大的工作量。如果我们能整理一下脚本,让这个字符串只出现一次而不是多次,会怎样呢?这样会使今后的脚本维护工作更加轻松。我们可以这样做:

    通过创建一个名为 title 的变量,并把 “System Information Report” 字符串赋值给它,我们就可以利用参数展开功能,把这个字符串放到文件中的多个位置。

    26.3.1 创建变量和常量

    那么,我们怎样来创建一个变量呢?很简单,我们只管使用它。当 shell 碰到一个变量的时候,它会自动地创建它。这不同于许多编程语言,它们中的变量在使用之前,必须显式的声明或是定义。关于这个问题,shell 要求非常宽松,这可能会导致一些问题。例如,考虑一下在命令行中发生的这种情形:

    1. [me@linuxbox ~]$ echo $foo
    2. yes
    3. [me@linuxbox ~]$ echo $fool
    4. [me@linuxbox ~]$
    1. [me@linuxbox ~]$ echo $foo

    经历了参数展开操作,然后得到:

    1. [me@linuxbox ~]$ echo yes

    然而这个命令:

    1. [me@linuxbox ~]$ echo $fool

    展开为:

    1. [me@linuxbox ~]$ echo

    这个空变量展开值为空!对于需要参数的命令来说,这会引起混乱。下面是一个例子:

    1. [me@linuxbox ~]$ foo=foo.txt
    2. [me@linuxbox ~]$ foo1=foo1.txt
    3. [me@linuxbox ~]$ cp $foo $fool
    4. cp: missing destination file operand after `foo.txt'
    5. Try `cp --help' for more information.

    我们给两个变量赋值,foo 和 foo1。然后我们执行 cp 操作,但是拼写错了第二个参数的名字。参数展开之后,这个 cp 命令只接受到一个参数,虽然它需要两个。

    有一些关于变量名的规则:

    1. 变量名可由字母数字字符(字母和数字)和下划线字符组成。

    2. 变量名的第一个字符必须是一个字母或一个下划线。

    3. 变量名中不允许出现空格和标点符号。

    单词 “variable” 意味着可变的值,并且在许多应用程序当中,都是以这种方式来使用变量的。然而,我们应用程序中的变量,title,被用作一个常量。常量有一个名字且包含一个值,在这方面就像是变量。不同之处是常量的值是不能改变的。在执行几何运算的应用程序中,我们可以把 PI 定义为一个常量,并把 3.1415 赋值给它,用它来代替数字字面值。shell 不能辨别变量和常量;它们大多数情况下是为了方便程序员。一个常用惯例是指定大写字母来表示常量,小写字母表示真正的变量。我们将修改我们的脚本来遵从这个惯例:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. TITLE="System Information Report For $HOSTNAME"
    4. echo "<HTML>
    5. <HEAD>
    6. <TITLE>$TITLE</TITLE>
    7. </HEAD>
    8. <BODY>
    9. <H1>$TITLE</H1>
    10. </BODY>
    11. </HTML>"

    我们亦借此机会,通过在标题中添加 shell 变量名 HOSTNAME,让标题变得活泼有趣些。这个变量名是这台机器的网络名称。


    注意:

    实际上,shell 确实提供了一种方法,通过使用带有-r(只读)选项的内部命令 declare,
    来强制常量的不变性。如果我们给 TITLE 这样赋值:

    declare -r TITLE=”Page Title”

    那么 shell 会阻止之后给 TITLE 的任意赋值。这个功能极少被使用,但为了很早之前的脚本,
    它仍然存在。


    26.3.2 给变量和常量赋值

    这里是我们真正开始使用参数扩展知识的地方。正如我们所知道的,这样给变量赋值:

    1. variable=value

    这里的 variable 是变量的名字,value 是一个字符串。不同于一些其它的编程语言,shell 不会在乎变量值的类型;它把它们都看作是字符串。通过使用带有-i 选项的 declare 命令,你可以强制 shell 把赋值限制为整数,但是,正如像设置变量为只读一样,极少这样做。

    可以在同一行中对多个变量赋值:

    1. a=5 b="a string"

    在参数展开过程中,变量名可能被花括号 “{}” 包围着。由于变量名周围的上下文,其变得不明确的情况下,这会很有帮助。这里,我们试图把一个文件名从 myfile 改为 myfile1,使用一个变量:

    1. [me@linuxbox ~]$ filename="myfile"
    2. [me@linuxbox ~]$ touch $filename
    3. [me@linuxbox ~]$ mv $filename $filename1
    4. mv: missing destination file operand after `myfile'
    5. Try `mv --help' for more information.

    这种尝试失败了,因为 shell 把 mv 命令的第二个参数解释为一个新的(并且空的)变量。通过这种方法可以解决这个问题:

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

    通过添加花括号,shell 不再把末尾的1解释为变量名的一部分。

    我们将利用这个机会来添加一些数据到我们的报告中,即创建包括的日期和时间,以及创建者的用户名:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. TITLE="System Information Report For $HOSTNAME"
    4. CURRENT_TIME=$(date +"%x %r %Z")
    5. TIME_STAMP="Generated $CURRENT_TIME, by $USER"
    6. echo "<HTML>
    7. <HEAD>
    8. <TITLE>$TITLE</TITLE>
    9. </HEAD>
    10. <BODY>
    11. <H1>$TITLE</H1>
    12. <P>$TIME_STAMP</P>
    13. </HTML>"

    26.4 Here 文档

    我们已经知道了两种不同的文本输出方法,两种方法都使用了 echo 命令。还有第三种方法,叫做 here document 或者 here script。一个 here document 是另外一种 I/O 重定向形式,我们在脚本文件中嵌入正文文本,然后把它发送给一个命令的标准输入。它这样工作:

    1. command << token
    2. text
    3. token

    这里的 command 是一个可以接受标准输入的命令名,token 是一个用来指示嵌入文本结束的字符串。我们将修改我们的脚本,来使用一个 here document:

    1. #!/bin/bash
    2. # Program to output a system information page
    3. TITLE="System Information Report For $HOSTNAME"
    4. CURRENT_TIME=$(date +"%x %r %Z")
    5. TIME_STAMP="Generated $CURRENT_TIME, by $USER"
    6. cat << _EOF_
    7. <HTML>
    8. <HEAD>
    9. <TITLE>$TITLE</TITLE>
    10. </HEAD>
    11. <BODY>
    12. <H1>$TITLE</H1>
    13. <P>$TIME_STAMP</P>
    14. </BODY>
    15. </HTML>
    16. _EOF_

    取代 echo 命令,现在我们的脚本使用 cat 命令和一个 here document。这个字符串EOF\(意思是“文件结尾”,一个常见用法)被选作为 token,并标志着嵌入文本的结尾。注意这个 token 必须在一行中单独出现,并且文本行中没有末尾的空格。

    那么使用一个 here document 的优点是什么呢?它很大程度上和 echo 一样,除了默认情况下,here documents 中的单引号和双引号会失去它们在 shell 中的特殊含义。这里有一个命令中的例子:

    1. [me@linuxbox ~]$ foo="some text"
    2. [me@linuxbox ~]$ cat << _EOF_
    3. > $foo
    4. > "$foo"
    5. > '$foo'
    6. > \$foo
    7. > _EOF_
    8. some text
    9. "some text"
    10. 'some text'
    11. $foo

    正如我们所见到的,shell 根本没有注意到引号。它把它们看作是普通的字符。这就允许我们在一个 here document 中可以随意的嵌入引号。对于我们的报告程序来说,这将是非常方便的。

    Here documents 可以和任意能接受标准输入的命令一块使用。在这个例子中,我们使用了一个 here document 将一系列的命令传递到这个 ftp 程序中,为的是从一个远端 FTP 服务器中得到一个文件:

    1. #!/bin/bash
    2. # Script to retrieve a file via FTP
    3. FTP_SERVER=ftp.nl.debian.org
    4. FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
    5. REMOTE_FILE=debian-cd_info.tar.gz
    6. ftp -n << _EOF_
    7. open $FTP_SERVER
    8. user anonymous me@linuxbox
    9. cd $FTP_PATH
    10. hash
    11. get $REMOTE_FILE
    12. bye
    13. _EOF_

    如果我们把重定向操作符从 “<\<” 改为 “<\<-”,shell 会忽略在此 here document 中开头的 tab 字符。这就能缩进一个 here document,从而提高脚本的可读性:

    在这一章中,我们启动了一个项目,其带领我们领略了创建一个成功脚本的整个过程。同时我们介绍了变量和常量的概念,以及怎样使用它们。它们是我们将找到的众多参数展开应用程序中的第一批实例。我们也知道了怎样从我们的脚本文件中产生输出,及其各种各样嵌入文本块的方法。

    拓展阅读