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

    前言

    正则表达式,又称规则表达式。(英语: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”部分。

    
    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. 首先,定义你想检索的字符串:

    
    let testString = "hat"

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

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

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

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

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

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

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

    
    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代码来匹配一个简单字符串

    
    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,让创建和匹配表达式更简单。

    首先第一行:

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

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

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

    
    extension NSRegularExpression {
     convenience init(_ pattern: String) {
      do {
       try self.init(pattern: pattern)
      } catch {
       preconditionFailure("Illegal regular expression: \(pattern).")
      }
     }
    }

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

    之后这些行:

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

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

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

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

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

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

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

    
    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个或更多。量词在它们修饰的字符后出现,就像下面这样:

    
    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},会匹配空格,连接线,斜杠等等,而不会匹配数字。

    总结

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