Bash Shell简介

大家好 这是RedHat RHCE考试准备书的翻译。 我认为,关于bash的基础知识非常容易理解。

Shell脚本本身就是一门科学。 在不深入探讨“幕后”发生的所有事情的情况下,您将学习如何使用基本元素来编写自己的脚本,并分析第三方Shell脚本中发生的情况。



了解Shell脚本的基本元素


实际上,shell脚本是顺序执行的命令的列表,以及一些仅允许在特定条件下执行代码的逻辑。

要了解复杂的Shell脚本,建议您从基本脚本开始。

以下是一个非常简单的脚本:

#!/bin/bash # # #This is a script that greets the world # Usage: ./hello clear echo hello world exit 0 

它包含几个应在所有脚本中使用的元素。 对于初学者,有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的文件。 将此内容粘贴到创建的文件中:

 #!/bin/bash #    .     ,      date who 

保存文件后,输入chmod + x / bin / datetime授予文件执行权限。 例如,使用cd〜命令更改到主目录,只需输入datetime

例如,转到cd〜home目录,然后输入datetime。

 [root@localhost ~]# datetime Sat Sep 28 00:33:41 EDT 2019 root tty1 2019-09-25 20:28 root pts/0 2019-09-27 20:07 (comp.corp.domain.ru) 

使用变量和输入


bash脚本不仅仅是顺序执行的命令列表。 脚本的优点之一是它们可以使用变量和输入来使脚本灵活。 在本节中,您将学习如何与他们合作。

使用位置参数


运行脚本时,可以使用参数。 参数是您在脚本命令后加上的所有内容。 可以使用参数使脚本更加灵活。 采取useradd lisa命令。 在此示例中,命令为useradd ,其参数lisa指示需要执行的操作。

作为这样的命令的结果,应该创建一个名为lisa的用户。

在脚本中,第一个参数是$ 1 ,第二个参数是$ 2,依此类推,清单1显示了如何使用这些参数。 通过将任何用户名指定为参数来尝试运行此代码。

清单1

 #!/bin/bash # run this script with a few arguments echo The first argument is $1 echo The second argument is $2 echo The third argument is $3 

参数表示在运行脚本之前输入数据。 在这种情况下,我在参数脚本名称之后指定了lisaloribob作为参数:

 [root@server1 ~]# ./argument lisa lori bob The first argument is lisa The second argument is lori The third argument is bob [root@server1 ~]# 

如果您尝试运行示例代码,您可能会注意到其内容并不完美。 如果在执行清单1中的脚本时使用三个参数,它将很好地工作。 如果仅使用两个参数,则输出第三个参数而没有值$ 3。

如果使用四个参数,则永远不会显示第四个值(将存储在$ 4中)。 因此,如果要使用参数,则最好使用更灵活的方法。

清单2

 #!/bin/bash # run this script with a few arguments echo you have entered $# arguments for i in $@ do echo $i done exit 0 

清单2显示了两个与参数相关的新元素:

  • $#是一个计数器,显示在运行脚本时使用了多少个参数。
  • $ @是运行脚本时使用的所有参数的列表。

要列出运行此脚本时使用的参数,请使用for循环。 在for循环中,只要条件为真,就会执行指令。 在这种情况下, $ @中i的条件表示“对于每个参数”。 每次脚本通过循环时,变量$ @的值都会分配给变量$ i

因此,只要有参数,脚本的主体就会执行。

for循环的主体始终以do开头,并以done结束 ,并且要执行的命令在这两个关键字之间列出。 因此,示例脚本将使用echo来显示每个参数的值,并在没有更多可用参数时停止显示。

在这个示例中,让我们尝试清单2中的脚本:

  1. 键入vi参数创建参数文件,并将清单2中脚本的内容复制到该文件。
  2. 保存文件并使其可执行。
  3. 运行命令./argument abc 。 您将看到显示三行。
  4. 运行命令./argument abcdef 。 您将看到除abc外,还将显示de f。

变数


变量是一个标签,用于指示内存中包含特定值的特定位置。 可以使用NAME = value静态定义变量,也可以动态定义变量。 动态定义变量有两种解决方案:

  • 在脚本中使用read关键字可从运行脚本的用户请求数据。
  • 使用命令替换来使用命令的结果并将其分配给变量。 例如,命令日期+%d-%m-%y以日-月-年格式显示当前日期。 为此,可以使用TODAY = $(date +%d-%m-%y) 。 要替换命令,只需将要使用其结果的命令放在方括号之间。

在上一章有关位置参数的部分中,您学习了如何在运行脚本时将参数分配给变量。 在某些情况下,当您发现缺少实质性内容时,请求信息可能会更有效。 下面的脚本显示了如何执行此操作。

清单3.使用read命令的示例脚本

 #!/bin/bash if [ -z $1 ]; then echo enter a text read TEXT else TEXT=$1 fi echo you have entered the text $TEXT exit 0 

在清单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。 请注意,要请求变量的当前值,它引用变量的名称,以其前面的$符号开头。

您可以在使用输入时练习使用此示例。

  1. 打开编辑器并创建一个名为text的文件。 将清单3中的代码内容输入此文件。
  2. 将文件写入磁盘并执行chmod + x文本以使其可执行。
  3. 通过运行./text且不使用其他参数来运行脚本。 您将看到它要求输入。
  4. 使用“ 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显示了如何评估从ifelse的几个条件。 如果您需要检查许多不同的值,这将很有用。

请注意,此示例还使用了几个测试命令。

清单4如果然后否则的示例

 #!/bin/bash # run this script with one argument # the goal is to find out if the argument is a file or a directory if [ -f $1 ] then echo "$1 is a file" elif [ -d $1 ] then echo "$1 is a directory" else echo "I do not know what \$1 is" fi exit 0 

|| 和&&


您可以使用逻辑运算符||代替编写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

在本练习中,您将处理一个脚本,该脚本检查什么是文件和什么是目录。

  1. 启动编辑器并创建一个名为filechk的脚本。
  2. 将清单4的内容复制到此脚本中。
  3. 用它运行几个测试,例如./filechk / etc / hosts./filechck/usr 、. / filechk non-existing-file

对于循环


for循环是处理数据范围的绝佳解决方案。 在清单5中,您可以看到带有的第一个示例,其中在该范围中有原始值的情况下确定并处理了范围。

清单5

 #!/bin/bash # for (( COUNTER=100; COUNTER>1; COUNTER-- )); do echo $COUNTER done exit 0 

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

 #!/bin/bash # # usage: monitor <processname> while ps aux | grep $1 | grep -v grep > /dev/tty11 do sleep 5 done clear echo your process has stopped logger $1 is no longer present mail -s "process $1 has stopped" root < . 

清单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

 #!/bin/bash # until users | grep $1 > /dev/null do echo $1 is not logged in yet sleep 5 done echo $1 has just logged in mail -s "$1 has just logged in" root < . 

了解案例


最后一个重要的迭代循环是大小写 *。操作员的情况下被用来评估许多预期值。特别是,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 ~]# bash -x 319.sh + grep Usage: grep [OPTION]... PATTERN [FILE]... Try 'grep --help' for more information. + users + echo is not logged in yet is not logged in yet + sleep 5 

总结一下


在本文中,您学习了如何编写Shell脚本。您已经看了几个示例,现在熟悉了创建成功脚本所需的一些基本元素。

Source: https://habr.com/ru/post/zh-CN471242/


All Articles