编写Shell脚本——启动项目

启动项目

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"

该特性多少存在一些问题,因为很多文本编辑器(以及程序员自身)偏好在脚本中使用空格符来代替制表符,实现缩进效果。

相关推荐
拦路雨g1 小时前
docker_data.vhdx大文件瘦身
运维·docker·容器
闲云野鹤在人间1 小时前
云计算入门|网络与存储云服务知识梳理
linux·运维·服务器·网络·centos·云计算·php
heqingchun161 小时前
Ubuntu 22.04 交叉编译 RK3568 MPP + FFmpeg 完整指南
linux·ubuntu·ffmpeg
好评1241 小时前
【Linux】五种IO模型与非阻塞IO
linux·服务器·nat
水母量化1 小时前
自动化开盘卖出如何适应价格笼子规则
运维·自动化·区块链·量化交易
码农阿豪2 小时前
不装 SSH 客户端也能连服务器:用 WebSSH 把终端搬进浏览器
运维·服务器·ssh
明月心9522 小时前
关于docker的一些命令
运维·docker·容器
云贝贝贝2 小时前
MySQL 运维高频 6 坑:字符集、超时、SQL_MODE、隐式转换、Online DDL、主从延迟
运维·sql·mysql
万象新讯2 小时前
企业海外子公司管理外包如何择优?看重数字化管理平台与全球服务覆盖,核心考核覆盖能力、自动化效率与规模化整合实力
运维·人工智能·自动化