如何在Linux上使用正则表达式(regexes)
想知道那些奇怪的符号字符串在Linux上做什么?他们给你命令行魔法!我们将教我们如何投放正则表达式拼写并提高命令行技能。
什么是正则表达式?
正则表达式(regexes)是查找匹配字符序列的一种方法。他们使用字母和符号来定义在文件或者流中搜索的模式。正则表达式有几种不同的口味。我们将看一下常见Linux实用程序和命令中使用的版本,例如grep
,该命令可打印与搜索模式匹配的行。
整本关于正则表达式的书都已经写好了,因此本教程只是一个介绍。有基本的和扩展的正则表达式,在这里我们将使用扩展。
要将扩展的正则表达式与grep
一起使用,必须使用-E
(扩展)选项。因为这很快变得很烦人,所以创建了egrep
命令。 egrep命令与grep -E组合相同,只是我们不必每次都使用-E选项。
如果发现使用egrep
更方便,则可以。但是,请注意,它已正式弃用。它仍然存在于我们检查的所有发行版中,但将来可能会消失。
当然,我们始终可以创建自己的别名,因此始终会包含我们喜欢的选项。
开始
对于我们的示例,我们将使用包含datas列表的纯文本文件。请记住,我们可以将正则表达式与许多Linux命令一起使用。我们只是使用grep作为一种方便的方式来演示它们。
这是文件的内容:
less data.txt
显示文件的第一部分。
让我们从一个简单的搜索模式开始,并在文件中搜索字母o的出现。再一次,因为我们在所有示例中都使用了-E(扩展的正则表达式)选项,所以我们键入以下内容:
grep -E 'o' datas.txt
显示包含搜索模式的每一行,并突出显示匹配的字母。我们执行了一个简单的搜索,没有任何限制。字母是否出现多次,在字符串的末尾,在同一单词中出现两次,甚至在其自身旁边出现都无所谓。
几个名字用双O表示;我们键入以下内容仅列出那些:
grep -E 'oo' datas.txt
正如预期的那样,我们的结果集要小得多,并且搜索词会按字面意义进行解释。除了我们键入的内容外,它没有其他含义:双o字符。
随着我们的前进,我们将在搜索模式中看到更多功能。
行号和其他grep技巧
如果我们希望grep列出匹配条目的行号,则可以使用-n(行号)选项。这是grep技巧,不是regex功能的一部分。但是,有时我们可能想知道匹配条目在文件中的位置。
我们输入以下内容:
grep -E -n 'o' datas.txt
我们可以使用的另一个便捷的grep技巧是-o(仅匹配)选项。它仅显示匹配的字符序列,而不显示周围的文本。如果我们需要快速扫描列表中的任何行中有重复的匹配项,这将很有用。
为此,我们键入以下内容:
grep -E -n -o 'o' datas.txt
如果要将输出减少到最低限度,可以使用-c(计数)选项。
我们键入以下内容以查看文件中包含匹配项的行数:
grep -E -c 'o' datas.txt
交替算子
如果要搜索双l和双o的出现,可以使用竖线(|
)字符,它是交替运算符。它查找左侧或者右侧搜索模式的匹配项。
我们输入以下内容:
grep -E -n -o 'll|oo' datas.txt
结果中将出现任何包含双l,o或者两者的行。
区分大小写
我们还可以使用交替运算符来创建搜索模式,如下所示:
am|Am
这将同时匹配am和Am。除了琐碎的例子以外,这很快导致麻烦的搜索模式。解决此问题的一种简单方法是在grep
中使用-i
(忽略大小写)选项。
为此,我们键入以下内容:
grep -E 'am' datas.txt
grep -E -i 'am' datas.txt
第一个命令产生三个结果,并突出显示三个匹配项。第二条命令产生四个结果,因为Amanda中的Am也是匹配项。
锚定
我们也可以用其他方式匹配Am序列。例如,我们可以专门搜索该模式或者忽略大小写,并指定该序列必须出现在行的开头。
当我们匹配出现在一行字符或者一个单词的特定部分的序列时,这称为锚定。我们可以使用脱字符号(^
)来表示搜索模式仅将字符序列视为匹配(如果它出现在行的开头)。
我们输入以下内容(请注意,插入符号位于单引号内):
grep -E Am'datas.txt
grep -E -i '^am' datas.txt
这两个命令都匹配Am。
现在,让我们寻找在行尾包含双精度n的行。
我们使用美元符号($)表示以下内容:
grep -E -i 'nn' datas.txt
grep -E -i 'nn$' datas.txt
通配符
我们可以使用句点(。)来表示任何单个字符。
我们键入以下内容以搜索以T开头,以m结尾并在它们之间具有单个字符的模式:
grep -E 'T.m' datas.txt
搜索模式匹配序列Tim和Tom。我们也可以重复句号以指示一定数量的字符。
我们输入以下内容表示我们不在乎中间的三个字符是什么:
grep-E 'J...n' datas.txt
匹配并显示了包含Jason的行。
使用星号(*
)匹配零个或者多个出现的前一个字符。在此示例中,星号之前的字符是句点(.
),(再次)表示任何字符。
这意味着星号(*
)将匹配出现的任意数量的字符(包括零)。
星号有时会使正则表达式新手感到困惑。也许是因为他们通常将其用作表示任何含义的通配符。
但是,在正则表达式中,'c * t'与猫,婴儿床,老傻瓜等不匹配。相反,它转换为匹配零个或者多个c'字符,后跟一个t'。因此,它匹配t,ct,cct,ccct或者任意数量的c字符。
因为我们知道文件中内容的格式,所以我们可以在搜索模式中添加一个空格作为最后一个字符。仅在名字和姓氏之间出现空格。
因此,我们键入以下内容以强制搜索仅包含文件中的名字:
grep -E 'J.*n ' datas.txt
grep -E 'J.*n ' datas.txt
乍一看,第一个命令的结果似乎包含一些奇数匹配。但是,它们都符合我们使用的搜索模式的规则。
该序列必须以大写字母J开头,后跟任意数量的字符,然后是n。尽管如此,尽管所有匹配项均以J开头并以n结尾,但是其中一些并不是我们所期望的。
因为我们在第二个搜索模式中添加了空格,所以我们达到了预期的目的:所有以J开头并以n结尾的名字。
角色类
假设我们要查找所有以大写字母N或者W开头的行。
如果我们使用以下命令,则它将与以大写字母N或者W开头的序列的任何行匹配,无论该行出现在什么位置:
grep -E 'N|W' datas.txt
那不是我们想要的。如果我们在搜索模式的开始处应用行锚(^
)的开始(如下所示),则会得到相同的结果集,但原因有所不同:
grep -E '^N|W' datas.txt
搜索匹配包含大写字母W的行(在行中的任何位置)。它也匹配No More行,因为它以大写字母N开头。行锚(^
)的开始仅应用于大写字母N。
我们还可以在大写字母W处添加一个行锚,但是这将很快变得比我们的简单示例更加复杂。
解决方案是将搜索模式的一部分括在方括号([[])中,然后将锚运算符应用于该组。方括号([[]
)表示此列表中的任何字符。这意味着我们可以省略(|
)交替运算符,因为我们不需要它。
我们可以将行锚的开始应用于括号(`[])中列表中的所有元素。 (请注意,行锚的起点不在括号内)。
我们键入以下内容来搜索以大写字母N或者W开头的任何行:
grep -E '^[NW]' datas.txt
我们还将在下一组命令中使用这些概念。
我们键入以下内容以搜索名为Tom或者Tim的任何人:
grep -E 'T[oi]m' datas.txt
如果插入符号(^
)是方括号([]
)中的第一个字符,则搜索模式将查找列表中未出现的任何字符。
例如,我们键入以下内容以查找任何以T开头,以m结尾且中间字母不是o的名称:
grep -E 'T[^o]m' datas.txt
我们可以在列表中包含任意数量的字符。我们键入以下内容以查找以T开头,以m结尾并在中间包含任何元音的名称:
grep -E 'T[aeiou]m' datas.txt
间隔表达式
我们可以使用间隔表达式来指定希望在匹配字符串中找到前一个字符或者组的次数。将数字括在大括号({}
)中。
单独的数字表示该数字,但是如果在其后加上逗号(,
),则表示该数字或者更大。如果用逗号(" 1,2")分隔两个数字,则表示数字范围从最小到最大。
我们要查找以T开头,后接至少一个但不超过两个连续元音并以m结尾的名称。
因此,我们键入以下命令:
grep -E 'T[aeiou]{1,2}m' datas.txt
这与Tim,Tom和Team匹配。
如果要搜索序列el,请输入以下内容:
grep -E 'el' datas.txt
我们在搜索模式中添加第二个l,以仅包括包含双l的序列:
grep -E 'ell' datas.txt
这等效于以下命令:
grep -E 'el{2}' datas.txt
如果我们提供至少出现一次且不超过两次的l范围,它将匹配el和ell序列。
这与这四个命令中的第一个命令的结果有细微的区别,在这四个命令中,所有匹配项都是针对el序列的,包括ell序列内部的匹配(并且仅突出显示一个l)。
我们输入以下内容:
grep -E 'el{1,2}' datas.txt
要查找两个或者多个元音的所有序列,我们键入以下命令:
grep -E '[aeiou]{2,}' datas.txt
转义字符
假设我们要查找以句点(。)为最后一个字符的行。我们知道美元符号($
)是行锚的结尾,因此我们可以输入以下内容:
grep -E '.$' datas.txt
但是,如下所示,我们没有达到预期的效果。
如前所述,句点(.
)与任何单个字符匹配。因为每一行都以一个字符结尾,所以结果中返回了每一行。
因此,当我们只想搜索某个实际字符时,如何防止该特殊字符执行其正则表达式功能?为此,请使用反斜杠(\
)来使字符转义。
我们使用-E
(扩展)选项的原因之一是因为当我们使用基本正则表达式时,它们所需的转义次数要少得多。
我们输入以下内容:
grep -e '\.$' datas.txt
这与行尾的实际句号(.
)相匹配。
锚定与言语
我们在上面介绍了行首锚(^
)和行尾($
)。但是,我们可以使用其他锚来操作单词的边界。
在这种情况下,单词是由空格(行的开头或者结尾)界定的一系列字符。因此,尽管我们不会在字典中找到psy66oh,但它会算作一个单词。
词锚的开头是(\ <
);注意它指向单词的开头。假设一个名称在所有小写字母中的输入错误。我们可以使用grep-i
选项执行不区分大小写的搜索,并找到以h开头的名称。
我们输入以下内容:
grep -E -i 'h' datas.txt
这样可以找到所有出现的h,而不仅仅是单词开头的那些。
grep -E -i '\<h' datas.txt
这只会发现单词开头的单词。
让我们用字母y做类似的事情;我们只想查看单词结尾处的实例。我们输入以下内容:
grep -E 'y' datas.txt
可以找到y的所有出现,无论它出现在单词中的何处。
现在,我们使用锚点(/>
)的结尾(指向右边或者单词的结尾)键入以下内容:
grep -E 'y\>' datas.txt
第二个命令产生期望的结果。
要创建一个搜索整个单词的搜索模式,可以使用边界运算符(\ b)。我们将在搜索模式的两端使用边界运算符(\ B)查找必须在较大单词内的字符序列:
grep -E '\bGlenn\b' datas.txt
grep -E '\Bway\B' datas.txt
更多角色类别
我们可以使用快捷方式在字符类中指定列表。这些范围指示符使我们不必在搜索模式中键入列表的每个成员。
我们可以使用以下所有内容:
A-Z:从A到Z的所有大写字母。
a-z:从a到z的小写字母。
0-9:从零到九的所有数字。
d-p:d到p的小写字母。这些自由格式样式使我们可以定义自己的范围。
2-7:所有数字从2到7.
我们还可以在搜索模式中使用任意多个字符类。以下搜索模式匹配以J开头,后跟o或者s,然后是e,h,l或者s的序列:
grep -E 'J[os][ehls]' datas.txt
在我们的下一个命令中,我们将使用a-z
范围说明符。
我们的搜索命令按以下方式分解:
H:序列必须以H开头。
[a-z]:下一个字符可以是此范围内的任何小写字母。
*:这里的星号代表任意数量的小写字母。
男人:序列必须以男人为结尾。
我们将所有内容放到以下命令中:
grep -E 'H[a-z]*man' datas.txt
没有什么不可穿透的
一些正则表达式可能很快就很难在视觉上解析。当人们编写复杂的正则表达式时,他们通常从小处开始,并添加越来越多的部分,直到它起作用为止。随着时间的流逝,它们的复杂性趋于增加。
当我们尝试从最终版本向后看以了解其功能时,这完全是另外一个挑战。
例如,查看以下命令:
grep -E '^([0-9]{4}[- ]){3}[0-9]{4}|[0-9]{16}' datas.txt
我们将其中开始理清呢?我们将从头开始,一次取一整块:
^:行锚的起点。因此,我们的顺序必须是直线上的第一件事。
([0-9] {4} [-]):括号将搜索模式元素归为一组。可以将其他操作整体应用于此组(稍后再介绍)。第一个元素是一个字符类,其中包含从零到九个[0-9]的数字范围。那么,我们的第一个字符是从零到九的数字。接下来,我们有一个间隔表达式,其中包含数字" {4}"。这适用于我们的第一个字符,我们知道这将是一个数字。因此,搜索模式的第一部分现在是四位数。后面可以跟另一个字符类中的空格或者连字符(
[-]
)。{3}:该组后面紧跟一个包含数字3的间隔说明符。它应用于整个组,因此我们的搜索模式现在是四位数,然后是空格或者连字符,重复了三遍。
[0-9]:接下来,我们还有另一个字符类,其中包含从零到九个[0-9]的数字范围。这会在搜索模式中添加另一个字符,并且可以是从零到九的任何数字。
{4}:另一个包含数字4的间隔表达式将应用于前一个字符。这意味着一个字符变为四个字符,所有字符都可以是从零到九的任何数字。
|:交替运算符告诉我们左侧的所有内容都是完整的搜索模式,右侧的内容则是新的搜索模式。因此,此命令实际上是在搜索两种搜索模式中的一种。第一组是三组,每组四位数,其后是空格或者连字符,然后是另外四位数。
[0-9]:第二个搜索模式以从零到九的任何数字开头。
{16}:将间隔运算符应用于第一个字符,并将其转换为16个字符,所有这些字符均为数字。
因此,我们的搜索模式将寻找以下任一情况:
四组四位数,每个组用空格或者连字符(
-
)分隔。一组十六位数字。