高效学习正则表达式Regex
2016-07-27 / modified at 2022-04-04 / 1.3k words / 5 mins
️This article has been over 2 years since the last update.

还在用C语言的方法进行for循环处理字符串吗?是时候使用Regex了。

以前看过30分钟入门等网站,最后大多数情况学了五分钟就放弃了,虽然也会用一点Regex,但是基本也就会一个*,没有系统的学习过,今天写一下常见的Regex应用场景。

  • 验证功能
  • 词汇提取
  • 找现成的表达式

验证功能

验证功能这个网上基本烂大街,就随便举例两个吧

判断http地址

1
2
3
4
5
//判断http地址
^(http[s]?:\/\/)?[^\s]+

//注意这个也是顶级的域名,是可以在内网上存在的
http://aabb/

计算器正则的验证(运算符,数字,括号,无空格)

1
2
3
4
^[\(\d]?[\d\+\-\*\/\(\)]*[\)\d]?$

//testcase
3+4*5-((4+2)/(3+5)+5)

HTTP框架

许多HTTP框架的路由功能都是通过Regex实现的,最后维护一个List<Pattern>,当有请求来时,对List进行for循环匹配。

提取功能

还在用Substring吗?是不是经常遇到数组越界?试试regex吧!此功能在Regex叫做Group,它在完成了Match之后才有用,我们在写正则表达式时,使用 ()进行分组。

1
2
3
4
5
6
7
8
9
10
public static List<String> splitByRegex(String line,String regex){
List<String> captures =new ArrayList<>();
Matcher matcher = Pattern.compile(regex).matcher(line);
while (matcher.find()) {
for (int i = 1; i < matcher.groupCount() + 1; i++) {
captures.add(matcher.group(i));
}
}
return captures;
}

提取地址

比如我想提取HTTP字符串中的地址,除了自带的URI类以外,还可以用Regex,比如运行下面的匹配,被括号括住的表示将被提取出来。

输出将是这样:

1
2
String s = "https://mediatemple.net";
splitByRegex(s,"MESSAGE \"(\w*)\" CODE (200)") => [mediatemple.net]

提取命令

这个在一些硬件类的通信或者某些OJ题目中经常出现(特别是某为),需要自己实现Parse,比如下面是某个输入命令,我希望提取其中去掉引号的消息与CODE

输出是这样的

1
2
String s = "MESSAGE \"HELLO\" CODE 200";
splitByRegex(s,"MESSAGE \"(\w*)\" CODE (200)") => [HELLO,200]

短信提取

这个在网上貌似有很多开源的方案,下文是一个例子,当然比不上手机ROM自带的云识别,但是还是可以凑合用的,如下(省略Java代码)。

1
2
3
4
5
6
7
8
//regex
您尾号[\d]{4}卡([\d]{1,2}月[\d]{1,2}日)([\d\d]{2}:[\d\d]{2})POS支出\(消费\)([\d,.]*)元,余额([\d,.]*)元。【工商银行】$

//testcase
您尾号123472718:27POS支出(消费)20元,余额3,062.13元。【工商银行】

//result
[727日,18:27,20,3,062.13]

再来一个Uber的例子

1
2
3
4
5
6
7
8
//regex
^【UBER优步】.*(\d{4}).*

//testcase
【UBER优步】还有一步,即可享受时尚风格、非凡体验。输入 9445 以确认您的账户。

//result
[9445]

路由功能

如下是简单正则表达式路由实现,Consumer是JDK8中的forEach回掉接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class Router {

Map<String, Consumer<List<String>>> map = new LinkedHashMap<>();
void route(String message) {
map.keySet().forEach(regex -> {
if (message.matches(regex)) {
map.get(regex).accept(splitByRegex(message, regex));
}
});
}

void add(String regex, Consumer<List<String>> consumer) {
map.put(regex, consumer);
}
}

本部分可用于解析硬件命令、高难高耗时OJ的任务。

多路由情况下,可能匹配被覆盖,此时考虑使用惰性求值(xxx?)

More

更多的例子可以在

  • RegexLib上去搜索现成的库
  • 参考 JavaGrok,PyGrok等项目实现更完善的正则表达式

Intellij

在Intellij下,可以点击正则表达式的位置,然后Alt + Enter,可以直接Inject Language,或者进行Regex的编辑与Check,希望大家能够用到。

Sed命令

调试后台报错

1
2
# 出现 Caused by后立即停止滚动,以便看原因
tailf catalina.log | sed -e "/Caused by/q"

使用ag进行查询

1
2
# find urls in markdown file
ag -o '\[(.+)\]\((http[s]?://[\w\d/\.]+)\)' --nofile --nonumber **/*.md

使用rg进行查询

1
2
3
4
5
# parse kv in package.json
rg '\s+"(.+)":\s+"(.+)"' package.json -r '$1|$2' -NI

# find urls in markdown file
rg '\[(.+)\]\((http[s]?://[\w\d/\.]+)\)' -r '$1 | $2' -NIoU -t md | grep -v '|'

简单的递归下降Parser实现处理dollar符号

1
Pattern.compile("\\$([A-Za-z0-9_]+|\\{[A-Za-z0-9_.]+\\}|\\$)");

关于高效学习的一点心得

如果自己愿意花时间学的话,建议参考

  1. 电子书《学习正则表达式》(图灵程序设计丛书)
  2. http://regexr.com/,内部有example与expian功能,方便理解

关于书,个人推荐去下载Kindle电子版,搬家方便,不推荐去网上找盗版书。各位月收入上万的哥们,每小时的工作收入税前至少50,消费了自己的时间去找扫描的低质量书籍,不如直接买更划算。

学习总成本:

  1. 一本电子书书,20块搞定,跨平台,随时读
  2. 3个晚上业余时间,包括阅读与码字,约8小时

学习成果:

  1. Regex的匹配与提取代码编写
  2. Regex在Intellij与在线网站的调试经验

百忙之中,学了一点,还是挺划得来。

当然,后续又花了2个月的学习,总之正则表达式与Shell一样,适合现学现用。