Shell命令和Shell脚本

在本学期的课程中,您已经看到了许多使用shell发出命令的示例。在Unix系统中,有许多强大的工具可以通过shell使用,而我们才刚刚开始利用shell和实用程序的强大功能。shell的另一个强大方面是shell脚本,它允许我们用shell脚本语言编写程序来自动执行常见任务。

在这些笔记中,我将引导您完成一个简单shell脚本的开发过程。在此过程中,我们将学习shell脚本编写的基础知识,并介绍一些功能强大的实用程序。

这个问题

假设您是Unix系统的系统管理员。在您的系统上运行的赢博体育程序之一是我们一直在开发的文件夹监视系统。管理员必须对该系统执行的一个常见维护任务是添加或删除监视文件夹。当然,管理员总是可以通过编辑/etc/fwd.conf文件,停止fwd赢博体育程序,然后重新启动它来做到这一点。或者,一个熟练的系统管理员可以构建一个脚本来自动执行这些任务,使得只需发出一个命令就可以完成赢博体育这些事情。

开始

在其最简单的形式中,向fwd系统添加一个新的监视文件夹的问题涉及到向配置文件添加一行文本。我们可以尝试在shell中输入以下命令来完成此操作

“newTag /path/to/new/folder” > /etc/fwd.conf

这有两个问题。首先,当您在shell中键入一行时,shell将尝试将您键入的内容解释为命令。这将产生一条错误消息:

bash: newTag /path/to/new/folder:没有这样的文件或目录

我们这里需要的是一个将生成所需文本的命令。然后,>操作符将把该命令的输出重定向到配置文件。echo命令就是我们需要做的:

> /etc/fwd.conf . echo "newTag /path/to/new/folder

这也不完全正确,因为重定向操作符>最终用我们想要添加的一行替换了相关文件的整个内容。这里我们需要使用替代操作符>>,它将文本附加到文件中,而不是清除之前的内容。

echo "newTag /path/to/new/folder" >> /etc/fwd.conf

这将达到我们的目的,但可能是一个不完整的解决方案。这可能无法解决的一个问题是,我们在这里尝试使用的标记可能已经在配置文件中使用了。我们应该从搜索现有的配置文件开始,看看是不是这样。强大的grep函数在这里是我们的朋友。Grep可以在文件中搜索您提供的模式,然后打印文件中出现该模式的赢博体育行。

grep newTag /etc/fwd.conf

这将起作用,但可能产生比我们想要的更多的输出。这个命令打印fwd.conf中出现newTag的赢博体育行。我们真正想看到的是newTag出现在行首的赢博体育行。为此,我们可以使用包含特殊符号^的模式来运行搜索。以^开头的模式将只返回newTag出现在该行开头的行:

grep "^newTag " /etc/fwd.conf

这里的搜索模式还包括newTag后面的空格,这样我们就不会意外捕获以newTag1或newTag52这样的标记开头的行。

我们的第一个简单脚本

现在我们已经开发了两个有用的shell命令来帮助完成我们的任务,现在是时候开始构建一个脚本来运行这些命令了。

下面是我们的第一个简单脚本simple.sh的文本。

#!/bin/bash exists=$(grep "^$1 " /etc/fwd.conf) if [-z $exists]则echo "$1 $2" >> /etc/fwd.conf kill -2 $(cat /run/fwd.pid) /opt/fwd/fwd else echo "Tag $1 already exists

在这段代码中有一些需要注意的事情:

1. 脚本的第一行

#!/bin/bash

指定我们希望在哪个shell程序中运行该脚本。许多Unix系统安装了几个不同的shell程序,这些可选shell中的每一个都有自己的shell脚本语法。为了确保脚本能够正确运行,我们必须指定在哪个shell程序中运行脚本。bash shell是最流行和最广泛使用的shell之一,您可以在网上找到关于如何使用bash脚本语言的大量文档和信息。

2. 在脚本的不同地方,您将看到表达式$1和$2。这些是变量评估。这里讨论的变量是命令行变量,其值是用户在运行脚本时提供的命令行参数。例如,如果用户通过运行命令调用此脚本

./first.sh newTag /path/to/new/folder

然后$1将计算为newTag, $2将计算为/path/to/new/folder。

3. 脚本中的下一行设置一个存在的变量。脚本中的变量可以包含文本或整数。在大多数情况下,我们设置变量来保存文本。这个特定变量的值来自于所谓的命令替换。表达式$( )运行命令 ,然后求值为该命令将打印的文本。在本例中,我们运行的命令是在/etc/fwd.conf文件中执行grep搜索,查找以我们感兴趣的标记开头的行。如果搜索找到一行,则表达式将求值为该行的文本。如果搜索没有找到行,则表达式将计算为空字符串。当shell计算命令替换表达式时,它将首先用它们的值替换命令中的任何变量计算,然后运行命令。

4. 该脚本还包含一个简单的if/else语句示例。if/else语句中的测试由条件提供。我们在这里使用的条件[-z $exists]检查存储在exists变量中的字符串是否为空。如果是,这意味着我们想要插入的新标记还不存在,将它添加到fwd.conf文件中是安全的。如果exists变量不为空,我们只想打印一条错误消息。您可以在本页阅读更多关于bash脚本中的条件的信息。

5. 在成功更新配置文件之后,我们需要重新启动fwd服务器。为此,我们需要使用fwd赢博体育程序的pid运行kill命令。为了获得pid,我们使用命令替换,运行cat命令来打印/run/fwd文件的内容。pid文件。

运行脚本

在运行刚才创建的脚本之前,我们必须执行一个额外的步骤。要运行脚本文件,我们必须使该文件可执行。我们通过在终端上运行chmod命令来改变文件的权限:

Chmod +x simple.sh

这将向文件添加执行权限。要运行我们的脚本,我们将调用与调用任何其他程序相同的方式:

./simple.sh newTag /path/to/folder

改进的脚本

现在我们有了一个脚本,它完成了我们需要做的大部分工作,我们应该继续添加一些额外的功能,使它更正确。特别是,我们想要添加这些功能:

  1. 在现有版本中,如果用户试图添加一个带有已在使用的标记的条目,脚本将拒绝执行此操作。对于这种情况,一个更加用户友好的选项是为用户提供用新条目替换现有条目的选项。
  2. 同样,检查用户想要观看的路径是否已经被观看也是有意义的。如果是这种情况,我们希望询问用户是想保留现有条目还是用新条目替换它。

这里是脚本的扩展版本,增加了这些功能:

#!/bin/bash tag=$(grep -w "^$1" /etc/fwd.conf) path=$(grep -w "$2\$" /etc/fwd.conf) restart="true" if [-n "$tag"] then read -p " tag $1已经存在。替换它吗?[y/n]" yn if [yn != "n"] then rest=$(grep -v "^$1" /etc/fwd.conf) echo $rest > /etc/fwd.conf echo "$1 $2" >> /etc/fwd.conf else restart="false" fi else if [-n "$path“] then read -p ”路径$2已经存在。替换它吗?[y/n]" yn if [yn != "n"] then rest=$(grep -v "$ 2\$" /etc/fwd.conf) echo $rest > /etc/fwd.conf echo "$1 $2" >> /etc/fwd.conf else restart="false" else echo "$1 $2" >> /etc/fwd.conf fi fi if [$restart == "true"] then kill -2 $(cat /run/fwd.pid) /opt/fwd/fwd fi

这个版本的脚本首先使用grep执行两次单独的搜索。第一次搜索查找使用与$1相同标记的条目,而第二次搜索查找以与$2相同路径结束的行。在grep模式的末尾加上$,就会发现该模式出现在一行的末尾。由于$字符在shell脚本中具有特殊的含义,我们必须将该字符转义为\$以便在grep命令中使用它。

如果我们发现用户想要添加的标签已经存在,我们将不得不询问用户是否想要替换它。为此,我们使用read命令向用户请求输入,然后将用户的响应放入yn变量中。

如果用户想要替换使用相同标记的现有条目,那么我们必须从文件中获取除该行以外的每一行。要做到这一点,我们可以使用grep中的-v选项,它将返回文件中除了匹配模式的行之外的每一行。

报告生成脚本

shell脚本的另一个常见用途是生成汇总来自数据集合(如日志文件)的数据的报告。在下一个示例中,我们将构造一个脚本,该脚本给出fwd.log文件中记录了哪些文件的更改以及这些文件被修改了多少次的摘要。例如,如果fwd.log文件包含以下条目

A 1 8980 b 5 9002 A 2 9022 A 2 9025 A 1 9067 A 1 9077 b 3 9234 b 3 9344 b 5 9546 b 5 9607 b 5 9714 A 2 9898

报告脚本将生成如下所示的报告:

A 1 3 A 2 3 b 5 4 b 3 2

在下面的注释中,我将开发一个脚本,它可以从fwd.log文件生成这个报告。我将开始开发这个脚本的过程,然后把剩下的工作留给您作为家庭作业。

我们的脚本中一个明显的组成部分是一个循环,它可以遍历fwd.log文件的内容。我们的第一个简单示例脚本演示了如何编写一个简单的循环来读取输入行。

#!/bin/bash
while read line
do
  echo "$line" >> output.txt
done

这个例子演示了bash while循环的用法。控制这个循环的命令是

读取一行

它调用bash read命令从标准输入中读取一行输入,然后将该行存储在名为line的变量中。在循环体中,我们读取每一行并将其附加到名为output.txt的文本文件中。

当您运行这个脚本时,它将读取您输入到终端中的文本行。当按下control-d组合键终止读取时,while循环将停止。运行脚本后,您可以打开output.txt文件,看到您输入的文本已经存储在文件中。

在下一个例子中,我们将构造一个类似的while循环。这次的不同之处在于,我们将使用重定向来让while循环从文件中读取,而不是从标准输入中读取。

#!/bin/bash
while read line
do
  echo "$line"
done < fwd.log

while循环结束后的表达式< fwd.log是一个文件重定向,它将fwd.log文件的内容重定向到while循环中,以便read命令将从该文件读取,而不是从标准输入读取。当您运行这个脚本时,它将把fwd.log文件的内容打印到输出中。

下一个示例演示了使用read命令可以实现的一个技巧。在上一个示例中,我们使用read命令从输入文件中读取一行文本到line变量中。在本例中,我们将把日志文件每行中出现的三个内容读入三个单独的变量中。

#!/bin/bash当读取标签文件时间做echo "$tag $file" done < fwd.log

当您运行这个脚本时,它将只将日志文件的前两列打印到控制台。

家庭作业

至此,您已经开始构建我们想要构建的报告脚本了。现在,我将把脚本的其余部分留给你们作为家庭作业。下面是你的脚本应该做的一些额外的事情:

  1. 脚本应该首先使用sort命令对fwd.log文件的内容进行排序。使用重定向将排序命令的输出转储到文本文件。
  2. 使用我在上一个示例中设置的循环来遍历在第1步中创建的文本文件的行。
  3. 向循环中添加逻辑,使您可以计算看到标记名和文件名的相同组合的次数。一旦您不再看到具有特定标记名和文件名组合的条目,您就应该将该标记名和文件名打印到控制台,并显示其出现的次数。
  4. 要收集循环中需要的计数,需要设置一个计数器变量来存储整数计数。您需要在网上做一些研究,了解如何在bash脚本中增加整数计数器。