正则表达式可在在线工具中使用,但与 NSRegularExpression 不一致

regex works in online tool but doesn't agree with NSRegularExpression

do {
    // initialization failed, looks like I can not use "\" here
    let regex = try NSRegularExpression.init(pattern: "(?<!\)\n")

    let string = """
    aaabbb
    zzz
    """
    
    // expect "aaabbb\nzzz"
    print(regex.stringByReplacingMatches(in: string, options: [], range: NSMakeRange(0, string.count), withTemplate: "\n"))
} catch let error {
    print(error)
}

这里我想把我的字符串中的“\n”替换成“\\n”,但是一开始就失败了,错误信息是

// NSRegularExpression did not recognize the pattern correctly.
Error Domain=NSCocoaErrorDomain Code=2048 "The value “(?<!\)
” is invalid." UserInfo={NSInvalidValue=(?<!\)
}

正则表达式已在 regular expression 101 中测试过,所以它是正确的,只是由于某些原因在 Swift 中不起作用。

我该怎么做?

基于 Larme 的评论:

in Swift, \ (double back slash) in a String is for "having a ``, as you see in the error, you have (?<!\), but it means then that you are escaping the closing ), so you have a missing closing ). I'd say that you should write then "(?<!\\)\n"?

我终于弄清楚发生了什么以及如何解决它。

问题是反斜杠

在Swift中,双引号内的反斜杠将被视为转义序列,像这样

// won't compile
// error: Invalid escape sequence in literal
let regex = try NSRegularExpression.init(pattern: "(?<!\)\n")

如果我们再添加一个反斜杠,可以吗?

不,因为这 2 个反斜杠将被视为即将结束的单个 转义字符

// compile but get a runtime error
let regex = try NSRegularExpression.init(pattern: "(?<!\)\n")

因此出现运行时错误

NSRegularExpression did not recognize the pattern correctly.
Error Domain=NSCocoaErrorDomain Code=2048 "The value “(?<!\)
” is invalid." UserInfo={NSInvalidValue=(?<!\)

为了表明我们需要的是文字反斜杠,实际上我们需要 4 个反斜杠

let regex = try NSRegularExpression.init(pattern: "(?<!\\)\n")

前两个反斜杠代表一个转义字符,后两个代表一个文字反斜杠。

这些好像很麻烦

更好的方法

幸运的是,从Swift5开始,我们可以用一对#来做到这一点

// works like in online tool
let regex = try NSRegularExpression.init(pattern: "(?<!\)\n")

另一件事

值得注意的是,正则表达式的初始化并不是唯一需要特殊处理的东西

// withTemplate
print(regex.stringByReplacingMatches(in: string, options: [], range: NSMakeRange(0, string.count), withTemplate: #"\n"#))

// As a comparison, this is OK
print(string.replacingOccurrences(of: "\n", with: "\N"))