## # 启动一个项目

Starting with this chapter, we will begin to build a program. The purpose of this project is to see how various shell features are used to create programs and, more importantly, create good programs.

The program we will write is a report generator. It will present various statistics about our system and its status, and will produce this report in HTML format, so we can view it with a web browser such as Firefox or Konqueror.

Programs are usually built up in a series of stages, with each stage adding features and capabilities. The first stage of our program will produce a very minimal HTML page that contains no system information. That will come later.

### # 第一阶段：最小的文档

The first thing we need to know is the format of a well-formed HTML document. It looks like this:

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


If we enter this into our text editor and save the file as foo.html, we can use the following URL in Firefox to view the file:

file:///home/username/foo.html


The first stage of our program will be able to output this HTML file to standard output. We can write a program to do this pretty easily. Let’s start our text editor and create a new file named ~/bin/sys_info_page:

[me@linuxbox ~]$vim ~/bin/sys_info_page  and enter the following program: 随后输入下面的程序： #!/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>"  Our first attempt at this problem contains a shebang, a comment (always a good idea) and a sequence of echo commands, one for each line of output. After saving the file, we’ll make it executable and attempt to run it: 我们第一次尝试解决这个问题，程序包含了一个 shebang，一条注释（总是一个好主意）和一系列的 echo 命令，每个命令负责输出一行文本。保存文件之后，我们将让它成为可执行文件，再尝试运行它： [me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page
[me@linuxbox ~]$sys_info_page  When the program runs, we should see the text of the HTML document displayed on the screen, since the echo commands in the script send their output to standard output. We’ll run the program again and redirect the output of the program to the file sys_info_page.html, so that we can view the result with a web browser: 当程序运行的时候，我们应该看到 HTML 文本在屏幕上显示出来，因为脚本中的 echo 命令会将输出 发送到标准输出。我们再次运行这个程序，把程序的输出重定向到文件 sys_info_page.html 中， 从而我们可以通过网络浏览器来查看输出结果： [me@linuxbox ~]$ sys_info_page > sys_info_page.html
[me@linuxbox ~]$firefox sys_info_page.html  So far, so good. 到目前为止，一切顺利。 When writing programs, it’s always a good idea to strive for simplicity and clarity. Maintenance is easier when a program is easy to read and understand, not to mention, it can make the program easier to write by reducing the amount of typing. Our current version of the program works fine, but it could be simpler. We could actually combine all the echo commands into one, which will certainly make it easier to add more lines to the program’s output. So, let’s change our program to this: 在编写程序的时候，尽量做到简单明了，这总是一个好主意。当一个程序易于阅读和理解的时候， 维护它也就更容易，更不用说，通过减少键入量，可以使程序更容易书写了。我们当前的程序版本 工作正常，但是它可以更简单些。实际上，我们可以把所有的 echo 命令结合成一个 echo 命令，当然 这样能更容易地添加更多的文本行到程序的输出中。那么，把我们的程序修改为： #!/bin/bash # Program to output a system information page echo "<HTML> <HEAD> <TITLE>Page Title</TITLE> </HEAD> <BODY> Page body. </BODY> </HTML>"  A quoted string may include newlines, and therefore contain multiple lines of text. The shell will keep reading the text until it encounters the closing quotation mark. It works this way on the command line, too: 一个带引号的字符串可能包含换行符，因此可以包含多个文本行。Shell 会持续读取文本直到它遇到 右引号。它在命令行中也是这样工作的： [me@linuxbox ~]$ echo "<HTML>

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


The leading “>” character is the shell prompt contained in the PS2 shell variable. It appears whenever we type a multi-line statement into the shell. This feature is a little obscure right now, but later, when we cover multi-line programming statements, it will turn out to be quite handy.

### # 第二阶段：添加一点儿数据

Now that our program can generate a minimal document, let’s put some data in the report. To do this, we will make the following changes:

#!/bin/bash
# Program to output a system information page
echo "<HTML>
<TITLE>System Information Report</TITLE>
<BODY>
<H1>System Information Report</H1>
</BODY>
</HTML>"


We added a page title and a heading to the body of the report.

### # 变量和常量

There is an issue with our script, however. Notice how the string “System Information Report” is repeated? With our tiny script it’s not a problem, but let’s imagine that our script was really long and we had multiple instances of this string. If we wanted to change the title to something else, we would have to change it in multiple places, which could be a lot of work. What if we could arrange the script so that the string only appeared once and not multiple times? That would make future maintenance of the script much easier. Here’s how we could do that:

#!/bin/bash
# Program to output a system information page
title="System Information Report"
echo "<HTML>
<TITLE>$title</TITLE> </HEAD> <BODY> <H1>$title</H1>
</BODY>
</HTML>"


By creating a variable named title and assigning it the value “System Information Report,” we can take advantage of parameter expansion and place the string in multiple locations.

So, how do we create a variable? Simple, we just use it. When the shell encounters a variable, it automatically creates it. This differs from many programming languages in which variables must be explicitly declared or defined before use. The shell is very lax about this, which can lead to some problems. For example, consider this scenario played out on the command line:

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


We first assign the value “yes” to the variable foo, then display its value with echo. Next we display the value of the variable name misspelled as “fool” and get a blank result. This is because the shell happily created the variable fool when it encountered it, and gave it the default value of nothing, or empty. From this, we learn that we must pay close attention to our spelling! It’s also important to understand what really happened in this example. From our previous look at how the shell performs expansions, we know that the command:

[me@linuxbox ~]$echo$foo


undergoes parameter expansion and results in:

[me@linuxbox ~]$echo yes  Whereas the command: 然而这个命令： [me@linuxbox ~]$ echo $fool  expands into: 展开为： [me@linuxbox ~]$ echo


The empty variable expands into nothing! This can play havoc with commands that require arguments. Here’s an example:

[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.  We assign values to two variables, foo and foo1. We then perform a cp, but misspell the name of the second argument. After expansion, the cp command is only sent one argument, though it requires two. 我们给两个变量赋值，foo 和 foo1。然后我们执行 cp 操作，但是拼写错了第二个参数的名字。 参数展开之后，这个 cp 命令只接受到一个参数，虽然它需要两个。 There are some rules about variable names: 有一些关于变量名的规则： 1. Variable names may consist of alphanumeric characters (letters and numbers) and underscore characters. 2. The first character of a variable name must be either a letter or an underscore. 3. Spaces and punctuation symbols are not allowed. ^ 1. 变量名可由字母数字字符（字母和数字）和下划线字符组成。 2. 变量名的第一个字符必须是一个字母或一个下划线。 3. 变量名中不允许出现空格和标点符号。 The word “variable” implies a value that changes, and in many applications, variables are used this way. However, the variable in our application, title, is used as a constant. A constant is just like a variable in that it has a name and contains a value. The difference is that the value of a constant does not change. In an application that performs geometric calculations, we might define PI as a constant, and assign it the value of 3.1415, instead of using the number literally throughout our program. The shell makes no distinction between variables and constants; they are mostly for the programmer’s convenience. A common convention is to use upper case letters to designate constants and lower case letters for true variables. We will modify our script to comply with this convention: 单词 “variable” 意味着可变的值，并且在许多应用程序当中，都是以这种方式来使用变量的。然而， 我们应用程序中的变量，title，被用作一个常量。常量有一个名字且包含一个值，在这方面就 像是变量。不同之处是常量的值是不能改变的。在执行几何运算的应用程序中，我们可以把 PI 定义为 一个常量，并把 3.1415 赋值给它，用它来代替数字字面值。shell 不能辨别变量和常量；它们大多数情况下 是为了方便程序员。一个常用惯例是指定大写字母来表示常量，小写字母表示真正的变量。我们 将修改我们的脚本来遵从这个惯例： #!/bin/bash # Program to output a system information page TITLE="System Information Report For$HOSTNAME"
echo "<HTML>
<TITLE>$TITLE</TITLE> </HEAD> <BODY> <H1>$TITLE</H1>
</BODY>
</HTML>"


We also took the opportunity to jazz up our title by adding the value of the shell variable HOSTNAME. This is the network name of the machine.

Note: The shell actually does provide a way to enforce the immutability of constants, through the use of the declare builtin command with the -r (read- only) option. Had we assigned TITLE this way:

declare -r TITLE="Page Title"

the shell would prevent any subsequent assignment to TITLE. This feature is rarely used, but it exists for very formal scripts.

#### # 给变量和常量赋值

Here is where our knowledge of expansion really starts to pay off. As we have seen, variables are assigned values this way:

variable=value


where variable is the name of the variable and value is a string. Unlike some other programming languages, the shell does not care about the type of data assigned to a variable; it treats them all as strings. You can force the shell to restrict the assignment to integers by using the declare command with the -i option, but, like setting variables as read-only, this is rarely done.

Note that in an assignment, there must be no spaces between the variable name, the equals sign, and the value. So what can the value consist of? Anything that we can expand into a string:

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.  Multiple variable assignments may be done on a single line: 可以在同一行中对多个变量赋值： a=5 b="a string"  During expansion, variable names may be surrounded by optional curly braces “{}”. This is useful in cases where a variable name becomes ambiguous due to its surrounding context. Here, we try to change the name of a file from myfile to myfile1, using a variable: 在参数展开过程中，变量名可能被花括号 “{}” 包围着。由于变量名周围的上下文，其变得不明确的情况下， 这会很有帮助。这里，我们试图把一个文件名从 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.  This attempt fails because the shell interprets the second argument of the mv command as a new (and empty) variable. The problem can be overcome this way: 这种尝试失败了，因为 shell 把 mv 命令的第二个参数解释为一个新的（并且空的）变量。通过这种方法 可以解决这个问题： [me@linuxbox ~]$ mv $filename${filename}1


By adding the surrounding braces, the shell no longer interprets the trailing 1 as part of the variable name.

We’ll take this opportunity to add some data to our report, namely the date and time the report was created and the user name of the creator:

#!/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>
<TITLE>$TITLE</TITLE> </HEAD> <BODY> <H1>$TITLE</H1>
<P>$TIME_STAMP</P> </BODY> </HTML>"  ### # Here Documents We’ve looked at two different methods of outputting our text, both using the echo command. There is a third way called a here document or here script. A here document is an additional form of I/O redirection in which we embed a body of text into our script and feed it into the standard input of a command. It works like this: 我们已经知道了两种不同的文本输出方法，两种方法都使用了 echo 命令。还有第三种方法，叫做 here document 或者 here script。一个 here document 是另外一种 I/O 重定向形式，我们 在脚本文件中嵌入正文文本，然后把它发送给一个命令的标准输入。它这样工作： command << token text token  where command is the name of command that accepts standard input and token is a string used to indicate the end of the embedded text. We’ll modify our script to use a here document: 这里的 command 是一个可以接受标准输入的命令名，token 是一个用来指示嵌入文本结束的字符串。 我们将修改我们的脚本，来使用一个 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>
<BODY>
<H1>$TITLE</H1> <P>$TIME_STAMP</P>
</BODY>
</HTML>
_EOF_


Instead of using echo, our script now uses cat and a here document. The string _EOF_ (meaning “End Of File,” a common convention) was selected as the token, and marks the end of the embedded text. Note that the token must appear alone and that there must not be trailing spaces on the line.

So what’s the advantage of using a here document? It’s mostly the same as echo, except that, by default, single and double quotes within here documents lose their special meaning to the shell. Here is a command line example:

[me@linuxbox ~]$foo="some text" [me@linuxbox ~]$ cat << _EOF_
> $foo > "$foo"
> '$foo' > \$foo
> _EOF_
some text
"some text"
'some text'
$foo  As we can see, the shell pays no attention to the quotation marks. It treats them as ordinary characters. This allows us to embed quotes freely within a here document. This could turn out to be handy for our report program. 正如我们所见到的，shell 根本没有注意到引号。它把它们看作是普通的字符。这就允许我们 在一个 here document 中可以随意的嵌入引号。对于我们的报告程序来说，这将是非常方便的。 Here documents can be used with any command that accepts standard input. In this example, we use a here document to pass a series of commands to the ftp program in order to retrieve a file from a remote FTP server: Here documents 可以和任意能接受标准输入的命令一块使用。在这个例子中，我们使用了 一个 here document 将一系列的命令传递到这个 ftp 程序中，为的是从一个远端 FTP 服务器中得到一个文件： #!/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  If we change the redirection operator from "<<" to "<<-", the shell will ignore leading tab characters in the here document. This allows a here document to be indented, which can improve readability: 如果我们把重定向操作符从 “<<” 改为 “<<-”，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


### # 总结归纳

In this chapter, we started a project that will carry us through the process of building a successful script. We introduced the concept of variables and constants and how they can be employed. They are the first of many applications we will find for parameter expansion. We also looked at how to produce output from our script, and various methods for embedding blocks of text.