shell脚本在哪里写(shell脚本的作用)
前言:
本章讨论编写 shell脚本的基础知识。在开始编写自己的shell脚本前,你必须了解的基本概念都在这里。
一、多个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可以在那里查找要执行的命令;
在本文中,我们将用第二种方式将脚本文件的确切位置告诉shell。记住,为了引用当前 目录下的文件,可以在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=10 var2=-57 var3=testing var4="still more testing"
shell脚本会自动决定变量值的数据类型。在脚本的整个生命周期里,shell脚本中定义的变量 会一直保持着它们的值,但在shell脚本结束时会被删除掉。 与系统变量类似,用户变量可通过$引用。
示例截图:
核心解说:
变量每次被引用时,都会输出当前赋给它的值。需要记住的是,引用一个变量值时需要使 用$符,而引用变量来对其进行赋值时则不要使用美元符。看下面的例子。
演示截图:
如果没有使用$符,shell会将变量名解释成普通的文本字符串,通常这并不是你想要的结果。
4.3命令替换
shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋 给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本数据时尤为方便。
两种操作方式:
- 反引号字符(`)
- $()
核心解说:
需要注意反引号字符,这可不是用于字符串的那个普通的单引号字符。由于在shell脚本之外很 少用到,你可能甚至都不知道在键盘什么地方能找到这个字符。但你必须慢慢熟悉它,因为这是 许多shell脚本中的重要组件。提示:在美式键盘上,它通常和波浪线(~)位于同一键位。 命令替换允许你将shell命令的输出赋给变量。尽管这看起来并不那么重要,但它却是脚本编 程中的一个主要组成部分。
如下示例:
用一对反引号把整个命令行命令围起来:
day='date'
使用$()格式:
day=$(date)
shell会运行命令替换符号中的命令,并将其输出赋给变量testing。注意,赋值等号和命令
替换字符之间没有空格。这里有个使用普通的shell命令输出创建变量的例子。
实战详解:
命令替换会创建一个子shell来运行对应的命令。子shell(subshell)是由运行该脚本的shell 所创建出来的一个独立的子shell(child shell)。正因如此,由该子shell所执行命令是无法使用脚本中所创建的变量的。 在命令行提示符下使用路径./运行命令的话,也会创建出子shell;要是运行命令的时候 不加入路径,就不会创建子shell。如果你使用的是内建的shell命令,并不会涉及子shell。 在命令行提示符下运行脚本时一定要留心!
五、重定向输入和输出
很多时候想要保存某个命令的输出而不仅仅只是让它显示在显示器上。bash shell提供了几 个操作符,可以将命令的输出重定向到另一个位置(比如文件)。重定向可以用于输入,也可以 用于输出,可以将文件重定向到命令输入。
5.1 输出重定向
最基本的重定向将命令的输出发送到一个文件中。bash shell用大于号(>)来完成这项功能:
使用格式:
command > outputfile
之前显示器上出现的命令输出会被保存到指定的输出文件中。
重定向操作符创建了一个文件1.txt(通过默认的umask设置),并将echo命令的输出重定向
到该文件中。如果输出文件已经存在了,重定向操作符会用新的文件数据覆盖已有文件。
很多时候我们可能并不想覆盖文件原有内容,而是想要将命令的输出追加到已有文件中,比如在创建一个记录系统上某个操作的日志文件。在这种情况下,可以用双大于号(>>)来追加数据。
可以看到,who命令产生的内容并没有覆盖1.txt中已有的内容,而是追加到文件的末尾。
5.2 输入重定向
输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的
输出重定向到文件。
输入重定向符号是小于号(<):< p="">
命令格式:
command < inputfile
一个简单的记忆方法就是:在命令行上,命令总是在左侧,而重定向符号“指向”数据流动
的方向。小于号说明数据正在从输入文件流向命令。
wc命令可以对对数据中的文本进行计数。默认情况下,它会输出3个值:
- 文本的行数
- 文本的词数
- 文本的字节数
通过将文本文件重定向到wc命令,你立刻就可以得到文件中的行、词和字节的计数。这个例 子说明1.txt文件有2行、11个单词以及83字节。
还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。这种方法 无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以了。乍看一眼, 这可能有点奇怪,但有些应用会用到这种方式。
内联输入重定向符号是远小于号(<<)。除了这个符号,你必须指定一个文本标记来划分输 入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文本标记必须一致。
command << EOF
data
EOF
在命令行上使用内联输入重定向时,shell会用PS2环境变量中定义的次提示符(参见第6章)
来提示输入数据。下面是它的使用情况。
六、管道
通过前面的学习,我们已经知道了怎样从文件重定向输入,以及重定向输出到文件。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 是 evaluate expressions 的缩写,译为“表达式求值”。Shell 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,如果是失败的,那状态码就是一个非零的正数值。
无效命令会返回一个退出状态码127。Linux错误退出状态码没有什么标准可循,但有一些可
用的参考,如下图:
退出状态码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 会话(终端窗口)会被关闭,我们就无法看到输出结果了。