前言:
现在在我们已经知道了Linux系统和命令行的基础知识,是时候开始编程了。本章讨论编写 shell脚本的基础知识。在开始编写自己的shell脚本前,你必须了解的基本概念都在这里。
一、多个shell命令的使用
这就是一个最简单的shell脚本,运行了两个shell命令,who命令先运行,输出了当前是谁登录了系统,而后运行了whoami,输出的是当前有效用户名。使用这个方式可以运行多个命令,他们都是以此串行的。
二、构建一个shell文件
构建一个shell文件,最简单的理解就是将类似上述的命令放在一个文本文件里,文本文件的核心开头是:
#!/bin/bash
#内容解释:
在通常的shell脚本中,井号(#)用作注释行。shell并不会处理shell脚本中的注释行。而, shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本
该行内容必须放在文本的第一行,表示使用的shell类型;本文以常用的bash为例,更多了类型的shell可以参考此文。
在第一行的内容后面,就可以写入你要执行的shell命令了,可以都写在一行,用分号隔开,但是一般情况下,为了美观和更高的辨识度,我们选择一行写一个命令,加上一个回车符,在输入另一个命令。好比下图:
推荐格式:
需要说明的是,你可以在文本中用"#"来注释你的内容,这样这些被注释的内容,shell就不会识别和执行了,一般我们会在脚本中写一些说明性的描述,这时需要用到"#"。如下图:
上述可以是一个完整的shell脚本了,可以直接保存为脚本文件test1,但是此时我们如果直接执行test1文件的话,还是不能达到效果的,会提示 command not found,这里就需要提到shell里的PATH环境变量的概念。shell 命令的查找都是通过环境变量的。
我们可以查看当前主机的环境变量:
此时我们的test1命令并没有生效,如果我们想要使其神效,可以采用这两个方式:
- 将shell脚本文件所处的目录添加到PATH环境变量中;
- 在提示符中用绝对或相对文件路径来引用shell脚本文件;
经验提示:
在centos Linux 发行版中,有的会将 $HOME/bin 目录添加进了 PATH 环境变量。它在每个用户的 HOME 目录下提供了一个存放文件的地方,shell 可以在那里查找要执行的命令;
核心说明:
执行时我们会发现还是没有执行成功,此时可以看到终端打印了 " Permission denied",这报错大家一定要熟悉起来,因为在以后的工作中,我们可能会遇到很多这种报错,遇到这个问题我们的第一反应就应该是想到,有些文件或者目录,我们当前的用户是没有相关的权限导致。正如test1文件,我们当前的test1用户是没有执行权限的,所以我们需要做的就是使用chmod 给文件添加对应的权限。
chmod u+x test1 添加权限后:
此时脚本文件就可以正常执行了。
实战解说:
工作中我们创建的脚本文件,一般都是用.sh 结尾的,这个是给我们电脑的使用者来识别用的,这样我们就可以一眼识别这个文件就是一个shell 脚本文件,比如上面的test1文件,我们通常是命名为test1.sh的。而且执行这个文件的时候,我们可以有一个更简单的方式,脚本对应的sh或bash来执行,好比上面的./test1 我们可以更换为 bash test1,会有同样的效果。
三、终端打印消息
很多时候shell都会输出一定的内容到终端,我们如果也想在脚本中输出一些内容到终端显示,告诉执行脚本的人,这个脚本在执行哪些功能,这个时候我们就需要用到echo命令来辅助。
最简单的输出如下:
echo 命令会将跟在它后面的字符串打印到终端屏幕。
此时我们如果想要引号也输出在终端的话,需要这样做:echo "Let's see if this'll work"
核心总结:
echo 命令可用单引号或双引号来划定文本字符串。如果在字符串中用到了它们,你需要在文本中使用其中一种引号,而用另外一种来将字符串划定起来。
此时我们就可以在脚本文件中任意位置使用echo来输出我们打算输出的内容了。如下图:
常用的组合命令参数:
- -n 不换行输出
- -e 处理特殊字符
\a 发出警告声;
\b 删除前一个字符;
\c 最后不加上换行符号;
\f 换行但光标仍旧停留在原来的位置;
\n 换行且光标移至行首;
\r 光标移至行首,但不换行;
\t 插入tab;
\v 与\f相同;
\ 插入\字符;
\nnn 插入nnn(八进制)所代表的ASCII字符;
实战解说:
在实际的使用中,我们通常也会使用echo 配合>>将内容追加到文本文件中,如下图:
四、变量的使用
有些时候我们会需要在 shell 命令使用 其他数据来处理信息。这可以通过变量来实现。变量允许临时性地将信息存储在shell 脚本中, 以便和脚本中的其他命令一起使用。
4.1 环境变量
shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用 户名、用户的系统ID (也称为 UID )、用户的默认主目录以及 shell 查找程序的搜索径。可以用 set命令来显示一份完整的当前环境变量列表。
【...】
在脚本中,变量的使用格式是:$变量名称
下面是变量在脚本中的使用,可以看到
实战解说:
如上文中的$HOME,我们一般还可以写成${HOME} 这两者的效果是等同的,而且需要注意的是,我们在$符号之前不能只是\,这样变量就会失效了,另一个需要注意的是,当你的变量需要和一个字符串连用的时候,此时一定要用{}的形式,否则变量会失效,如下图演示:
变量用{}包括起来:
4.2 用户变量
除了环境变量, shell 脚本还允许在脚本中定义和使用自己的变量。定义变量允许临时存储数据并在整个脚本中使用,从而使 shell 脚本看起来更像一个真正的计算机程序。用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过 20 个。用户变量区分大小写,所以变量 Var1 和变量 var1 是不同的。这个小规矩经常让脚本编程初学者感到头疼。使用等号将值赋给用户变量。在变量、等号和值之间不能出现空格(另一个困扰初学者的用法)。这里有一些给用户变量赋值的例子。
变量示例:
var1=10var2=-57var3=testingvar4="still more testing"
核心解说:
变量每次被引用时,都会输出当前赋给它的值。需要记住的是,引用一个变量值时需要使 用$符,而引用变量来对其进行赋值时则不要使用美元符。看下面的例子。
4.3命令替换
shell 脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本数据时尤为方便。
两种操作方式:
- 反引号字符(`)
- $()
核心解说:
需要注意反引号字符,这可不是用于字符串的那个普通的单引号字符。由于在 shell 脚本之外很 少用到,你可能甚至都不知道在键盘什么地方能找到这个字符。但你必须慢慢熟悉它,因为这是 许多shell 脚本中的重要组件。提示:在美式键盘上,它通常和波浪线( ~ )位于同一键位。 命令替换允许你将shell 命令的输出赋给变量。尽管这看起来并不那么重要,但它却是脚本编 程中的一个主要组成部分。
实战详解:
命令替换会创建一个子 shell 来运行对应的命令。子 shell ( subshell )是由运行该脚本的 shell所创建出来的一个独立的子 shell ( child shell )。正因如此,由该子 shell 所执行命令是无法使用脚本中所创建的变量的。在命令行提示符下使用路径 ./ 运行命令的话,也会创建出子 shell ;要是运行命令的时候 不加入路径,就不会创建子shell 。如果你使用的是内建的 shell 命令,并不会涉及子 shell 。 在命令行提示符下运行脚本时一定要留心!
五、重定向输入和输出
很多时候想要保存某个命令的输出而不仅仅只是让它显示在显示器上。 bash shell 提供了几 个操作符,可以将命令的输出重定向到另一个位置(比如文件)。重定向可以用于输入,也可以 用于输出,可以将文件重定向到命令输入。
5.1 输出重定向
command > outputfile
可以看到,who命令产生的内容并没有覆盖1.txt中已有的内容,而是追加到文件的末尾。
5.2 输入重定向
command < inputfile
- 文本的行数
- 文本的词数
- 文本的字节数
六、管道
通过前面的学习,我们已经知道了怎样从文件重定向输入,以及重定向输出到文件。Shell 还有一种功能,就是可以将两个或者多个命令(程序或者进程)连接到一起,把一个命令的输出作为下一个命令的输入,以这种方式连接的两个或者多个命令就形成了管道 ‘|’(pipe)。
Linux 管道使用竖线|
连接多个命令,这被称为管道符。Linux 管道的具体语法格式如下:
command1 | command2
command1 | command2 [ | commandN... ]
当在两个命令之间设置管道时,管道符
|
左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道。大部分的 Linux 命令都可以用来形成管道。
核心讲解:
这里需要注意,command1 必须有正确输出,而 command2 必须可以处理 command2 的输出结果;而且 command2 只能处理 command1 的正确输出结果,不能处理 command1 的错误信息。
使用示例:
a.工作中常用的就是配合grep 使用,下图表示的意思是,将cat读取出来的文本内容发送到 grep 命令;
b.使用管道将 cat 命令的输出作为 less 命令的输入,这样就可以将 cat 命令的输出每次按照一个屏幕的长度显示,这对于查看长度大于一个屏幕的文件内容很有帮助。
c.查看指定程序的进程运行状态,并将输出重定向到文件中。
d.统计系统中当前登录的用户数。
七、执行数学运算
对任何编程语言都很重要的特性是操作数字的能力。遗憾的是,对 shell 脚本来说,这个处理过程会比较麻烦。在 shell 脚本中有两种途径来进行数学运算。
7.1 expr 命令
expr 对表达式
的格式有几点特殊的要求:
- 出现在
表达式
中的运算符、数字、变量和小括号的左右两边至少要有一个空格,否则会报错。 - 有些特殊符号必须用反斜杠
\
进行转义(屏蔽其特殊含义),比如乘号*
和小括号()
,如果不用\
转义,那么 Shell 会把它们误解为正则表达式中的符号(*
对应通配符,()
对应分组)。 - 使用变量时要加
$
前缀。
[root@bd15-21-131-161 ~]# expr 2 +3 #错误:加号和 3 之前没有空格
expr: syntax error
[root@bd15-21-131-161 ~]# expr 2 + 3 #这样才是正确的
5
[root@bd15-21-131-161 ~]# expr 4 * 5 #错误:乘号没有转义
expr: syntax error
[root@bd15-21-131-161 ~]# expr 4 \* 5 #使用 \ 转义后才是正确的
20
[root@bd15-21-131-161 ~]# expr ( 2 + 3 ) \* 4 #小括号也需要转义
-bash: syntax error near unexpected token `2'
[root@bd15-21-131-161 ~]# expr \( 2 + 3 \) \* 4 #使用 \ 转义后才是正确的
20
[root@bd15-21-131-161 ~]# n=3
[root@bd15-21-131-161 ~]# expr n + 2
expr: non-numeric argument
[root@bd15-21-131-161 ~]# expr $n + 2 #使用变量时要加 $
5
[root@bd15-21-131-161 ~]# m=7
[root@bd15-21-131-161 ~]# expr $m \* \( $n + 5 \)
56
以上是直接使用 expr 命令,计算结果会直接输出,如果你希望将计算结果赋值给变量,那么需要将整个表达式用反引号``
(位于 Tab 键的上方)包围起来,请看下面的例子。
实战详解:
使用 expr 进行数学计算是多么的麻烦呀,需要注意各种细节,工作中不推荐使用。
7.2 使用方括号[ ]
bash shell 为了保持跟 Bourne shell 的兼容而包含了 expr 命令,但它同样也提供了一种更简单的方法来执行数学表达式。在bash 中,在将一个数学运算结果赋给某个变量时,可以用美元符和 方括号($[ operation ] )将数学表达式围起来.
用方括号执行shell数学运算比用expr命令方便很多。这种技术也适用于shell脚本。
需要额外注意的是bash shell数学运算符只支持整数运算。若要进行任何实际的数学计算,这是一个巨大的限制。如下图:
八、退出脚本
迄今为止所有的示例脚本中,我们都是突然停下来的。运行完最后一条命令时,脚本就结束了。其实还有另外一种更优雅的方法可以为脚本划上一个句号。shell 中运行的每个命令都使用 退出状态码 ( exit status )告诉 shell 它已经运行完毕。退出状态码是一个 0 ~ 255 的整数值,在命令结束运行时由命令传给 shell 。可以捕获这个值并在脚本中使用。
8.1 查看退出状态码
Linux 提供了一个专门的变量 $? 来保存上个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用$? 变量。它的值会变成由 shell 所执行的最后一条命令 的退出状态码。
如果命令成功结束,那么它退出的状态码就是 0,如果是失败的,那状态码就是一个非零的正数值。
退出状态码126表明用户没有执行命令的正确权限。
另一个会碰到的常见错误是给某个命令提供了无效参数。
8.2 exit
exit 是一个 Shell 内置命令,用来退出当前 Shell 进程,并返回一个退出状态;使用
$?
可以接收这个退出状态。exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
一般情况下,退出状态为 0 表示成功,退出状态为非 0 表示执行失败(出错)了。
exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
Shell 进程执行出错时,可以根据退出状态来判断具体出现了什么错误,比如打开一个文件时,我们可以指定 1 表示文件不存在,2 表示文件没有读取权限,3 表示文件类型不对。
可以看到,"after exit"
并没有输出,这说明遇到 exit 命令后,test1执行就结束了。
实战详解:
注意,exit 表示退出当前 Shell 进程,我们必须在新进程中运行 test1,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法看到输出结果了。