1.简介
简而言之,正则表达式(简称regexes或regexp)是指定字符串模式的一种方式。 您无疑熟悉您喜欢的文本编辑器或IDE中的搜索和替换功能。 您可以搜索确切的单词和短语。 您还可以激活选项,例如不区分大小写,以便搜索单词“ color”时也会找到“ Color”,“ COLOR”和“ CoLoR”。 但是,如果您要搜索“颜色”一词的拼写形式(美国拼写:color,英国拼写:colour)而不必执行两次单独的搜索,该怎么办?
如果该示例看起来过于简单,那么如何搜索英文名称“ Katherine”的所有拼写变体(例如Catherine,Katharine,Kathreen,Kathryn等)。 更一般而言,您可能希望在文档中搜索所有类似于十六进制数字,日期,电话号码,电子邮件地址,信用卡号等的字符串。
正则表达式是(部分或全部)解决涉及文本的这些(以及许多其他)实际问题的有效方法。
大纲
本教程的结构如下。 我将通过适应理论教科书中使用的方法(除去任何不必要的严谨或学问之后),介绍您需要理解的核心概念。 我更喜欢这种方法,因为它可以让您在一些基本原则的背景下理解所需的功能的70%。 其余30%是更高级的功能,您可以稍后学习或跳过,除非您打算成为正则表达式大师。
与正则表达式相关的语法种类很多,但是其中大部分都可以使您尽可能简洁地应用核心思想。 我将逐步介绍这些内容,而不是丢掉一张大桌子或一张列表让您记住。
我们不会直接跳到Swift实施中,而是会通过一个出色的在线工具探索基础知识,该工具将帮助您以最小的摩擦和不必要的负担设计和评估正则表达式。 一旦您对主要思想感到满意,编写Swift代码基本上就是将您的理解映射到Swift API的问题。
在整个过程中,我们将努力保持务实的心态。 对于每种字符串处理情况,正则表达式都不是最佳工具。 在实践中,我们需要确定正则表达式很好运行的情况和不正常运行的情况。 还有一个中间立场,可以使用正则表达式来完成部分工作(通常是一些预处理和过滤),而其余的工作则留给算法逻辑。
核心概念
正则表达式在计算机科学研究的主题之一“计算理论”中有其理论基础,在它们中,输入作用于应用于特定类型的抽象计算机(称为有限自动机)的输入。
放轻松,但是,您无需学习理论背景即可实际使用正则表达式。 我之所以仅提及它们,是因为我将首先从头开始激发正则表达式的方法与计算机科学教科书中用于定义“理论”正则表达式的方法相似。
假设您对递归有所了解,我希望您牢记如何定义递归函数。 函数是根据其自身的较简单版本定义的,并且,如果您通过递归定义进行跟踪,则必须以显式定义的基本情况结束。 我提出这一点是因为下面的定义也将是递归的。
请注意,在一般性地讨论字符串时,我们会隐式考虑一个字符集,例如ASCII,Unicode等。让我们假设一下,我们生活在一个由字符串组成的小写26个字母的宇宙中字母(a,b,... z),仅此而已。
规则
我们首先断言该集合中的每个字符都可以视为正则表达式,并将其自身匹配为字符串。 所以 a
作为正则表达式匹配“一个”(被视为一个字符串), b
是与字符串“ b”匹配的正则表达式, Ɛ
。也可以说存在一个与空字符串“”匹配的“空”正则表达式Ɛ
。 这样的情况对应于递归的琐碎的“基本情况”。
现在,我们考虑以下规则,这些规则可帮助我们从现有规则中创建新的正则表达式:
- 任何两个正则表达式的串联 (即“串在一起”)是一个新的正则表达式,它与匹配原始正则表达式的任意两个字符串的串联匹配。
- 两个正则表达式的交替是一个新的正则表达式,它与两个原始正则表达式中的任何一个匹配。
- 正则表达式的Kleene星会匹配零个或多个与原始正则表达式匹配的相邻实例。
让我们用字母字符串几个简单的例子来具体说明一下。
例子1
根据规则1, a
和b
是与“ a”和“ b”匹配的正则表达式,表示ab
是与字符串“ ab”匹配的正则表达式。 由于ab
和c
是正则表达式,因此abc
是与字符串“ abc”匹配的正则表达式,依此类推。 以此方式继续,我们可以制作任意长的正则表达式,以匹配具有相同字符的字符串。 尚未发生任何有趣的事情。
例子2
根据规则2, o
和a
为正则表达式, o|a
匹配“ o”或“ a”。 竖线表示交替。 c
和t
是正则表达式,结合规则1,我们可以断言c(o|a)t
是正则表达式。 括号用于分组。
它匹配什么? c
和t
仅匹配它们,这意味着正则表达式 c(o|a)t
匹配“ c”,后跟“ a”或“ o”后跟“ t”,例如字符串“ cat”或“ cot”。 请注意,它与“外套” 不匹配,因为o|a
仅匹配“ a”或“ o”,但不能一次匹配两个。 现在事情开始变得有趣。
例子3
根据规则3, a*
与零个或多个“ a”实例匹配。 它与空字符串或字符串“ a”,“ aa”,“ aaa”等匹配。 让我们结合其他两个规则来练习该规则。
这是什么ho*t
匹配? 它与“ ht”(零个“ o”实例),“ hot”,“ hoot”,“ hooot”等匹配。 b(o|a)*
呢? 它可以匹配“ b”,后跟任意数量的“ o”和“ a”实例(不包括任何实例)。 “ b”,“ boa”,“ baa”,“ bao”,“ baooaoaaooo”只是此正则表达式匹配的无数个字符串中的一些。 再次注意,括号用于将正则表达式中应用了*
的部分组合在一起。
例子4
让我们尝试发现与我们已经想到的字符串匹配的正则表达式。 我们将如何做一个正则表达式来识别绵羊的不适,我将其视为基本声音“ baa”(“ baa”,“ baabaa”,“ baabaabaa”等)的任意重复。
如果您说(baa)*
,那么您几乎是正确的。 但是请注意,此正则表达式也将匹配空字符串,这是我们不希望的。 换句话说,我们要忽略不败的绵羊。 baa(baa)*
是我们要寻找的正则表达式。 同样,母牛的叫声可能是moo(moo)*
。 我们如何识别任何一种动物的声音? 简单。 使用交替。 baa(baa)*|moo(moo)*
如果您已了解上述想法,那么恭喜您,您的工作进展顺利。
2.语法事项
回想一下,我们对字符串进行了愚蠢的限制。 它们只能由小写字母组成。 现在,我们将取消此限制,并考虑所有由ASCII字符组成的字符串。
我们必须意识到,为了使正则表达式成为一种方便的工具,它们本身需要表示为字符串。 因此,与以前不同,我们不能再使用*
, |
等字符|
, (
, )
等,而不会以某种方式表明我们是否将它们用作表示交替,分组等的“特殊”字符,或者是否将它们视为需要按字面值进行匹配的普通字符。
解决方案是对待这些和其他可能具有特殊含义的“元字符”。 要在一种用途和另一种用途之间切换,我们需要能够逃脱它们。 这类似于使用“ \ n”(转义n)来指示字符串中的新行的想法。 稍微复杂一点在于,根据通常为“元”的上下文字符,它可以表示其字面的自我而不会逃脱。 我们稍后将看到此示例。
我们重视的另一件事是简洁。 仅使用上一节的符号即可表达的许多正则表达式将变得冗长乏味。 例如,假设您只想查找由小写字母后跟数字组成的所有两个字符串(例如,诸如“ a0”,“ b9”,“ z3”之类的字符串)。 使用我们前面讨论的表示法,这将导致以下正则表达式:
(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)(0|1|2|3|4|5|6|7|8|9)
只是输入那个怪物就把我扫地出门。
[abcdefghijklmnopqrstuvwxyz][0123456789]
看起来更好吗? 请注意表示一组字符的元字符[
和]
,其中任何一个都给出正匹配。 实际上,如果我们认为字母a到z以及数字0到9在ASCII码集中依次出现,则可以将正则表达式缩减为很酷的[az][0-9]
。
在字符集的范围内,破折号-
是表示范围的另一个元字符。 请注意,您可以将多个范围压缩到同一对方括号中。 例如, [0-9a-zA-Z]
可以匹配任何字母数字字符。 9和a (以及z和A ) 互相挤压可能看起来很有趣,但是请记住,正则表达式都是为了简洁,含义很明确。
简洁起见,我们将在稍后介绍一些更加简洁的方式来表示某些类别的相关字符。 请注意,替换栏, |
,仍然是有效且有用的语法,我们稍后会看到。
更多语法
在开始练习之前,让我们看一下更多的语法。
期
期间, .
,匹配任何单个字符,但换行符除外。 这意味着ct
可以匹配“ cat”,“ crt”,“ c9t”,“ c%t”,“ ct”,“ ct”等。 例如,如果我们想将句点作为普通字符进行匹配,以匹配字符串“ ct”,则可以将其转义( c\.t
)或将其放入其自己的字符类( c[.]t
)。
通常,这些想法适用于其他元字符,例如[
, ]
, (
, )
, *
,以及我们尚未遇到的其他元字符。
括弧
如前所述,括号( (
和)
)用于分组。 我们将使用令牌一词来表示单个字符或带括号的表达式。 原因是许多正则表达式运算符都可以应用于其中任何一个。
括号还用于定义捕获组 ,使您能够确定正则表达式中的特定捕获组捕获了匹配的哪一部分。 稍后,我将详细讨论这个非常有用的功能。
加
令牌后的+
是该令牌的一个或多个实例。 在我们的绵羊起毛示例中, baa(baa)*
可以更简洁地表示为(baa)+
。 回想一下*
表示零次或多次出现。 请注意, (baa)+
与baa+
有所不同,因为在前者中, +
适用于baa
令牌,而在后者中,它仅适用于之前的a
。 在后者中,它匹配字符串“ baa”,“ baaa”和“ baaaa”。
问号
一个?
跟随令牌表示该令牌的零个或一个实例。
实践
RegExr是一个出色的在线工具,可以对正则表达式进行实验。 当您舒适地阅读和编写正则表达式时,使用Foundation框架的正则表达式API会容易得多。 即使这样,也可以更轻松地首先在网站上实时测试您的正则表达式。
访问网站,并专注于页面的主要部分。 这是您将看到的:
您在顶部的框中输入正则表达式,然后输入要查找匹配项的文本。
表达式框末尾的“ / g”本身不是正则表达式的一部分。 它是一个标志,会影响正则表达式引擎的整体匹配行为。 通过在正则表达式后附加“ / g”,引擎将在文本中搜索正则表达式的所有可能匹配项,这是我们想要的行为。 蓝色突出显示表示匹配。 将鼠标悬停在正则表达式上是一种方便的方法,可以提醒您其组成部分的含义。
知道正则表达式有多种形式,具体取决于您使用的语言或库。 这不仅意味着语法在各种类型之间可能有所不同,在功能和功能上也有所不同。 例如,Swift使用ICU指定的模式语法。 我不确定RegExr(在JavaScript上运行)使用的是哪种样式,但是在本教程的范围内,它们非常相似,甚至不相同。
我也鼓励您浏览左侧的窗格,该窗格以简洁的方式显示了很多信息。
我们的第一个实际例子
为了避免潜在的混乱,我应该提到,在谈论正则表达式匹配时,我们可能指的是以下两种情况之一:
- 寻找与正则表达式匹配的字符串的任何(或全部)子字符串
- 检查完整的字符串是否与正则表达式匹配
正则表达式引擎使用的默认含义是(1)。 到目前为止,我们一直在谈论的是(2)。 幸运的是,通过后面将介绍的元字符很容易实现含义(2)。 现在不用担心。
让我们从测试我们的绵羊出血示例开始简单。 在表达式框中输入(baa)+
,然后输入一些示例来测试匹配项,如下所示。
我希望您理解为什么成功的比赛实际上成功了,而其他比赛为什么失败了。 即使在这个简单的示例中,也需要指出一些有趣的事情。
贪婪的比赛
字符串“ baabaa”是否包含两个匹配项或一个匹配项? 换句话说,每个“ baaba”是一个匹配项,还是整个“ baabaa”是一个匹配项? 这取决于是否正在寻求“贪婪的比赛”。 贪婪的匹配会尝试匹配尽可能多的字符串。
现在,正则表达式引擎正在贪婪地进行匹配,这意味着“ baabaa”是一个匹配项。 有多种方法可以进行延迟匹配,但这是一个更高级的主题,并且由于我们已经准备好了完整的版面,因此在本教程中不会对此进行介绍。
如果字符串的两个相邻部分分别单独(但不是共同)匹配正则表达式,则RegExr工具会在突出显示中留下一个小的但可识别的间隙。 我们将稍后看到此行为的示例。
大写和小写
由于大写字母“ B”,“ Baabaa”失败。 假设您只想将第一个“ B”大写,相应的正则表达式将是什么? 尝试先自己弄清楚。
一个答案是(B|b)aa(baa)*
。 如果您大声朗读它会有所帮助。 大写或小写“ b”,后跟“ aa”,后跟零个或多个“ baa”实例。 这是可行的,但请注意,这可能很快会带来不便,特别是如果我们想完全忽略大写的话。 例如,我们将不得不为每种情况指定替代项,这将导致诸如([Bb][Aa][Aa])+
笨拙。
幸运的是,正则表达式引擎通常可以选择忽略大小写。 对于RegExr,请单击显示“标志”的按钮,然后选中“忽略大小写”复选框。 请注意,在正则表达式末尾的选项列表之前是字母“ i”。 请尝试使用一些大小写混合的示例,例如“ bAABaa”。
另一个例子
让我们尝试设计一个可以捕获名称“ Katherine”的变体的正则表达式。 您将如何解决这个问题? 我会写下尽可能多的变体,着眼于共同的部分,然后尝试以单词的形式将变体(强调替代和可选字母)表达为序列。 接下来,我将尝试制定能够吸收所有这些变化的正则表达式。
让我们用以下变体列表进行尝试:Katherine,Katharine,Catherine,Kathreen,Kathleen,Katryn和Catrin。 如果您愿意,我会再写下一些。 综观这些变化,我可以粗略地说:
- 名称以“ k”或“ c”开头
- 其次是“在”
- 后面可能跟一个“ h”
- 可能后跟一个“ a”或“ e”
- 后跟一个“ r”或“ l”
- 后跟“ i”,“ ee”或“ y”之一
- 并且肯定跟着“ n”
- 最后可能是“ e”
考虑到这个想法,我可以提出以下正则表达式:
[kc]ath?[ae]?(r|l)(i|ee|y)ne?
请注意,第一行“ KatherineKatharine”具有两个匹配项,两者之间没有任何分隔。 如果您在RegExr的文本编辑器中仔细查看,您会发现两次匹配之间的突出显示之间有一个小间隙,这就是我之前所说的。
请注意,上述正则表达式还与我们没有考虑甚至可能不存在的名称匹配,例如“ Cathalin”。 在目前的情况下,这根本不会对我们产生负面影响。 但是在某些应用程序中,例如电子邮件验证,您想更详细地说明匹配的字符串和拒绝的字符串。 这通常会增加正则表达式的复杂性。
更多语法和示例
在继续介绍Swift之前,我想讨论一下正则表达式语法的更多方面。
简洁的表述
几类相关字符具有简洁的表示形式:
-
\w
字母数字字符,包括下划线,等效于[a-zA-Z0-9_]
-
\d
代表一个数字,等效于[0-9]
-
\s
表示空格,即空格,制表符或换行符
这些类还具有相应的否定类:
-
\W
表示非字母数字,非下划线字符 -
\D
非数字 -
\S
为非空格字符
记住没有大写字母的班级,然后记住相应的大写字母与没有大写字母的班级不匹配。 请注意,如果需要,可以通过在方括号内包含这些内容进行组合。 例如, [\s\S]
代表任何字符,包括换行符。 回想一下那个时期.
匹配除换行符以外的任何字符。
锚点
^
和$
是分别表示字符串开头和结尾的锚点。 还记得我写过,您可能想匹配整个字符串,而不是寻找子字符串匹配吗? 这就是您的做法。 ^c[oau]t$
匹配“ cat”,“ cot”或“ cut”,但不匹配“ catch”或“ recut”。
词边界
\b
表示单词之间的边界(例如,由于空格或标点符号所致)以及字符串的开头或结尾。 请注意,它与位置而不是显式字符匹配有点不同。 将单词边界视为将单词与上一个/下一个单词分开的无形分隔符可能会有所帮助。 如您所料, \B
表示“不是单词边界”。 \bcat\b
在“ cat”,“ a cat”,“ Hi,cat”中找到匹配项,但在“ acat”或“ catch”中找不到匹配项。
否定
否定的想法可以通过使用字符集中的^
元字符来变得更加具体。 这是一个完全不同的使用^
从“开始串锚”。 这意味着,对于否定,必须在开头的字符集中使用^
。 [^a]
匹配字母“ a”以外的任何字符, [^az]
匹配除小写字母之外的任何字符。
您可以使用否定和字符范围来表示\W
吗? 答案是[^A-Za-z0-9_]
。 您认为[a^]
匹配什么? 答案是“ a”或“ ^”字符,因为它不是出现在字符集的开头。 此处的“ ^”按字面意义进行匹配。
或者,我们可以像这样[\^a]
对其进行显式转义。 希望您开始对转义的工作方式有所了解。
量词
我们看到了*
(和+
)如何用于匹配令牌零次或多次(以及一次或多次)。 使用大括号中的量词可以使这种多次匹配令牌的想法更加具体。 例如, {2, 4}
表示前一个令牌的两到四个匹配项。 {2,}
表示两个或更多匹配项,而{2}
表示恰好两个匹配项。
在下一个教程中,我们将查看使用大多数这些元素的详细示例。 但是,为了练习起见,我建议您编写自己的示例并测试我们刚刚在RegExr工具中看到的语法。
结论
在本教程中,我们主要关注正则表达式的理论和语法。 在下一个教程中,我们将Swift添加到混合中。 在继续之前,请通过玩RegExr确保您了解我们在本教程中介绍的内容 。
翻译自: https://code.tutsplus.com/tutorials/swift-and-regular-expressions-syntax--cms-26387