在本章中,我们将讨论使用流编辑器(sed)和 AWK 最神秘的部分。 它们是正则表达式,简称 regex。 在前面的章节中,我们害羞地讨论了一些正则表达式,这是因为我们不需要深入了解它们。
如果您了解如何编写正则表达式,您将节省大量的时间和精力。 使用正则表达式,您将释放 sed 和 AWK 背后的真正力量,并将专业地使用它们。
本章将涵盖以下几个方面:
- 正则表达式引擎
- 定义 BRE 模式
- 定义在模式
- 使用
grep
本章的源代码可在此下载:
首先,什么是正则表达式?
正则表达式是正则表达式引擎解释为匹配特定文本的字符串。 这就像是一种高级的搜索方式。
假设您想搜索文件中以任何小写字母开头的行,或者您想搜索包含数字的行,或者搜索以特定文本开头的行。 普通的搜索不能是泛型的:唯一的方法就是使用正则表达式。
什么是正则表达式引擎?
regex 引擎是一种软件,它能够理解这些字符串并翻译它们以找到匹配的文本。
有很多正则表达式引擎; 例如,Java、Perl 和 Python 等编程语言附带的引擎。 另外,Linux 工具使用的引擎是 sed 和 AWK,现在对我们来说重要的事情是学习 Linux 中 regex 引擎的类型。
在 Linux 中有两种 regex 引擎:
- 基本正则表达式(BRE)引擎
- 扩展正则表达式(ERE)引擎
大多数 Linux 二进制程序都理解这两种引擎,比如 sed 和 AWK。
grep
也可以理解 ERE,但必须使用-E
选项,相当于使用egrep
。
我们将看到如何为 sed 和 AWK 定义正则表达式模式。 我们先来定义 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
在这里,我们匹配任何包含灰分且不以b
或r
开始的行。
记住,在方括号外使用插入符号(^
)表示一行的开始。
使用字符类,你可以指定你的字符。 如果有很长的字符范围怎么办?
您可以指定在方括号之间匹配的字符范围,如下所示:
[a-d]
这意味着从a
到d
的字符范围,因此包括a
、b
、c
和d
。
让我们使用相同的示例文件:
$ awk '/[a-m]ash/{print $0}' myfile
$ sed -n '/[a-m]ash/p' myfile
选择字符范围a
到m
。 第三行包含 ash 之前的r
,它不在我们的范围内,所以只有第二行不匹配。
你也可以使用数字范围:
$ awk '/[0-9]/'
这个模式意味着从0
到9
是匹配的。
你可以在同一个括号中写入多个范围:
$ awk '/[d-hm-z]ash/{print $0}' myfile $ sed -n '/[d-hm-z]ash/p' myfile
在这个模式中,从d
到h
和从m
到z
被选择,因为第一行包含了 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:]]
| 0
至9
匹配 |
| [[: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
字符,该字符在字符类中不存在,因此没有匹配。
第三行包含字符a
和r
,每个字符对应一次,它们存在于字符类中,因此该行也匹配模式。
我们看到了定义 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
支持 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 的实际例子。
- 假设你有以下文件:
Welcome to shell scripting.
I love shell scripting.
shell scripting is awesome.
假设您运行以下命令:
$ awk '/awesome$/{print $0}' myfile
输出中将打印多少行?
- 如果对前一个文件使用下面的命令,将打印多少行?
$ awk '/scripting\..*/{print $0}' myfile
- 如果我们对前面的示例文件使用下面的命令,将打印多少行?
$ awk '/^[Ww]?/{print $0}' myfile
- 下面命令的输出是什么?
$ echo "welcome to shell scripting" | sed -n '/Linux|bash|shell/p'
请参阅以下有关本章的进一步阅读资料: