在多数语言中,字符串中的"\"会作为转义字符串出现,用以将紧跟其后的字符转义,拓展字符的使用范围。但是在表示正则语义时,"\"会让一些使用场景显得很别扭.
别扭的"\\"
有这样一个场景:有一个文件名数组,需要从中筛选出以"json"结尾的文件名,在iOS中使用传统的正则匹配大概会这样:
Swift
let files = ["filea.txt", "fileb.json", "filec.json", "filed.pdf"];
let regex = try? NSRegularExpression(pattern: ".*\\.json", options: NSRegularExpression.Options.caseInsensitive)
let result = files.filter( { (regex?.matches(in: $0, range: NSRange(location: 0, length: $0.count)))?.isEmpty == false } )
print("result:\(result)");
这时就会发现本来只是需要转义"."的"\"需要出现两次,为什么? 这里实际上经历了两个阶段的解析:
- 所在语言的字符串解析:将"\\."解析为 \.
- 正则引擎的正则解析: 将 \. 解析为 .
为什么必须是"\\"
在正则表达式(ERE)中, 由于"."有特殊元字符含义,所以使用其本来的意义时就需要使用"\."来表达.
而在swift中,"\"也是一个特殊含义的字符: 转义字符, 所以直接出现的"\"会被认为应该和紧跟其后的字符连起来组合称为一个新字符使用, 所以单独使用"\"会被认为是转义字符,而不是"\"本身,当需要表示"\"时就需要使用"\\"来表示一个真正意义上的"\". 所以在正则引擎中的 \. 在字符串中就会被表示成"\\.".
这里还有一个问题,在iOS的字符串中,常见的转义字符并不多:
\n \t \\ \" \r \0 \u{}
所以原则上讲,使用"\."来表示正则中的"."并没有歧义,那为什么不能直接使用"\."来表示呢?最要的原因是防止潜在的隐患.试想一下, 本来在字符串中出现"\"时,正常的逻辑是其后会出现一个需要被转义的字符,然后发现紧跟其后的是字符".", 并不是一个可以被转义的字符, 如果不报错就意味着"\anything"是一个合理的存在,在这种情况下编译器就无法进行正确的静态分析,无法提示错误,也会造成可读性下降(这他么难道是一个我不知道的转义字符). 所以, 不明语义的转义是不被允许的,这样就可以:
- 防止拼写错误等隐藏bug;
- 便于进行静态分析,提示错误,增强可阅读性;
- 保持和其他主流语言(C/C++/Python/JavaScript/Java等)的逻辑一致性
其他实现方式
事实上,在swift中并不是必须使用这种看起来别扭的语法,有很多方式可以避开这种逻辑.
原始字符串
可以使用原始字符串来避开转义逻辑,上边的实现就可以使用
Swift
let regex = try? NSRegularExpression(pattern: #".*\.json"#, options: NSRegularExpression.Options.caseInsensitive)
来进行替换,这样就可以避开所在语言字符串的转义解析.
使用新的API
在Regex中已经直接使用"/pattern/" 来表示匹配模式,上边的实现可以修改为:
Swift
let result = files.filter({ $0.wholeMatch(of: /.*\.json/) != nil })
其他非正则的方式
可以借助于其他非正则封装的方式来实现,比如 `hasSuffix`之类.