启动项目
-
- 1、第一阶段:最小化文档
- 2、第二阶段:添加数据
- 3、变量与常量
-
- 3.1、为变量与常量赋值
- [3.2、here document](#3.2、here document)
1、第一阶段:最小化文档
我们要做的第一件事就是创建结构良好的HTML文件,如下所示:
html
<html>
<head>
<title>Page Title</title>
</head>
<body>
Page body.
</body>
</html>
如果将这些内容输入文本编辑器,将文件保存为foo.html,就可以在FireFox中使用地址file:///home/username/foo.html查看该文件。
程序第一阶段的目标是将这个HTML文件输出至标准输出。我们可以编写一个程序来轻松地完成此任务。启动文本编辑器,创建新文件~/bin/sys_info_page:
bash
[me@linuxbox ~]$ vim ~/bin/sys_info_page
输入下列程序:
bash
#! /bin /bash
# 程序输出一个系统信息页
echo "<html>"
echo " <head>"
echo " <title>Page Title< /title>"
echo " < /head>"
echo " <body>"
echo " Page body."
echo " < /body>"
echo "< /html>"
我们给出的第一个解决方案中包含了shebang行、注释以及一系列echo命令。保存好文件,设置可执行权限,执行一下试一试:
bash
[me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page
[me@linuxbox ~]$ sys_info_page
程序执行时,我们应该会看到HTML文档的内容出现在显示器上,这是因为脚本中的echo命令会将其输出发送至标准输出。再次执行该程序,将程序的输出重定向到sys_info_page.html,这样就能使用Web浏览器查看结果了:
bash
[me@linuxbox ~]$ sys_info_page > sys_info_page.html
[me@linuxbox ~]$ firefox sys_info_page.html
在编写程序时,应该力求简洁明了。如果程序利于阅读和理解,维护起来就省心多了,而且通过减少输入量,程序也更容易编写。该程序的当前版本虽然工作良好,但还可以简化。我们可以将所有的echo命令合并成一个,这样的话,在添加更多的程序输出时肯定会更容易。因此,将程序修改如下:
bash
#!/bin/bash
# 程序输出一个系统信息页
echo "<html>
<head>
<title>Page Title< /title>
< /head>
<body>
Page body.
< /body>
< /html>"
被引用的字符串中可以加入换行符,也就是说,能够包含多行文本。Shell持续读入文本,直到遇见闭合引号。这种方法在命令行中也管用:
bash
[me@linuxbox ~]$ echo "<html>
> <head>
> <title>Page Title</title>
> </head>
> <body>
> Page body.
> </body>
> </html>"
每行开头的>属于提示符,保存在Shell变量PS2中。只要在Shell中输入了多行语句,就会出现该提示符。这个特性现在还有点儿让人不明所以,但是稍后,等到我们介绍多行编程语句时,你就会发现它非常方便。
2、第二阶段:添加数据
现在,我们程序已经可以生成最小化文档,接着要在其中添加数据。为此,需要做出以下改动:
bash
#!/bin/bash
# 程序输出一个系统信息页
echo "<html>
<head>
<title>System Information Report</title>
</head>
<body>
<h1>System Information Report</h1>
</body>
</html>"
我们加入了一个页标题,并为报告添加了正文标题。
3、变量与常量
但是,我们的脚本存在一个问题。注意字符串SystemInformation Report重复了多少次?对我们这种微型脚本来说,这算不上什么事。然而,假设我们的脚本代码量很大,其中就会有大量该字符串的实例。如果想把标题修改 为其他内容,那就不得不在多处进行修改,这工作量可就大了。如果我们修改脚本,让字符串只出现一次而不是多次呢?这大大简化了日后的脚本维护工作。实现方法如下:
bash
#!/bin/bash
# 程序输出一个系统信息页
title="System Information Report"
echo "<html>
<head>
<title>$title</title>
</head>
<body>
<h1>$title</h1>
</body>
</html>"
通过创建名为title的变量并为其赋值System InformationReport,我们就可以利用参数扩展,将字符串置于多个位置。
那该如何创建变量?很简单,直接用就行了。当Shell碰到一个变量时,会自动创建该变量。这一点不同于很多编程语言,在后者中,在使用变量之前必须明确声明或定义变量。Shell对此要求并不严格,这也导致了一些问题。例如,考虑命令行中出现的下列场景:
bash
[me@linuxbox ~]$ foo="yes"
[me@linuxbox ~]$ echo $foo
yes
[me@linuxbox ~]$ echo $fool
[me@linuxbox ~]$
我们先为变量foo赋值yes,然后使用echo显示该变量的值。接下来,我们显示了一个拼错名字的变量fool的值,结果得到的是空值。这是因为Shell在碰到fool变量时,很乐意创建该变量,并为其设置了默认的空值。由此看出,一定要加倍注意自己的拼写!此外,理解在这个示例中究竟发生了什么也非常重要。我们先前学过,Shell会执行扩展,所以下列命令:
bash
[me@linuxbox ~]$ echo $foo
其完成参数扩展后的结果为:
bash
[me@linuxbox ~]$ echo yes
相比之下,下列命令:
bash
[me@linuxbox ~]$ echo $fool
其完成参数扩展后的结果为:
bash
[me@linuxbox ~]$ echo
空变量被扩展之后也是空变量!这对要求参数的命令而言可就是灾难了。来看下面的例子:
bash
[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赋值。在执行cp命令时,不小心拼错了第二个参数的名称。经过扩展之后,cp命令只得到了一个参数,而按照命令语法,则需要两个参数。
变量命名是有一些规则的。
- 变量名可以包含字母数字字符(字母和数字)和下画线。
- 变量名的第一个字符只能是字母或下画线。
- 变量名中不允许出现空格符和标点符号。
变量也就是这么用的。但是,在我们的程序中,变量title被用作常量。常量和变量一样,有名称,也有值。
两者的区别在于常量的值不能改变。在执行几何计算的应用中,我们可以定义PI为常量,将其赋值为3.1415,不再在程序中到处使用具体数值。Shell并不区分变量和常量,这种区分主要是为了方便程序员理解。普遍惯例是使用大写字母表示常量,小写字母表示变量。我们将脚本据此进行修改:
bash
#!/bin/bash
# 程序输出一个系统信息页
TITLE="System Information Report For $HOSTNAME"
echo "<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
</body>
</html>"
我们还借此机会在标题中加入了Shell变量HOSTNAME的值。该变量包含着主机的网络名称。
注意
Shell其实还提供了一种强制常量不变性(immutability)的方法,这是通过使用带有-r选项(read-only)的内建命令delcare实现的。我们用此方法为TITLE赋值:
bash
declare -r TITLE="Page Title"
Shell会阻止之后对TITLE的赋值操作。这个特性很少被使用,只有在非常正式的脚本中才会看到它。
3.1、为变量与常量赋值
我们之前学过的Shell扩展知识就要在这里派上用场了。变量的赋值方式如下:
bash
variable=value
其中,variable是变量名,value是字符串。不同于其他一些编程语言,Shell并不在意变量赋值的数据类型,它将一切都视为字符串。你可以使用带有-i选项的declare命令强制Shell只能赋值整数,但就像设置只读变量一样,这种做法也很少见。
注意,在赋值操作中,变量名、赋值号、值这三者之间绝不能出现空白字符。那么,值可以是什么?只要值能扩展成字符串,是什么都可以。
bash
a=z # 将字符串z赋给变量a
b="a string" # 内嵌空格符的字符串必须放入双引号中
c="a string and $b" # 赋值中包含其他扩展(例如,变量扩展)
d="$(ls -l foo.txt)" # 命令的执行结果
e=$((5 * 7)) # 算术扩展
f="\t\ta string\n" # 转义序列,例如制表符、换行符
在一行中也可以完成多个变量的赋值。
bash
a=5 b="a string"
在进行扩展时,变量名两边可以有选择地添加花括号。如果变量名由于其所在的上下文而产生歧义,花括号就能发挥作用了。我们尝试使用变量将文件myfile重命名为myfile1:
bash
[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命令的第二个参数解释为一个新变量(内容为空)。解决方法如下:
bash
[me@linuxbox ~]$ mv "$filename" "${filename}1"
在两边添加花括号之后,Shell就不会再认为末尾的1属于变量名的一部分了。
注意
将变量和命令替换置于双引号内,以此限制Shell对其进行单词分割(word-splitting)操作,这是一种很好的做法。如果变量中可能包含文件名,双引号就尤为重要。
我们借此机会向报告中添加一些数据:报告生成的日期和时间以及创建者的用户名。
bash
#!/bin/bash
# 程序输出一个系统信息页
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
echo "<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
</body>
</html>"
3.2、here document
我们已经见识过两种不同的文本输出方式,二者使用的都是echo命令,还有第3种称为here document或herescript的方式。here document是另一种I/O重定向形式,我们利用它在脚本内嵌入文本块并将其送至命令的标准输入。here document的形式如下:
bash
command << token
text
token
其中,command是能够接受标准输入的命令名称,token是一个字符串,用于标示嵌入文本的起止。我们接下来按照here document的方法修改脚本:
bash
#!/bin/bash
# 程序输出一个系统信息页
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
</body>
</html>
_EOF_
我们使用cat和here document代替了先前的echo。选择字符串_EOF_(文件起止,end of file的缩写,这也是命名惯例)作为token,标示嵌入文本的起止。注意,该字符串必须单独出现,所在行的末尾绝不能有空白字符。
那么,here document的优势何在?它其实和echo大同小异,只不过在默认情况下,here document中的单引号和双引号不具备特殊含义。来看一个命令行示例:
bash
[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
some text
"some text"
'some text'
$foo
可以看出,Shell并没有理会引号,只是将其作为普通字符对待。这就使在here document中能够随意嵌入引号。对我们的报告程序而言,这实在是太方便了。
所有能够接受标准输入的命令都可以使用heredocument。在这个例子中,我们用here document向ftp程序传递一系列命令,以便从远程FTP服务器上下载文件:
bash
#!/bin/bash
# 通过FTP检索文件的脚本
FTP_SERVER=ftp.nl.debian.org FTP
PATH=/debian/dists/stretch/main/installeramd64/
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就会忽略heredocument中每行文本开头的制表符(但不忽略空格符)。这样就可以对here document进行缩进,增强可读性。
bash
#!/bin/bash
# 通过FTP检索文件的脚本
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/stretch/main/installer-amd64/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"
该特性多少存在一些问题,因为很多文本编辑器(以及程序员自身)偏好在脚本中使用空格符来代替制表符,实现缩进效果。