在Swift中如何使用正则表达式详解

时间:2021-08-22 14:40:08

前言

正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表通常被用来检索、替换那些符合某个模式(规则)的文本。

正则表达式(Regular expression, regex)允许我们在几秒钟内在成千上万文档间进行复杂检索与替换,自从诞生50多年来它依旧广泛使用。

Swift虽然是一个新出的语言,但却不提供专门的处理正则的语法和类。所以我们只能使用古老的NSRegularExpression类进行正则匹配。

在这篇文章中,我会讲解在Swift中正则表达式的基本用法。我们会从易到难,详细讲解一些最重要的正则表达式语法,以及一些有用的扩展。

NSRegularExpression:如何在字符串中匹配正则表达式

NSRegularExpression类让我们可以用正则表达式查找替换子字符串,它可以简洁灵活地描述文本。例如,如果你想从"My name is Taylor Swift"中提取出"Taylor Swift",可以写一个匹配文本“My name is”的正则表达式,它的后面可以是任何文本,之后把它传递给NSRegularExpression类。

具体可见下面代码。注意我们要提取出的是第二范围,因为第一范围是匹配的字符串,而第二范围才是"Taylor Swift"部分。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
do {
 let input = "My name is Taylor Swift"
 let regex = try NSRegularExpression(pattern: "My name is (.*)", options: NSRegularExpression.Options.caseInsensitive)
 let matches = regex.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
 
 if let match = matches.first {
  let range = match.range(at:1)
  if let swiftRange = Range(range, in: input) {
   let name = input[swiftRange]
  }
 }
} catch {
 // regex was bad!
}

正则表达式的详细讲解

让我们从几个简单例子开始,方便不熟悉的人了解正则表达式。正则表达式,简称regex,用于让我们在字符串中进行模糊检索。例如我们知道”cat”包含”at”,但如果我们检索所有以“at”结尾的3字母单词该怎么做呢?

正则表达式就用于解决这个问题,尽管由于Objective-C的基础,它们的语法有些不太灵巧。

1. 首先,定义你想检索的字符串:

?
1
let testString = "hat"

之后创建NSRange实例来表示整个字符串的长度

?
1
let range = NSRange(location: 0, length: testString.utf16.count)

这里使用utf16来避免类似表情符号等带来的问题

2. 之后使用正则表达式语法创建NSRegularExpression实例

?
1
let regex = try! NSRegularExpression(pattern: "[a-z]at")

[a-z]在正则表达式中用于指定a到z之间任意字母。实际使用中你可能会提供一个无效的正则表达式,但是这里我们有了一个硬编码的正确正则表达式,所以就不需要查找错误了。

3. 最后在创建好的正则表达式调用firstMatch(in:),输入要检索的字符串,一些特殊选项,和字符串的范围。如果字符串匹配正则表达式,就会返回数据,否则就是nil。所以如果你想检查字符串是否完全匹配,就用firstMatch(in:)的结果和nil比较:

?
1
regex.firstMatch(in: testString, options: [], range: range) != nil

这里必须要用到NSRange——尽管这个API是为NSString设计,和Swift衔接的不太好。Swift String Manifesto可能会替换它,但看起来还要很久。

正则表达式“[a-z]at”会成功匹配“hat”,和“cat”, “sat”, “mat”, “bat”等等——我们只要关注想匹配什么,NSRegularExpression会处理好它。

让NSRegularExpression用起来更简单

接下里会展示更多的正则表达式语法,首先来看看如何让NSRegularExpression稍微好用一些

现在我们的要3行Swift代码来匹配一个简单字符串

?
1
2
3
let range = NSRange(location: 0, length: testString.utf16.count)
let regex = try! NSRegularExpression(pattern: "[a-z]at")
regex.firstMatch(in: testString, options: [], range: range) != nil

我们可以从多种方式改进,不过最有效的是扩展NSRegularExpression,让创建和匹配表达式更简单。

首先第一行:

?
1
let regex = try! NSRegularExpression(pattern: "[a-z]at")

我提到过,创建一个NSRegularExpression实例可能导致错误,因为可能会提供一个非法的正则表达式。比如[a-zat,忘记了]

结果就是,通常会用try!创建NSRegularExpression实例。然而这会导致lint工具(如SwiftLint)的破坏。所以好一点的方法是创建一个方便的初始化,能正确创建正则表达式,或者在开发时能生成一个断言失败。

?
1
2
3
4
5
6
7
8
9
extension NSRegularExpression {
 convenience init(_ pattern: String) {
  do {
   try self.init(pattern: pattern)
  } catch {
   preconditionFailure("Illegal regular expression: \(pattern).")
  }
 }
}

注意:如果你的app需要用户写正则表达式,你需要使用NSRegularExpression(pattern:)初始化,这样可以更好的处理错误。

之后这些行:

?
1
2
let range = NSRange(location: 0, length: testString.utf16.count)
regex.firstMatch(in: testString, options: [], range: range) != nil

第一行创建了一个包含整个字符串的NSRange,第二行则是在文本中查找first match。但这是很笨的方法,因为大多时候你想查找输入的整个字符串,用firstMatch(in:)与nil判定会弄混你的意图。

所以,用另一个扩展来替代它,它把下面代码包含在一个简单的matches()方法中。

?
1
2
3
4
5
6
extension NSRegularExpression {
 func matches(_ string: String) -> Bool {
  let range = NSRange(location: 0, length: string.utf16.count)
  return firstMatch(in: string, options: [], range: range) != nil
 }
}

如果你把这两个扩展合并,就可以更轻松的创建和检索正则表达式了。

?
1
2
let regex = NSRegularExpression("[a-z]at")
regex.matches("hat")

我们可以进一步通过运算符重载让Swift包含的,~=,运算符适用于正则表达式:

?
1
2
3
4
5
6
7
extension String {
 static func ~= (lhs: String, rhs: String) -> Bool {
  guard let regex = try? NSRegularExpression(pattern: rhs) else { return false }
  let range = NSRange(location: 0, length: lhs.utf16.count)
  return regex.firstMatch(in: lhs, options: [], range: range) != nil
 }
}

通过上面代码,我们可以在一句话的左边使用任意字符,右边用正则表达式。

"hat" ~= "[a-z]at"

注意:创建NSRegularExpression实例会有一定消耗,所以如果你想要反复使用一个正则表达式,最好把NSRegularExpression实例保存起来。

正则表达式语法学之旅

我们已经使用了[a-z]来表示“a”到“z”之间任意字母,在正则表达式中这是一个字符类。它让你指定要匹配的一组字母,可以通过制定的字母列表匹配,或者通过一段字符范围匹配。

正则表达式范围不一定是整个字母表,你可以用[a-t] 来排除“u”到“z”之间的字母。另外,如果你想特别指定一些字母,只需要像这样单独列出它们:

[csm]at

正则表达式默认区分大小姐写,也就是说“Cat”和“Mat”不会在“[a-z]at”被匹配。如果你想忽略大小写,可以使用“[a-zA-Z]at”,或者创建你自己的NSRegularExpression对象,并标记.caseInsensitive

除了大小写以外,你可以通过字符类指定数字范围。最常用的是[0-9]表示任何数字,或[A-Za-z0-9]表示任何字母数字混编字符,也可以用[A-Fa-f0-9]来表示16进制数字。

如果你想匹配一个字符序列,还需要一个叫做量词(quantifier)的概念。它用于表示字符出现的数量。

最常用的是星号量词,*,意思是匹配0个或更多。量词在它们修饰的字符后出现,就像下面这样:

?
1
let regex = NSRegularExpression("ca[a-z]*d")

这句话先查找“ca”,之后是0或多个从“a”到“z”的字母,最后是“d”——它能匹配“cad”, “card”, “clamped”等等。

除了*之外,还有2个类似的量词 + 和 ? 。 + 意味着“1个或更多”,与 * 的“0个或更多”有点区别。而 ? 的意思是”0或1个”

这些量词是正则表达式基础内容,希望大家能确实理解它们的区别,比如下面3个正则表达式

  • ca[a-z]*d
  • ca[a-z]+d
  • ca[a-z]?d

并想想如果给出字符串“cd”或“clamped”,哪些能够匹配。

如果需要,可以用大括号 { 和 } 来更详细的指定匹配数量,比如[a-z]{3}意味着匹配3个小写字母。

考虑一个电话号码格式比如111-1111。如果要正好匹配这个格式,用[0-9-]+是行不通的。所以我们需要用这样的正则表达式[0-9]{3}-[0-9]{4},即先是3个数字,之后连接号,之后4个数字。

此外还可以用大括号指定范围,它可以是有界限的或*限的。比如[a-z]{1,3}代表匹配1,2,或3个小写字母。[a-z]{3,}代表匹配3个或更多个

最后,元字符(meta-characters)是特殊字符,正则表达式中有特别的意义,在这里介绍其中几个使用最频繁的。

首先其中是最常用,也是最滥用的 . 字符。它可以匹配除了换行符以外任意一个字符。比如正则表达式c.t可以匹配“cat”,但不能匹配“cart”。如果你把 . 和 * 量词共同使用,就意味着匹配1个或多个除了换行符以外所有字符,这可能是你最常见的正则表达式了。

.* 常用的原因也显而易见:不需要具体设计一个特别的正则表达式,.* 就可以匹配几乎一切了。然而问题是,特定化本来就是正则表达式的要点之一,你可以在文本中精确查找一些字符并操作它们。而太多人完全依赖 .* ,却没有意识到这可能会给他们的表达式带来难以察觉的错误。

用前面电话号码的例子来说,我们用[0-9]{3}-[0-9]{4}匹配类似555-5555的电话号码。考虑到有些人会写成“555 5555”或“5555555”,我们可能就会把正则表达式条件放宽一些,改成[0-9]{3}.*[0-9]{4}

但是这样就带来一个问题,它会匹配“123-4567”, “123-4567890”, 或 “123-456-789012345”。为了让[0-9]{3}与[0-9]{4}匹配上,.* 会匹配尽可能多的字符

所以这里要用字符类与量词,比如[0-9]{3}[ -]*[0-9]{4},代表3个数字,之后0个或更多空格与连接线,之后4个数字。或者使用不包含字符类,即用它来匹配数字以外的字符,如[0-9]{3}[^0-9]+[0-9]{4},会匹配空格,连接线,斜杠等等,而不会匹配数字。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://www.cocoachina.com/ios/20180831/24749.html