实践正则表达式的多种使用方式

2,560 阅读7分钟

前言

正则,毋庸置疑,是一个无比强大的工具。如果仔细留意,我们可以在源代码(各种语言)、编辑器、开发者工具、命令行等地方看到正则的身影。学习使用正则,不仅有趣,也是提升工作效率的一条捷径。

基础概念

正则的常用概念可以参考下表,主要熟记字符类型、锚点、转义字符、量词等。

基础用法相信大家都很快理解,以下主要介绍各种正则断言。

断言

断言的实用性相当于给一段正则表达式提供了一个锚点,笔者在日常工作实践中,使用频率较高。

正先行断言

模式:x(?=y)

匹配出 x 表达式,但 x 后面必须跟着 y 表达式。

负先行断言

模式:x(?!y)

匹配出 x 表达式,但 x 后面不能跟着 y 表达式。

正后发断言

模式:(?<=y)x

相当明显,匹配出 x 表达式,但 x 前必须出现 y 表达式。

负后发断言

模式:(?<!y)x

相当明显,匹配出 x 表达式,但 x 前不能出现 y 表达式。

\1

\1 其实就是上一个匹配到分组引用。

比如我要在 I like JJLin and Huahua~ 中匹配 Huahua,如图:

当然以下方式也可达成:

  1. 使用量词 + 或 *

  2. 或者用全局搜索标志 g

  3. 使用范围 {2}

不过,虽然条条大路通罗马,但还是需要注意匹配的 hua 数量。比如方法 1、2 都会尽可能多的匹配。

使用正则

JavaScript 中的 RegExp 对象

exec 方法

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。

/(?<=appear\s)(\w+)[^\d]+(\d+)/g.exec("apple yummy, appear angel id 1030");
// => ["angel id 1030", "angel", "1030", index: 20, input: "apple yummy, appear angel id 1030", groups: undefined]

可以看出数组第一项为匹配的全部字符串。接下来展示了匹配到的分组(\w+) -> angel,(\d+) -> 1030。

当然笔者个人觉得 exec 最容易踩坑的点:当 exec 方法使用 "g" 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。

改写以上例子,我们将正则存为一个变量:

const regexAngelAndId = /(?<=appear\s)(\w+)[^\d]+(\d+)/g;

regexAngelAndId.exec("apple yummy, appear angel id 1030");
// => ["angel id 1030", "angel", "1030", index: 20, input: "apple yummy, appear angel id 1030", groups: undefined]
regexAngelAndId.lastIndex;
// => 33
regexAngelAndId.exec("apple yummy, appear angel id 1030");
// => null
regexAngelAndId.lastIndex;
// => 0

可以看到。当我们存为一个正则字面量时,查找将从该正则表达式的 lastIndex 属性指定的位置开始。(test() 也会更新 lastIndex 属性)。因此第二次就再也找不到相同的值了,结果自然而然为 null。

使用 exec 时,务必还是留意这一点。

test 方法

test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。

const regexAngelAndId = /(?<=appear\s)(\w+)[^\d]+(\d+)/g;

regexAngelAndId.test("apple yummy, appear angel id 1030");
// => true
regexAngelAndId.lastIndex;
// => 33
regexAngelAndId.test("apple yummy, appear angel id 1030");
// => false
regexAngelAndId.lastIndex;
// => 0

同样和 exec 方法需要留意 "g" 标志,不再赘述。

上述两个方法在 JavaScript 中很常用,务必牢记。

正则表达式静态属性

  • RegExp.$1-$9
  • RegExp.input ($_)
  • RegExp.lastMatch ($&)
  • RegExp.lastParen ($+)
  • RegExp.leftContext ($`)

以下都是正则对象上的静态属性,一般来说使用频率不大(或者我没发现?),具体使用方法可参考 MDN 文档 - RegExp

当然还有很多其他方法以及属性,都可自查文档。

JavaScript 中的 String 对象

在字符串对象上使用正则也是十分常见的操作。大家可能知道 replace、match 方法可以用正则,但是平常还有 search、split,甚至很新鲜出炉的 matchAll 方法都可以帮助我们更好的在字符串上使用正则。

这里比较有趣的是 matchAll 方法,这个方法的出现完美的取代了用 Regexp.exec()方法循环匹配信息。

使用 matchAll 会得到一个迭代器的返回值,可以使用 for...of, 扩展运算符, 或者 Array.from() 来进行取值。十分便利。

关于这些 API 最好的学习方式,可参考文档:MDN 文档 - String

编辑器

在我们的 vscode 里也有正则的用武之地!

比如说我们需要全局查找文件中 console.error 的值,并将其替换。就可以使用以下操作。(普通的文本查找可做不到这种姿势)

前提是要勾选使用正则的选项(红色箭头所指处)

或者单个文件查找替换也是可以的。

浏览器

在我们熟悉的浏览器中,可以使用正则来筛选网络请求。

当然如果存在极大量日志的情况下,正则也可以帮助我们快速定位某项日志。

当然控制台 source 面板身为一个编辑器,也肯定少不了正则查找的功能。

命令行

这里主要介绍命令行中最常用的文本处理工具 grep。(除此之外还有 sed、awk)。grep 全名为"Global search Regular Expression and Print out the line"。

见名思义,grep 可以与正则结合进行文本匹配。

假设我们有文本如下:

$ cat tinytext
apple yummy
appear angle
and wisper: "ANGLE"

我们想要匹配 angle 单词的行

$ grep "angle" tinytext
appear angle

成功!

如果我们想要忽略大小写敏感,可以加上 "-i" 或者 "--ignore-case" 选项。(默认 grep 是大小写敏感)。

$ grep -i "angle" tinytext
appear angle
and wisper: "ANGLE"

许多时候,我们并不想匹配行,而想匹配关键字,此时可以加上 "-o" 或则 "--only-matching" 选项。

$ grep -io "angle" tinytext
angle
ANGLE

以上我们都只是普通文本匹配,grep 支持基本正则表达式,我们可以使用 "-E" 或者 ”"--extended-regexp" 选项使用扩展正则表达式。

比如我们想匹配出 yummy 前的单词,我稍微一考虑,就写出以下规则:匹配至少大于一个 a-z 字符组成的,并且后面必须跟着一个空格及 yummy 的一个单词。

$ grep -oE "[a-z]+(?=\syummy)" tinytext
grep: repetition-operator operand invalid

但是,不幸的发现失败了。经过一番查找,发现 grep 默认不支持 "?=" 操作符。但是不要急,在 mac 系统中可以通过以下命令升级我们的工具箱。

$ brew insall grep
...
...
All commands have been installed with the prefix "g".

于是我们获得了 ggrep 这个工具,然后可以加上 "-P" 或者 "--perl-regexp" 使用兼容 Perl 的正则引擎(得以支持 "?=" 等操作符)。

$ ggrep -oP "[a-z]+(?=\syummy)" tinytext
apple

成功了!

相信经常用命令行处理工具的同学都会认可 grep 的实用性(前端同学可能比较陌生,但是建议学习)

匹配 ifconfig 中的 ip 地址

笔者有个困扰,经常开发项目的时候需要知道自己笔记本的 ip 地址。这时候我会敲入 ifconfig 然后肉眼一顿搜索,每次都得浪费几秒,才能找到 ip 地址。

此时 grep 命令发挥了作用

$ ifconfig | ggrep -oP "(?<=inet\s)\d{1,3}(\.\d{1,3}){3}"
127.0.0.1
192.168.31.254

成功获取到 ip 地址。但是 127.0.0.1 我们其实并不需要。我们可以加上选项 "-v" 或者 "--invert-match" 反向排除掉本地 ip。

$ ifconfig | ggrep -oP "(?<=inet\s)\d{1,3}(\.\d{1,3}){3}" | ggrep -vP "127[\d\.]+"
172.20.10.13

噌噌噌!简简单单~我们可以将以上命令配置为 bash 别名 alias get-ip='...',这里不详细介绍。我之前的博客有提到。

$ get-ip
192.168.31.254

匹配对应端口的 node 应用程序进程 PID

比如我们需要寻找端口 8001 的 node 应用程序进程 PID。

我们通过命令 lsof -i tcp:8001

$ lsof -i tcp:8001
COMMAND   PID        USER   FD   TYPE            DEVICE SIZE/OFF NODE NAME
node    87701 yanguangjie   23u  IPv6 0x42c450e30b40bcb      0t0  TCP *:vcom-tunnel (LISTEN)

使用 grep 匹配出 node 应用程序 PID。

$ lsof -i tcp:8001 | ggrep -oP "(?:node[^\d]+)\d+(?=\s)" | egrep -o "\d+"
87701

要问匹配 PID 出来有什么用?当然是强制杀掉它了!

$ kill -9 $(lsof -i tcp:8001 | ggrep -oP "(?:node[^\d]+)\d+(?=\s)" | egrep -o "\d+")

当然如果觉得以上方式略显麻烦的同学,可以基于以上脚本写个工具,减少重复工作。

这里笔者写了一个简易的工具 k-port,若感兴趣的同学可以去看看以及使用。

$ npm install -g k-port
$ k-port 8001,8003
total ports: [ '8001', '8003' ]

get PID 2152 by port: 8001
kill PID 2152 ok.
get PID 2164 by port: 8003
kill PID 2164 ok.

寻找npm包的所有指定版本

快速过滤出 react 的所有 alpha 版本。

$ npm view react versions | ggrep -oP "[^\s]+alpha[^\s]+"

小结

到此为止,也只是比较肤浅的介绍了一下有限的,我知道的正则的各种使用方式,当然~所以欢迎大家补充。正则是实践性极强的一门工具,或者说是艺术也不为过。多上手练习和使用。

感谢阅读~

参考资料