四个标志各管什么
不勾 g 就只找第一处匹配。要数个数或者全部替换,必须打开。
i 忽略大小写。m 让 ^ 和 $ 按行生效,而不是只认整段字符串的头尾,从多行日志里挑行首时就靠它。
s 让 . 也能匹配换行。默认情况下点号不跨行,所以要整块抓住横跨好几行的内容,得开这个标志。
值得记住的零件
\d数字,\w字母数字和下划线,\s空白。写成大写(\D\W\S)意思相反。+一个或多个,*可以没有,?可有可无,{2,4}两到四次。^开头,$结尾,\b单词边界。(...)是捕获组,会留在结果里;(?:...)只分组不捕获。- 写成
(?<name>...)就能用名字而不是编号取值。
贪婪与懒惰
.* 会一路吃到底再往回退。对 <b>一</b><b>二</b> 用 <b>.*</b>,整段会被当成一处匹配吞掉。
加个问号写成 <b>.*?</b>,它会在最早能停的地方停下,于是分成两处。要取标签或引号里面的东西,一般都该用这种写法。
为什么放在单独的线程里跑
量词一旦嵌套,正则就可能失控。给 (a+)+b 喂三十来个 a 就是这种情况:引擎要把这些 a 的每种拆法都试一遍,工作量成数量级地涨。输入明明很短,却要跑上好几分钟。
这个工具把正则放在与页面分开的线程上执行,超过一秒就把那条线程整个结束掉。因为这一切发生在一次调用内部,中途没有办法叫停。所以就算填进失控的表达式,页面照样能动。
同样的事发生在服务器上,就会有一个请求把 CPU 占住。如果你的代码会把用户输入交给正则,尤其要检查有没有嵌套的量词。
各种语言的写法并不一样
这里跑的是 JavaScript 的正则。后行断言((?<=...))在当前浏览器可用,但递归、原子组这些 PCRE 独有的功能并不存在。
Python 的命名组写作 (?P<name>...),Java 则要在字符串里再转义一层反斜杠。把这里调好的表达式搬过去时,记得换成那门语言的写法。