Skip to content

Latest commit

 

History

History
572 lines (352 loc) · 18.4 KB

File metadata and controls

572 lines (352 loc) · 18.4 KB

十一、正则表达式

在本章中,我们将讨论使用流编辑器(sed)和 AWK 最神秘的部分。 它们是正则表达式,简称 regex。 在前面的章节中,我们害羞地讨论了一些正则表达式,这是因为我们不需要深入了解它们。

如果您了解如何编写正则表达式,您将节省大量的时间和精力。 使用正则表达式,您将释放 sed 和 AWK 背后的真正力量,并将专业地使用它们。

本章将涵盖以下几个方面:

  • 正则表达式引擎
  • 定义 BRE 模式
  • 定义在模式
  • 使用grep

技术要求

本章的源代码可在此下载:

https://github.com/PacktPublishing/Mastering-Linux-Shell-Scripting-Second-Edition/tree/master/Chapter11

正则表达式引擎

首先,什么是正则表达式?

正则表达式是正则表达式引擎解释为匹配特定文本的字符串。 这就像是一种高级的搜索方式。

假设您想搜索文件中以任何小写字母开头的行,或者您想搜索包含数字的行,或者搜索以特定文本开头的行。 普通的搜索不能是泛型的:唯一的方法就是使用正则表达式。

什么是正则表达式引擎?

regex 引擎是一种软件,它能够理解这些字符串并翻译它们以找到匹配的文本。

有很多正则表达式引擎; 例如,Java、Perl 和 Python 等编程语言附带的引擎。 另外,Linux 工具使用的引擎是 sed 和 AWK,现在对我们来说重要的事情是学习 Linux 中 regex 引擎的类型。

在 Linux 中有两种 regex 引擎:

  • 基本正则表达式(BRE)引擎
  • 扩展正则表达式(ERE)引擎

大多数 Linux 二进制程序都理解这两种引擎,比如 sed 和 AWK。

grep也可以理解 ERE,但必须使用-E选项,相当于使用egrep

我们将看到如何为 sed 和 AWK 定义正则表达式模式。 我们先来定义 BRE 模式,让我们开始吧。

定义 BRE 模式

要定义一个正则表达式模式,你可以输入以下内容:

$ echo "Welcome to shell scripting" | sed -n '/shell/p'
$ echo "Welcome to shell scripting" | awk '/shell/{print $0}'

关于正则表达式模式,你需要知道的一件非常重要的事情是,它们通常是大小写敏感的:

$ echo "Welcome to shell scripting" | awk '/shell/{print $0}'
$ echo "Welcome to SHELL scripting" | awk '/shell/{print $0}'

假设你想匹配以下任何一个字符:

.*[]^${}\+?|()

必须用反斜杠对它们进行转义,因为这些字符是正则表达式引擎的特殊字符。

现在您知道如何定义 BRE 模式了。 让我们使用普通的 BRE 字符。

锚的角色

锚字符用于匹配行首或行尾。 有两个锚点字符:插入符号(^)和美元符号($)。

插入符号用于匹配一行的开头:

$ echo "Welcome to shell scripting" | awk '/^Welcome/{print $0}'
$ echo "SHELL scripting" | awk '/^Welcome/{print $0}'
$ echo "Welcome to shell scripting" | sed -n '/^Welcome/p'

因此,插入符号用于检查指定的文本是否在行首。

如果你想搜索插入符号作为一个字符,如果你使用 AWK,你应该用反斜杠转义它。

然而,如果你使用sed,你不需要逃避它:

$ echo "Welcome ^ is a test" | awk '/\^/{print $0}'
$ echo "Welcome ^ to shell scripting" | sed -n '/^/p'

要匹配文本结尾,可以使用美元符号字符($):

$ echo "Welcome to shell scripting" | awk '/scripting$/{print $0}'
$ echo "Welcome to shell scripting" | sed -n '/scripting$/p'

您可以在同一模式中同时使用字符(^)和字符($)来指定文本。

你可以使用这些字符做一些有用的事情,比如搜索空行并修改它们:

$ awk '!/^$/{print $0}' myfile

感叹号(!)被称为否定字符,它否定它后面的内容。

脱字符号的模式搜索^$(^)指一行的开头和美元符号($)指的是一条线,这意味着寻找行之间没有开头和结尾这意味着空行。 然后我们用感叹号(!)来否定它,以得到其他不为空的行。

让我们将它应用到以下文件:

Lorem Ipsum is simply dummy text .
Lorem Ipsum has been the industry's standard dummy.
It has survived not only five centuries
It is a long established fact that a reader will be distracted.

现在,让我们看看它的魔力:

$ awk '!/^$/{print $0}' myfile

打印的行中没有空行。

点的角色

点字符匹配除新行(\n)以外的任何字符。 让我们对下面的文件使用它:

Welcome to shell scripting.
I love shell scripting.
shell scripting is awesome.

假设我们使用以下命令:

$ awk '/.sh/{print $0}' myfile
$ sed -n '/.sh/p' myfile

该模式匹配任何包含sh的行及其之前的任何文本:

如您所见,它只匹配前两行,因为第三行以sh开头,所以没有匹配第三行。

字符类

我们了解了如何使用点字符匹配任何字符。 如果只想匹配一组特定的字符,该怎么办?

您可以在方括号[]之间传递您想要匹配的字符来匹配它们,这就是字符类。

让我们以以下文件为例:

I love bash scripting.
I hope it works without a crash.
Or I'll smash it.

让我们看看角色职业是如何工作的:

$ awk '/[mbr]ash/{print $0}' myfile
$ sed -n '/[mbr]ash/p' myfile

字符类[mbr]匹配任何后跟 ash 的包含字符,因此匹配这三行。

你可以在一些有用的地方使用它,比如匹配大写或小写字符:

$ echo "Welcome to shell scripting" | awk '/^[Ww]elcome/{print $0}'
$ echo "welcome to shell scripting" | awk '/^[Ww]elcome/{print $0}'

字符类使用插入字符进行反置,如下所示:

$ awk '/[^br]ash/{print $0}' myfile 

在这里,我们匹配任何包含灰分且不以br开始的行。

记住,在方括号外使用插入符号(^)表示一行的开始。

使用字符类,你可以指定你的字符。 如果有很长的字符范围怎么办?

字符的范围

您可以指定在方括号之间匹配的字符范围,如下所示:

[a-d]

这意味着从ad的字符范围,因此包括abcd

让我们使用相同的示例文件:

$ awk '/[a-m]ash/{print $0}' myfile
$ sed -n '/[a-m]ash/p' myfile

选择字符范围am。 第三行包含 ash 之前的r,它不在我们的范围内,所以只有第二行不匹配。

你也可以使用数字范围:

$ awk '/[0-9]/'

这个模式意味着从09是匹配的。

你可以在同一个括号中写入多个范围:

$ awk '/[d-hm-z]ash/{print $0}' myfile $ sed -n '/[d-hm-z]ash/p' myfile

在这个模式中,从dh和从mz被选择,因为第一行包含了 ash 之前的b,只有第一行不匹配。

您可以使用该范围选择所有的大小写字符,如下所示:

$ awk '/[a-zA-Z]/'

特殊字符类

我们了解了如何使用字符类匹配一组字符,然后我们了解了如何使用字符范围匹配一组字符。

实际上,ERE 引擎提供了一些现成的类来匹配一些常见的字符集,如下所示:

| [[:alpha:]] | 匹配任何字母字符 | | [[:upper:]]          | 只匹配 A-Z 大写字母 | | [[:lower:]] | 只匹配 a-z 小写字母 | | [[:alnum:]]          | Matches 0–9, A–Z, or a–z | | [[:blank:]]  | 只匹配空格或选项卡 | | [[:space:]]          | 匹配任何空白字符:空格,Tab, CR | | [[:digit:]] | 09匹配 | | [[:print:]]           | 匹配任何可打印字符 | | [[:punct:]]          | 匹配任何标点字符 |

因此,如果您想匹配大写字符,您可以使用[[:upper:]],它将与字符范围[A-Z]完全一致。

让我们通过下面的示例文件来测试其中一个:

checking special character classes.
This LINE contains upper case.
ALSO this one.

我们将匹配大写字符,看看它是如何工作的:

$ awk '/[[:upper:]]/{print $0}' myfile $ sed -n '/[[:upper:]]/p' myfile

大写特殊类使匹配任何包含大写字母的行变得容易。

星号

星号用于匹配字符或字符类是否存在 0 次或多次。

当搜索一个有多个变体的单词或拼写错误时,这可能是有用的:

$ echo "Checking colors" | awk '/colou*rs/{print $0}' $ echo "Checking colours" | awk '/colou*rs/{print $0}'

如果字符u根本不存在或存在,它将匹配模式。

我们可以利用星号字符和点字符来匹配任意数量的字符。

让我们看看如何在下面的示例文件中使用它们:

This is a sample line
And this is another one
This is one more
Finally, the last line is this

让我们编写一个匹配任何包含单词this及其后内容的行:

$ awk '/this.*/{print $0}' myfile $ sed -n '/ this.*/p' myfile

第四行包含单词this,但是第一行和第三行包含大写的T,因此它不匹配。

第二行包含单词及其后的文本,而第四行包含单词及其后的任何内容,在这两种情况下,星号匹配 0 个或多个实例。

您可以将星号与字符类一起使用,以匹配字符类中存在的任何字符一次或根本不存在。

$ echo "toot" | awk '/t[aeor]*t/{print $0}' $ echo "tent" | awk '/t[aeor]*t/{print $0}' $ echo "tart" | awk '/t[aeor]*t/{print $0}'

第一行包含字符o两次,因此匹配。

第二行包含n字符,该字符在字符类中不存在,因此没有匹配。

第三行包含字符ar,每个字符对应一次,它们存在于字符类中,因此该行也匹配模式。

定义在模式

我们看到了定义 BRE 模式是多么容易。 现在,我们将看到一些更强大的 ERE 模式。

除了 BRE 模式外,ERE 引擎还理解以下模式:

  • 问号
  • 加号
  • 花括号
  • 管字符
  • 表达式分组

默认情况下,AWK 支持 ERE 模式,sed 需要-r来理解这些模式。

问号

问号只匹配前一个字符或字符类 0 或一次:

$ echo "tt" | awk '/to?t/{print $0}' $ echo "tot" | awk '/to?t/{print $0}' $ echo "toot" | awk '/to?t/{print $0}' $ echo "tt" | sed -r -n '/to?t/p' $ echo "tot" | sed -r -n '/to?t/p' $ echo "toot" | sed -r -n '/to?t/p'

在前两个示例中,字符o存在 0 次和一次,而在第三个示例中,它存在两次,这与模式不匹配

同样,你可以在字符类中使用问号:

$ echo "tt" | awk '/t[oa]?t/{print $0}' $ echo "tot" | awk '/t[oa]?t/{print $0}' $ echo "toot" | awk '/t[oa]?t/{print $0}' $ echo "tt" | sed -r -n '/t[oa]?t/p' $ echo "tot" | sed -r -n '/t[oa]?t/p' $ echo "toot" | sed -r -n '/t[oa]?t/p'

第三个例子不匹配是因为它包含了o字符两次。

注意,当在字符类中使用问号时,它不需要在文本中包含所有字符类; 一个就足够通过模式了

的加号

加号与前面的字符或字符类匹配一次或多次,因此它必须至少存在一次:

$ echo "tt" | awk '/to+t/{print $0}' $ echo "tot" | awk '/to+t/{print $0}' $ echo "toot" | awk '/to+t/{print $0}' $ echo "tt" | sed -r -n '/to+t/p' $ echo "tot" | sed -r -n '/to+t/p' $ echo "toot" | sed -r -n '/to+t/p'

第一个示例没有o字符,这就是为什么它是唯一没有匹配的示例。

同样,我们可以在字符类中使用加号:

$ echo "tt" | awk '/t[oa]+t/{print $0}' $ echo "tot" | awk '/t[oa]+t/{print $0}' $ echo "toot" | awk '/t[oa]+t/{print $0} $ echo "tt" | sed -r -n '/t[oa]+t/p' $ echo "tot" | sed -r -n '/t[oa]+t/p' $ echo "toot" | sed -r -n '/t[oa]+t/p'

第一个示例不匹配是因为它根本不包含o字符。

花括号

花括号定义前一个字符或字符类存在的个数:

$ echo "tt" | awk '/to{1}t/{print $0}' $ echo "tot" | awk '/to{1}t/{print $0}' $ echo "toot" | awk '/to{1}t/{print $0}' $ echo "tt" | sed -r -n '/to{1}t/p' $ echo "tot" | sed -r -n '/to{1}t/p' $ echo "toot" | sed -r -n '/to{1}t/p'

第三个例子不包含任何匹配,因为o字符存在两次。 那么,如果你想指定一个更灵活的数字呢?

你可以在花括号内指定一个范围:

$ echo "toot" | awk '/to{1,2}t/{print $0}' $ echo "toot" | sed -r -n '/to{1,2}t/p'

这里,如果o字符存在一次或两次,我们将匹配它。

同样,你也可以在字符类中使用花括号:

$ echo "tt" | awk '/t[oa]{1}t/{print $0}' $ echo "tot" | awk '/t[oa]{1}t/{print $0}' $ echo "toot" | awk '/t[oa]{1}t/{print $0}' $ echo "tt" | sed -r -n '/t[oa]{1}t/p' $ echo "tot" | sed -r -n '/t[oa]{1}t/p' $ echo "toot" | sed -r -n '/t[oa]{1}t/p'

正如所料,如果任何字符[oa]存在一次,模式将匹配。

管道字符

管道字符(|)告诉 regex 引擎匹配任何传递的字符串。 所以,如果其中一个存在,这就足够让模式匹配。 它就像在传递的字符串之间的逻辑OR:

$ echo "welcome to shell scripting" | awk '/Linux|bash|shell/{print $0}' $ echo "welcome to bash scripting" | awk '/Linux|bash|shell/{print $0}' $ echo "welcome to Linux scripting" | awk '/Linux|bash|shell/{print $0}' $ echo "welcome to shell scripting" | sed -r -n '/Linux|bash|shell/p' $ echo "welcome to bash scripting" | sed -r -n '/Linux|bash|shell/p' $ echo "welcome to Linux scripting" | sed -r -n '/Linux|bash|shell/p'

前面的所有示例都有一个匹配,因为这三个单词中的任何一个都存在于每个示例中。

在管道和单词之间没有空格。

表达式分组

您可以使用括号()将字符或单词分组,使它们在正则表达式引擎中成为一体:

$ echo "welcome to shell scripting" | awk '/(shell scripting)/{print $0}' $ echo "welcome to bash scripting" | awk '/(shell scripting)/{print $0}' $ echo "welcome to shell scripting" | sed -r -n '/(shell scripting)/p' $ echo "welcome to bash scripting" | sed -r -n '/(shell scripting)/p'

由于shell scripting字符串与括号组合在一起,因此它将被视为一个单独的片段。

因此,如果整个句子不存在,模式将失败。

你可能已经意识到,你可以在没有括号的情况下实现这一点:

$ echo "welcome to shell scripting" | sed -r -n '/shell scripting/p'

那么,使用括号或表达式分组的好处是什么呢? 检查下面的例子来了解它们的区别。

你可以使用任何带有分组括号的 ERE 字符:

$ echo "welcome to shell scripting" | awk '/(bash scripting)?/{print $0}' $ echo "welcome to shell scripting" | awk '/(bash scripting)+/{print $0}' $ echo "welcome to shell scripting" | sed -r -n '/(bash scripting)?/p' $ echo "welcome to shell scripting" | sed -r -n '/(bash scripting)+/p' 

在第一个例子中,我们使用问号对整个句子bash scripting进行零次或一次搜索,因为整个句子不存在,所以模式成功。

如果没有表达式分组,就不会得到相同的结果。

使用 grep

如果我们想恰当地谈论grep,整本书是不够的。 grep支持 BRE 和 ERE 等多种发动机。 它支持诸如perl 兼容的正则表达式(PCRE)之类的引擎。

grep是一个非常强大的工具,大多数系统管理员每天都在使用。 我们只是想说明使用 BRE 和 ERE 模式的要点,就像使用 sed 和 AWK 一样。

grep工具默认理解 BRE 模式,如果你想使用 ERE 模式,你应该使用-E选项。

让我们使用以下示例文件并使用 BRE 模式:

Welcome to shell scripting.
love shell scripting.
shell scripting is awesome.

让我们来测试一下 BRE 模式:

$ grep '.sh' myfile

结果显示为红色。

让我们测试一个 ERE 模式:

$ grep -E 'to+' myfile

所有其他 ERE 字符都可以以同样的方式使用。

总结

在本章中,我们介绍了正则表达式和正则表达式引擎 BRE 和 ERE。 我们学习了如何为它们定义模式。

我们学习了如何为 sed、AWK 和grep编写这些模式。

此外,我们还了解了特殊字符类如何使匹配字符集变得很容易。

我们了解了如何使用强大的 ERE 模式以及如何对表达式进行分组。

最后,我们了解了如何使用grep工具以及如何定义 BRE 和 ERE 模式。

在接下来的两章中,我们将看到一些 AWK 的实际例子。

问题

  1. 假设你有以下文件:
Welcome to shell scripting.
I love shell scripting.
shell scripting is awesome.

假设您运行以下命令:

$ awk '/awesome$/{print $0}' myfile

输出中将打印多少行?

  1. 如果对前一个文件使用下面的命令,将打印多少行?
$ awk '/scripting\..*/{print $0}' myfile
  1. 如果我们对前面的示例文件使用下面的命令,将打印多少行?
$ awk '/^[Ww]?/{print $0}' myfile
  1. 下面命令的输出是什么?
$ echo "welcome to shell scripting" | sed -n '/Linux|bash|shell/p'

进一步的阅读

请参阅以下有关本章的进一步阅读资料: