大家好 这是RedHat RHCE考试准备书的翻译。 我认为,关于bash的基础知识非常容易理解。
Shell脚本本身就是一门科学。 在不深入探讨“幕后”发生的所有事情的情况下,您将学习如何使用基本元素来编写自己的脚本,并分析第三方Shell脚本中发生的情况。

了解Shell脚本的基本元素
实际上,shell脚本是顺序执行的命令的列表,以及一些仅允许在特定条件下执行代码的逻辑。
要了解复杂的Shell脚本,建议您从基本脚本开始。
以下是一个非常简单的脚本:
它包含几个应在所有脚本中使用的元素。 对于初学者,有shebang-这是行#!/ Bin / bash。 从父外壳启动脚本时,它将打开一个子外壳,在该子外壳中执行脚本中指定的命令。
这些命令可以以多种方式解释。 为了准确理解它们应该如何解释,使用了shebang。 在上面的示例中,shebang明确指出该脚本必须由bash shell执行。
也可以指示其他外壳。 例如,如果您的脚本包含Perl代码,则shebang应该为#!/ Usr / bin / perl。 与shebang一起启动脚本是一个好习惯。 如果省略,脚本代码将由用于运行脚本的同一shell执行。
在爆炸之后,立即有一部分解释了脚本的含义。 在每个方案的开头都添加一些注释行是一个好主意。 在一个简短的脚本中,通常会很清楚它的作用,但是随着脚本的变长,以及越来越多的人参与编写和支持它,作者的意图变得越来越不清楚。
为避免这种情况,请确保添加以每个#字符开头的注释行。 注释不仅可以在第一行中,而且可以在脚本每个小节的开头。 如果您在几个月后阅读脚本,这肯定会有所帮助!
您不仅可以在小节中评论,还可以在个别行中评论。
无论使用什么位置,从#字符到行尾的所有内容都是注释。
在注释块之后,找到了脚本主体。 在上面的示例中,这些是顺序执行的几个命令。 Shell脚本的主体可能随着其发展而增加。
在脚本的最后,我包括了
exit 0语句。 exit语句告诉父外壳脚本是否成功。 脚本中最后一个命令的退出状态是脚本本身的退出状态,除非在脚本末尾使用了
exit 0 。
知道您可以与
exit一起使用来告诉父shell情况如何是很有用的。
在父shell中引入了echo $?。 允许您查询上一个正在运行的脚本的退出状态。
创建脚本后,请确保可以执行该脚本。 最常见的方法是对其应用运行位。 因此,如果脚本文件名是hello,请使用
chmod + x ./hello命令使其可执行。
该脚本也可以作为bash命令的参数执行。 在这种情况下,输入
bash ./hello来运行hello脚本。 如果脚本作为bash命令的参数运行,则脚本文件不必是可执行文件。
实际上,您可以将脚本存储在任何位置,但是如果要将其存储在$ PATH变量未包含的目录中,则需要在脚本名称前使用./来执行该脚本。
键入
./hello来运行脚本,或将其放在$ PATH变量中包含的标准目录中,例如/ usr / local / bin。
您也可以将脚本放置在/ bin目录中,然后在文件系统中的任何位置输入文件名,脚本就会执行。
例子使用
vi / bin / datetime在/ bin目录中创建一个名为datetime的文件。 将此内容粘贴到创建的文件中:
保存文件后,输入
chmod + x / bin / datetime授予文件执行权限。 例如,使用
cd〜命令更改到主目录,只需输入
datetime 。
例如,转到cd〜home目录,然后输入datetime。
[root@localhost ~]
使用变量和输入
bash脚本不仅仅是顺序执行的命令列表。 脚本的优点之一是它们可以使用变量和输入来使脚本灵活。 在本节中,您将学习如何与他们合作。
使用位置参数
运行脚本时,可以使用参数。 参数是您在脚本命令后加上的所有内容。 可以使用参数使脚本更加灵活。 采取
useradd lisa命令。 在此示例中,命令为
useradd ,其参数
lisa指示需要执行的操作。
作为这样的命令的结果,应该创建一个名为lisa的用户。
在脚本中,第一个参数是
$ 1 ,第二个参数是
$ 2,依此类推,清单1显示了如何使用这些参数。 通过将任何用户名指定为参数来尝试运行此代码。
清单1
参数表示在运行脚本之前输入数据。 在这种情况下,我在参数脚本名称之后指定了
lisa ,
lori和
bob作为参数:
[root@server1 ~]
如果您尝试运行示例代码,您可能会注意到其内容并不完美。 如果在执行清单1中的脚本时使用三个参数,它将很好地工作。 如果仅使用两个参数,则输出第三个参数而没有值$ 3。
如果使用四个参数,则永远不会显示第四个值(将存储在$ 4中)。 因此,如果要使用参数,则最好使用更灵活的方法。
清单2
清单2显示了两个与参数相关的新元素:- $#是一个计数器,显示在运行脚本时使用了多少个参数。
- $ @是运行脚本时使用的所有参数的列表。
要列出运行此脚本时使用的参数,请使用
for循环。 在
for循环中,只要条件为真,就会执行指令。 在这种情况下,
$ @中i的条件表示“对于每个参数”。 每次脚本通过循环时,变量
$ @的值
都会分配给变量
$ i 。
因此,只要有参数,脚本的主体就会执行。
for循环的主体始终以
do开头,并以
done结束 ,并且要执行的命令在这两个关键字之间列出。 因此,示例脚本将使用
echo来显示每个参数的值,并在没有更多可用参数时停止显示。
在这个示例中,让我们尝试清单2中的脚本:- 键入vi参数创建参数文件,并将清单2中脚本的内容复制到该文件。
- 保存文件并使其可执行。
- 运行命令./argument abc 。 您将看到显示三行。
- 运行命令./argument abcdef 。 您将看到除abc外,还将显示de f。
变数
变量是一个标签,用于指示内存中包含特定值的特定位置。 可以使用NAME = value静态定义变量,也可以动态定义变量。 动态定义变量有两种解决方案:
- 在脚本中使用read关键字可从运行脚本的用户请求数据。
- 使用命令替换来使用命令的结果并将其分配给变量。 例如,命令日期+%d-%m-%y以日-月-年格式显示当前日期。 为此,可以使用TODAY = $(date +%d-%m-%y) 。 要替换命令,只需将要使用其结果的命令放在方括号之间。
在上一章有关位置参数的部分中,您学习了如何在运行脚本时将参数分配给变量。 在某些情况下,当您发现缺少实质性内容时,请求信息可能会更有效。 下面的脚本显示了如何执行此操作。
清单3.使用
read命令的示例脚本
在清单3的脚本中,
if ... then ... else ... fi运算符用于测试
$ 1参数的存在。 这是使用
test完成的(test是一个单独的命令)。 测试命令可以通过两种方式编写*:
test或
[...] 。 在此示例中,执行
if [-z $ 1] ...行以查看测试(检查)
-z $ 1 。
*
-实际上是三个来源 (大约翻译器)-z测试检查
$ 1是否存在。 换句话说,
if [-z $ 1]行检查
$ 1是否为空,这意味着在运行此脚本时未提供任何参数。 如果是这样,则执行
then语句之后的命令。
请注意,在使用方括号编写
测试命令时,请务必在左括号和右括号之前使用空格,没有空格的命令将不起作用。
注意
then语句紧跟在
test之后 。 这是可能的,因为使用了分号(;)。 分号是命令分隔符,可以替换脚本中的新行。
then语句执行两个命令:
echo命令(在屏幕上显示消息)和
read命令。
read命令将停止脚本,以便可以处理用户输入并将其存储在TEXT变量中。 因此,
read TEXT将所有用户输入放入TEXT变量,该变量将在脚本的后面使用。
下一部分由
else语句表示 。 在所有其他情况下,
else语句之后的命令
都将执行,在这种情况下,其含义是“否则,如果提供了参数”。 如果是这样,则确定TEXT变量并将
$ 1的当前值分配给它。
注意变量的定义方式:在变量名后紧跟有一个=号,后跟$ 1。 请注意,定义变量时切勿使用空格。
然后,使用
fi运算符关闭if条件。 if条件完成后,您肯定会知道TEXT变量已定义且具有值。 脚本的倒数第二行读取TEXT变量的值,并使用
echo命令将此值映射到STDOUT。 请注意,要请求变量的当前值,它引用变量的名称,以其前面的$符号开头。
您可以在使用输入时练习使用此示例。- 打开编辑器并创建一个名为text的文件。 将清单3中的代码内容输入此文件。
- 将文件写入磁盘并执行chmod + x文本以使其可执行。
- 通过运行./text且不使用其他参数来运行脚本。 您将看到它要求输入。
- 使用“ hello ”作为参数运行脚本(./text hello)。 结果将在STDOUT中显示“您已输入文字问候”。
使用条件和循环
如您所见,条件语句可以在脚本中使用。 这些条件语句仅在满足特定条件时才执行。
bash中经常使用一些条件语句和循环。
- if ... then ... else-如果满足特定条件,则用于执行代码
- for-用于执行一系列值的命令
- while-用于在满足特定条件时执行代码
- 之前 -用于执行代码直到满足特定条件
- 大小写 -用于评估有限数量的特定值
如果那样的话
if then else构造通常用于评估特定条件。 您已经看到了他的榜样。 此条件语句通常与
test命令一起使用。 此命令使您可以检查许多事情:例如,不仅文件是否存在,而且还比较文件,比较整数等等。
您可以在参考手册中使用命令man test来了解有关test的更多信息。
基本的
if构造是
if ... then ... fi 。
它比较一个条件,如以下示例所示:
if [ -z $1 ] then echo no value provided fi
在清单3中,您了解了如何评估两个条件,包括表达式中的
其他条件。 清单4显示了如何评估从
if到
else的几个条件。 如果您需要检查许多不同的值,这将很有用。
请注意,此示例还使用了几个
测试命令。
清单4 。
如果然后否则的示例
|| 和&&
您可以使用逻辑运算符
||代替编写complete
if ...语句。 以及
&& 。
|| 是逻辑“或”,并且仅在第一部分不为真时才执行语句的第二部分;
&&是逻辑“ AND”,仅在第一部分为true时才执行语句的第二部分。
考虑以下两行: [ -z $1 ] && echo no argument provided
ping -c 1 8.8.8.8 2>/dev/null || echo node is not available
第一个示例检查
$ 1是否为空。 如果此检查正确(基本上意味着该命令以退出代码0结尾),则执行第二个命令。
在第二个示例中,使用
ping命令检查主机可用性。
在
ping命令失败的情况下,此示例使用逻辑“或”显示文本“节点不可用”。
您会经常发现这种情况,而不是有条件的
if语句 ,
&&和
||。 。 在下面的练习中,您可以练习使用条件语句,方法是使用
if ... then ... else或
&&和
||。 。
锻炼身体 。 使用
if ... then ... else在本练习中,您将处理一个脚本,该脚本检查什么是文件和什么是目录。
- 启动编辑器并创建一个名为filechk的脚本。
- 将清单4的内容复制到此脚本中。
- 用它运行几个测试,例如./filechk / etc / hosts , ./filechck/usr 、. / filechk non-existing-file 。
对于循环
for循环是处理数据范围的绝佳解决方案。 在清单5中,您可以看到带有的第一个示例,其中在该范围中有原始值的情况下确定并处理了范围。
清单5
for循环始终以for关键字开头,后跟需要检查的条件。 后面紧跟着
do关键字,接着是必须执行的命令,如果条件为true,则循环使用
done关键字终止。
在清单5的示例中,您可以看到条件是分配给COUNTER变量的括号内的数字范围。
一点解释内部
((...))算术表达式被计算并返回其结果。 例如,在最简单的情况下,结构a = $((5 + 3))将为变量“ a”分配表达式“ 5 + 3”或8的值。此外,双括号允许使用C语言风格的变量。
首先,将变量初始化为100,只要该值大于1,就在每次迭代中将其减去1,只要条件为真,就使用
echo命令显示$ COUNTER变量的值。
在清单6中,您可以看到带有的我最喜欢的单行代码
之一 。 这次将范围定义为一个数字序列,从100开始到104。
清单6 for i in {100..104}; do ping –c 1 192.168.4.$i >/dev/null && echo 192.168.4.$i is up; done
请注意如何确定范围:首先指定第一个数字,然后指定两个点并指示范围中的最后一个数字。 此外,
对于i in,对于这些数字中的每一个,
都分配变量
i 。 这些数字中的每一个都分配给变量
i ,然后执行
ping命令,其中
-c 1选项确保仅发送一个请求。
ping命令的结果未考虑在内,因此其输出重定向到/ dev / null。 根据
ping命令的输出状态,执行
&&表达式的一部分。 因此,如果主机可用,则会显示一行以表明主机正在运行。
了解何时和直到
如果您刚刚阅读的for语句对于处理元素范围非常有用,那么当您要跟踪诸如流程可访问性之类的内容时,
while语句将非常有用。 还有一条
直到语句,只要所检查的条件为假,就执行该语句。 在清单7中,您可以了解如何使用
while来监视流程活动。
注意事项 我不明白该脚本的作用。 就我而言,使用CentOS 7,默认情况下没有监视器,尽管脚本明确指出: 用法:监视<进程名>
在某个地方呆了半个小时,我用Google搜索了CetOS的监控程序,但没有找到。 通常,如果使用ps aux,则不清楚这是什么侧面监视器。 无论如何,我都不了解该脚本的作用。 帮助解决此问题的一大要求是调整文本和/或脚本。
清单7
清单7中的脚本由两部分组成。 首先,有一个
while循环 。 其次,当
while循环不再被评估为true时,需要做的所有事情。
while循环的核心是
ps命令,其值为
$ 1 。
注意使用了
grep -v grep ,它从结果中排除了包含
grep命令的行。 请记住,
ps命令将包括
ps命令输出传递到的所有正在运行的进程,包括
grep命令 。 这可能会导致误判匹配。
ps aux命令的输出重定向到/ dev / tty11。 这使您可以在以后从tty11中读取结果,但默认情况下不会显示它们。
while语句后面是如果要检查的条件为true则必须执行的命令。在这种情况下,它是sleep 5命令,它将脚本执行暂停5秒钟。只要while语句的条件为true,循环就继续执行。如果条件为假(在这种情况下意味着该过程不再可用),则循环停止,并且可以执行其后的命令。您应该熟悉所有这些命令,除了最后一个。在行-s中,“进程$ 1已停止”根<。邮件使用内部邮件系统发送给root用户,该内部邮件系统默认在Linux *上运行。邮件命令将使用-s选项指定的消息主题作为第一个参数。*-至少在CentOS上默认情况下有效。 (在翻译器中)注意<。在团队结束时。通常,在交互模式下使用mail命令时,会打开一个编辑器,您可以在其中编写消息正文。该编辑器已关闭,提供的行只有一个句点。在此命令中,通过重定向STDIN提供一个点。这样就可以在无需任何其他用户活动要求的情况下处理消息。周期而 -该周期的相反直到,它的一个例子示于表8。直到开始一个迭代,直到条件变为真为止。在清单8中,它用于通过$ 1的出现来过滤users命令的输出,$ 1将是用户名。在此命令正确之前,迭代将继续。在用户输出中找到用户名后,迭代将关闭,直到直到循环之后,其余命令才会执行。清单8
了解案例
最后一个重要的迭代循环是大小写 *。操作员的情况下被用来评估许多预期值。特别是,case语句在Linux启动脚本中很重要,在以前的版本中,case语句用于启动服务。*-这是一个周期吗?在case语句中,定义所需的每个具体参数,如果使用了该参数,则定义要执行的命令。在清单9中,您可以看到case语句,该语句在较早的版本中用于运行几乎所有服务。清单9 case "$1" in start) start;; stop) rm -f $lockfile stop;; restart) restart;; reload) reload;; status) status ;; *) echo "Usage: $0 (start|stop|restart|reload|status)" ;; esac
案件有几个特点。首先是顺序情况。接下来是需要评估的所有可能值的列表。每个元素用括号封闭)。如果使用了特定参数,则后面是要执行的命令列表。命令列表以双分号;;结束。可以在最后一条命令之后立即使用,并且可以在单独的行上使用。另请注意,*)适用于先前未指定的所有其他参数。这是一个无所不包的运营商。案例迭代循环以esac语句结束。注意,以防万一的序列按顺序执行。进行第一个匹配时,case语句将不评估任何内容。作为评估的一部分,可以使用与模板相似的模板。这显示在*)匹配所有内容的序列中。但是,您也可以使用诸如start | Start | START)之类的序列来匹配另一种情况。在Bash中调试脚本
当脚本无法满足您的期望时,进行一些调试很有用。首先,尝试将其作为bash -x命令的参数执行。这将逐行向您显示脚本尝试执行的操作,以及如果脚本无法正常运行的情况下的具体错误。清单10显示了一个使用bash -x的示例,在该示例中,很明显grep命令不知道应该做什么,因为缺少用于其操作的参数。 [root@server1 ~]
总结一下
在本文中,您学习了如何编写Shell脚本。您已经看了几个示例,现在熟悉了创建成功脚本所需的一些基本元素。