一口Linux

电子技术应用专栏作家——一口Linux。一起学习嵌入式、Linux、网络、驱动、arm知识

Linux0基础入门,教你如何在Shell中使用正则表达式

0
阅读(1587)

image.png

      正则表达式

在 shell 脚本中成功运用 sed 编辑器和 gawk 程序的关键在于熟练使用正则表达式。这可不是件简单的事,从大量数据中过滤出特定数据可能会(而且经常会)很复杂。本章将介绍如何在 sed 编辑器和 gawk 程序中创建正则表达式来过滤出需要的数据。

什么是正则表达式

理解正则表达式的第一步在于弄清它们到底是什么。本节将会解释什么是正则表达式并介绍 Linux 如何使用正则表达式。

定义

正则表达式是你所定义的模式模板(pattern template),Linux 工具可以用它来过滤文本。Linux 工具(比如 sed 编辑器或 gawk 程序)能够在处理数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉。

正则表达式模式利用通配符来描述数据流中的一个或多个字符。Linux 中有很多场景都可以使用通配符来描述不确定的数据。在本书之前你已经看到过在 Linux 的 ls 命令中使用通配符列出文件和目录的例子。

星号通配符允许你只列出满足特定条件的文件,例如:


image.png


da*参数会让 ls 命令只列出名字以 da 开头的文件。文件名中 da 之后可以有任意多个字符(包括什么也没有)。ls 命令会读取目录中所有文件的信息,但只显示跟通配符匹配的文件的信息。

正则表达式通配符模式的工作原理与之类似。正则表达式模式含有文本或特殊字符,为 sed 编辑器和 gawk 程序定义了一个匹配数据时采用的模板。可以在正则表达式中使用不同的特殊字符来定义特定的数据过滤模式。

正则表达式的类型

使用正则表达式最大的问题在于有不止一种类型的正则表达式。Linux 中的不同应用程序可能会用不同类型的正则表达式。这其中包括编程语言(Java、Perl 和 Python)、Linux 实用工具(比如 sed 编辑器、gawk 程序和 grep 工具)以及主流应用(比如 MySQL 和 PostgreSQL 数据库服务器)。

正则表达式是通过正则表达式引擎(regular expression engine)实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。在 Linux 中,有两种流行的正则表达式引擎:

  • POSIX 基础正则表达式(basic regular expression,BRE)引擎

  • POSIX 扩展正则表达式(extended regular expression,ERE)引擎

大多数 Linux 工具都至少符合 POSIX BRE 引擎规范,能够识别该规范定义的所有模式符号。遗憾的是,有些工具(比如 sed 编辑器)只符合了 BRE 引擎规范的子集。这是出于速度方面的考虑导致的,因为 sed 编辑器希望能尽可能快地处理数据流中的文本。

POSIX BRE 引擎通常出现在依赖正则表达式进行文本过滤的编程语言中。它为常见模式提供了高级模式符号和特殊符号,比如匹配数字、单词以及按字母排序的字符。gawk 程序用 ERE 引擎来处理它的正则表达式模式。

由于实现正则表达式的方法太多,很难用一个简洁的描述来涵盖所有可能的正则表达式。后续几节将会讨论最常见的正则表达式,并演示如何在 sed 编辑器和 gawk 程序中使用它们。

定义 BRE 模式

最基本的 BRE 模式是匹配数据流中的文本字符。本节将会演示如何在正则表达式中定义文本以及会得到什么样的结果。

纯文本

前面演示了如何在 sed 编辑器和 gawk 程序中用标准文本字符串来过滤数据。通过下面的例子来复习一下。

image.png

第一个模式定义了一个单词 test。sed 编辑器和 gawk 程序脚本用它们各自的 print 命令打印出匹配该正则表达式模式的所有行。由于 echo 语句在文本字符串中包含了单词 test,数据流文本能够匹配所定义的正则表达式模式,因此 sed 编辑器显示了该行。

第二个模式也定义了一个单词,这次是 trial。因为 echo 语句文本字符串没包含该单词,所以正则表达式模式没有匹配,因此 sed 编辑器和 gawk 程序都没打印该行。

你可能注意到了,正则表达式并不关心模式在数据流中的位置。它也不关心模式出现了多少次。一旦正则表达式匹配了文本字符串中任意位置上的模式,它就会将该字符串传回 Linux 工具。

关键在于将正则表达式模式匹配到数据流文本上。重要的是记住正则表达式对匹配的模式非常挑剔。第一条原则就是:正则表达式模式都区分大小写。这意味着它们只会匹配大小写也相符的模式。

image.png

第一次尝试没能匹配成功,因为 this 在字符串中并不都是小写,而第二次尝试在模式中使用大写字母,所以能正常工作。

在正则表达式中,你不用写出整个单词。只要定义的文本出现在数据流中,正则表达式就能够匹配。

image.png

如果你在正则表达式中定义了空格,那么它必须出现在数据流中。甚至可以创建匹配多个连续空格的正则表达式模式。

image.png

单词间有两个空格的行匹配正则表达式模式。这是用来查看文本文件中空格问题的好办法。

特殊字符

在正则表达式模式中使用文本字符时,有些事情值得注意。在正则表达式中定义文本字符时有一些特例。有些字符在正则表达式中有特别的含义。如果要在文本模式中使用这些字符,结果会超出你的意料。

正则表达式识别的特殊字符包括:

image.png

现在 sed 编辑器能正确解释正则表达式模式了,一切都很顺利。

锚字符

默认情况下,当指定一个正则表达式模式时,只要模式出现在数据流中的任何地方,它就能匹配。有两个特殊字符可以用来将模式锁定在数据流中的行首或行尾。

脱字符(^)定义从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置,正则表达式模式则无法匹配。要用脱字符,就必须将它放在正则表达式中指定的模式前面。

image.png

由于脱字符出现在正则表达式模式的尾部,sed 编辑器会将它当作普通字符来匹配。

如果指定正则表达式模式时只用了脱字符,就不需要用反斜线来转义。但如果你在模式中先指定了脱字符,随后还有其他一些文本,那么你必须在脱字符前用转义字符。


跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定义了行尾锚点。将这个特殊字符放在文本模式之后来指明数据行必须以该文本模式结尾。

image.png

将行尾的单词 book 改成复数形式,就意味着它不再匹配正则表达式模式了,尽管 book 仍然在数据流中。要想匹配,文本模式必须是行的最后一部分。


在一些常见情况下,可以在同一行中将行首锚点和行尾锚点组合在一起使用。在第一种情况中,假定你要查找只含有特定文本模式的数据行。

image.png

定义的正则表达式模式会查找行首和行尾之间什么都没有的那些行。由于空白行在两个换行符之间没有文本,刚好匹配了正则表达式模式。sed 编辑器用删除命令 d 来删除匹配该正则表达式模式的行,因此删除了文本中的所有空白行。这是从文档中删除空白行的有效方法。

点号字符

特殊字符点号用来匹配除换行符之外的任意单个字符。它必须匹配一个字符,如果在点号字符的位置没有字符,那么模式就不成立。来看一些在正则表达式模式中使用点号字符的例子。

image.png

你应该能够明白为什么第一行无法匹配,而第二行和第三行就可以。第四行有点复杂。注意,我们匹配了 at,但在 at 前面并没有任何字符来匹配点号字符。其实是有的!在正则表达式中,空格也是字符,因此 at 前面的空格刚好匹配了该模式。第五行证明了这点,将 at 放在行首就不会匹配该模式了。

字符组

点号特殊字符在匹配某个字符位置上的任意字符时很有用。但如果你想要限定待匹配的具体字符呢?在正则表达式中,这称为字符组(character class)。可以定义用来匹配文本模式中某个位置的一组字符。如果字符组中的某个字符出现在了数据流中,那它就匹配了该模式。

使用方括号来定义一个字符组。方括号中包含所有你希望出现在该字符组中的字符。然后你可以在模式中使用整个组,就跟使用其他通配符一样。这需要一点时间来适应,但一旦你适应了,效果可是令人惊叹的。下面是个创建字符组的例子。

image.png

正则表达式使用了 3 个字符组来涵盖了 3 个字符位置含有大小写的情况。
字符组不必只含有字母,也可以在其中使用数字。

image.png

这个正则表达式模式匹配了任意含有数字 0、1、2 或 3 的行。含有其他数字以及不含有数字的行都会被忽略掉。

可以将字符组组合在一起,以检查数字是否具备正确的格式,比如电话号码和邮编。但当你尝试匹配某种特定格式时,必须小心。这里有个匹配邮编出错的例子。

image.png

这个结果出乎意料。它成功过滤掉了不可能是邮编的那些过短的数字,因为最后一个字符组没有字符可匹配。但它也通过了那个六位数,尽管我们只定义了 5 个字符组。

记住,正则表达式模式可见于数据流中文本的任何位置。经常有匹配模式的字符之外的其他字符。如果要确保只匹配五位数,就必须将匹配的字符和其他字符分开,要么用空格,要么像这个例子中这样,指明它们就在行首和行尾。

image.png

现在好多了!本章随后会看到如何进一步进行简化。

字符组的一个极其常见的用法是解析拼错的单词,比如用户表单输入的数据。你可以创建正则表达式来接受数据中常见的拼写错误。

image.png

本例中的两个 sed 打印命令利用正则表达式字符组来帮助找到文本中拼错的单词 maintenance 和 separate。同样的正则表达式模式也能匹配正确拼写的 maintenance。

排除型字符组

在正则表达式模式中,也可以反转字符组的作用。可以寻找组中没有的字符,而不是去寻找组中含有的字符。要这么做的话,只要在字符组的开头加个脱字符。

image.png

通过排除型字符组,正则表达式模式会匹配 c 或 h 之外的任何字符以及文本模式。由于空格字符属于这个范围,它通过了模式匹配。但即使是排除,字符组仍然必须匹配一个字符,所以以 at 开头的行仍然未能匹配模式。

区间

你可能注意到了,我之前演示邮编的例子的时候,必须在每个字符组中列出所有可能的数字,这实在有点麻烦。好在有一种便捷的方法可以让人免受这番劳苦。可以用单破折线符号在字符组中表示字符区间。只需要指定区间的第一个字符、单破折线以及区间的最后一个字符就行了。根据 Linux 系统采用的字符集,正则表达式会包括此区间内的任意字符。现在你可以通过指定数字区间来简化邮编的例子。

image.png

该模式不匹配 fat 文本,因为它没在指定的区间。

特殊的字符组

除了定义自己的字符组外,BRE 还包含了一些特殊的字符组,可用来匹配特定类型的字符。下面介绍了可用的 BRE 特殊的字符组。

  • [[:alpha:]] 匹配任意字母字符,不管是大写还是小写

  • [[:alnum:]] 匹配任意字母数字字符 0~9、A~Z 或 a~z

  • [[:blank:]] 匹配空格或制表符

  • [[:digit:]] 匹配 0~9 之间的数字

  • [[:lower:]] 匹配小写字母字符 a~z

  • [[:upper:]] 匹配任意大写字母字符 A~Z

  • [[:print:]] 匹配任意可打印字符

  • [[:punct:]] 匹配标点符号

  • [[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT 和 CR

可以在正则表达式模式中将特殊字符组像普通字符组一样使用。

image.png

使用特殊字符组可以很方便地定义区间。如可以用[[:digit:]]来代替区间[0-9]。

星号

在字符后面放置星号表明该字符必须在匹配模式的文本中出现 0 次或多次。

image.png

这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子,如果需要写个可能用在美式或英式英语中的脚本,可以这么写:

image.png

可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。

星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间。

image.png

只要 a 和 e 字符以任何组合形式出现在 b 和 t 字符之间(就算完全不出现也行),模式就能够匹配。如果出现了字符组之外的字符,该模式匹配就会不成立。

扩展正则表达式

POSIX ERE 模式包括了一些可供 Linux 应用和工具使用的额外符号。gawk 程序能够识别 ERE 模式,但 sed 编辑器不能。

记住,sed 编辑器和 gawk 程序的正则表达式引擎之间是有区别的。gawk 程序可以使用大多数扩展正则表达式模式符号,并且能提供一些额外过滤功能,而这些功能都是 sed 编辑器所不具备的。但正因为如此,gawk 程序在处理数据流时通常才比较慢。

本节将介绍可用在 gawk 程序脚本中的较常见的 ERE 模式符号。

问号

问号类似于星号,不过有点细微的不同。问号表明前面的字符可以出现 0 次或 1 次,但只限于此。它不会匹配多次出现的字符。

image.png

如果字符组中的字符出现了 0 次或 1 次,模式匹配就成立。但如果两个字符都出现了,或者其中一个字符出现了 2 次,模式匹配就不成立。

加号

加号是类似于星号的另一个模式符号,但跟问号也有不同。加号表明前面的字符可以出现 1 次或多次,但必须至少出现 1 次。如果该字符没有出现,那么模式就不会匹配。

image.png

这次如果字符组中定义的任一字符出现了,文本就会匹配指定的模式。

使用花括号

ERE 中的花括号允许你为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。可以用两种格式来指定区间。

  • 正则表达式准确出现 m 次。

  • m, n:正则表达式至少出现 m 次,至多 n 次。

这个特性可以精确调整字符或字符集在模式中具体出现的次数。

如果你的 gawk 版本过老,gawk 程序不会识别正则表达式间隔。必须额外指定 gawk 程序的--re- interval 命令行选项才能识别正则表达式间隔。

这里有个使用简单的单值间隔的例子。

image.png

通过指定间隔为 1,限定了该字符在匹配模式的字符串中出现的次数。如果该字符出现多次,模式匹配就不成立。

很多时候,同时指定下限和上限也很方便。

image.png

在这个例子中,字符 e 可以出现 1 次或 2 次,这样模式就能匹配;否则,模式无法匹配

间隔模式匹配同样适用于字符组。

image.png

如果字母 a 或 e 在文本模式中只出现了 1~2 次,则正则表达式模式匹配;否则,模式匹配失败。

管道符号

管道符号允许你在检查数据流时,用逻辑 OR 方式指定正则表达式引擎要用的两个或多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。

使用管道符号的格式如下:

image.png

这里有个例子。

image.png

这个例子会匹配数据流文本中的 cat、hat 或 dog。

管道符号

正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符。举个例子:

image.png

模式(c|b)a(b|t)会匹配第一组中字母的任意组合以及第二组中字母的任意组合

正则表达式实战

现在你已经了解了使用正则表达式模式的规则和一些简单的例子,该把理论用于实践了。随后几节将会演示 shell 脚本中常见的一些正则表达式例子。

目录文件计数

让我们先看一个 shell 脚本,它会对 PATH 环境变量中定义的目录里的可执行文件进行计数。要这么做的话,首先你得将 PATH 变量解析成单独的目录名。前面介绍过如何显示 PATH 环境变量。

image.png

一旦获得了单个目录,就可以用 ls 命令来列出每个目录中的文件,并用另一个 for 语句来遍历每个文件,为文件计数器增值。
这个脚本的最终版本如下。

image.png

现在我们开始体会到正则表达式背后的强大之处了!

验证电话号码

前面的例子演示了在处理数据时,如何将简单的正则表达式和 sed 配合使用来替换数据流中的字符。正则表达式通常用于验证数据,确保脚本中数据格式的正确性。
一个常见的数据验证应用就是检查电话号码。数据输入表单通常会要求填入电话号码,而用户输入格式错误的电话号码是常有的事。在美国,电话号码有几种常见的形式:

(123)456-7890
(123) 456-7890
123-456-7890
123.456.7890

这样用户在表单中输入的电话号码就有 4 种可能。正则表达式必须足够强大,才能处理每一种情况。

在构建正则表达式时,最好从左手边开始,然后构建用来匹配可能遇到的字符的模式。在这个例子中,电话号码中可能有也可能没有左圆括号。这可以用如下模式来匹配:

^\(?

脱字符用来表明数据的开始。由于左圆括号是个特殊字符,因此必须将它转义成普通字符。问号表明左圆括号可能出现,也可能不出现。
紧接着就是 3 位区号。在美国,区号以数字 2 开始(没有以数字 0 或 1 开始的区号),最大可到 9。要匹配区号,可以用如下模式。

[2-9][0-9]{2}

这要求第一个字符是 2~9 的数字,后跟任意两位数字。在区号后面,收尾的右圆括号可能存在,也可能不存在。

\)?

在区号后,存在如下可能:有一个空格,没有空格,有一条单破折线或一个点。你可以对它们使用管道符号,并用圆括号进行分组。

(| |-|\.)

第一个管道符号紧跟在左圆括号后,用来匹配没有空格的情形。你必须将点字符转义,否则它会被解释成可匹配任意字符。
紧接着是 3 位电话交换机号码。这里没什么需要特别注意的。

[0-9]{3}

在电话交换机号码之后,你必须匹配一个空格、一条单破折线或一个点。

( |-|\.)

最后,必须在字符串尾部匹配 4 位本地电话分机号。

[0-9]{4}$

完整的模式如下。

^\(?[2-9][0-9]{2}\)?(| |-|\.)[0-9]{3}( |-|\.)[0-9]{4}$

你可以在 gawk 程序中用这个正则表达式模式来过滤掉不符合格式的电话号码。现在你只需要在 gawk 程序中创建一个使用该正则表达式的简单脚本,然后用这个脚本来过滤你的电话薄。脚本如下,可以将电话号码重定向到脚本来处理。

image.png

只有匹配该正则表达式模式的有效电话号码才会出现。

解析邮件地址

如今这个时代,电子邮件地址已经成为一种重要的通信方式。验证邮件地址成为脚本程序员的一个不小的挑战,因为邮件地址的形式实在是千奇百怪。邮件地址的基本格式为:

username@hostname

username 值可用字母数字字符以及以下特殊字符:

  • 点号

  • 单破折线

  • 加号

  • 下划线

在有效的邮件用户名中,这些字符可能以任意组合形式出现。邮件地址的 hostname 部分由一个或多个域名和一个服务器名组成。服务器名和域名也必须遵照严格的命名规则,只允许字母数字字符以及以下特殊字符:

  • 点号

  • 下划线

服务器名和域名都用点分隔,先指定服务器名,紧接着指定子域名,最后是后面不带点号的顶级域名。
顶级域名的数量在过去十分有限,正则表达式模式编写者会尝试将它们都加到验证模式中。然而遗憾的是,随着互联网的发展,可用的顶级域名也增多了。这种方法已经不再可行。
从左侧开始构建这个正则表达式模式。我们知道,用户名中可以有多个有效字符。这个相当容易。

^([a-zA-Z0-9_\-\.\+]+)@

这个分组指定了用户名中允许的字符,加号表明必须有至少一个字符。下一个字符很明显是@,没什么意外的。

hostname 模式使用同样的方法来匹配服务器名和子域名。

([a-zA-Z0-9_\-\.]+)

这个模式可以匹配文本:

server
server.subdomain
server.subdomain.subdomain

对于顶级域名,有一些特殊的规则。顶级域名只能是字母字符,必须不少于二个字符(国家或地区代码中使用),并且长度上不得超过五个字符。下面就是顶级域名用的正则表达式模式。

\.([a-zA-Z]{2,5})$

将整个模式放在一起会生成如下模式。

^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$

这个模式会从数据列表中过滤掉那些格式不正确的邮件地址。现在可以创建脚本来实现这个正则表达式了。

image.png

原文链接:https://mp.weixin.qq.com/s/I3pxGtWLg86wfHItRafXQQ


微信图片_20220701092006.jpg

电子技术应用专栏作家  一口Linux