正则表达式惰性匹配

Regex lazy matching

我有这个字符串

(Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/data Safari/data2) /Producer (Skia/PDF m80) /CreationDate (D:20200420090009+00'00') /ModDate (D:20200420090009+00'00')

我想在 ( 或 ) 之前没有任何 \ 的情况下获得 () 的第一次出现。那样的话我会得到

(Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/data Safari/data2)

我正在使用这个正则表达式

\([\s\S]*[^\]{1}\)?

但是我得到了整个字符串

你的正则表达式可以这样分解。

[空格和换行符是为了清楚起见]

\(             match a literal (
  [\s\S]*      match 0 or more of whitespace or not-whitespace (anything)
  [^\]{1}     match 1 thing which is not \
\)?            optionally match a literal )

regex101 demo

就是那个 [\s\S]* 什么都吃完了。

最后的?并不意味着懒惰,它使匹配)成为可选的。为了懒惰,? 必须放在开放式限定符前面,例如 *?+?{3,}?{1,5}?.


为了只匹配第一组括号,我们想要延迟匹配未转义括号之间的任何内容。惰性匹配任何东西都很容易 .*?.

匹配未转义的括号有点难。我们可以匹配 [^\]\),但这需要一个字符来匹配。如果左括号位于字符串的开头,这将不起作用,因为 ( 之前没有字符。我们也可以通过匹配字符串的开头来解决这个问题:(?:[^\]|^)\).

(?:           non-capturing group
  [^\]         match a non \
  |             or
  ^             the beginning of the string
) 
\(            match a literal (
  .*?         lazy match 0 or more of anything
[^\]         match a non \ 
\)            match a literal )

regex101 demo

但这将被 () 挫败。 It will match all of ()(foo).

(?:[^\]|^) 匹配字符串的开头。 \( 匹配第一个 (。剩下 .*?[^\]\) 看着 )(foo)。第一个 ) 不匹配,因为没有前导字符,它已经被消耗了。所以 .*? 吞噬字符直到他的 o) 匹配 [^\]\).

边界问题negative look behinds解决得比较好。 (?<!\) 表示前面的字符不能是 \ ,它根本不包含任何字符。 Lookbehinds 不会消耗它们匹配的内容,因此它们可用于向后和向前窥视。大多数(但不是全部)正则表达式引擎都支持它们。

(?<!\) \(    match a literal ( which is not after a \
  .*?         lazy match 0 or more of anything
(?<!\) \)    match a literal ) which is not after a \

regex101 demo


但是,有一些库可以解析用户代理。 ua-parser 有多种语言的库,