学习正则表达式

学习正则表达式

本文写于 2020 年 9 月 8 日

“抄自”正则表达式 30 分钟入门

最重要的是——请给我 30 分钟,如果你没有使用正则表达式的经验,请不要试图在 30 秒内入门——除非你是超人 😃 —— 出自原文

如果你用过 Windows,你很可能见过这样一个东西:*.doc。这是用来匹配所有的 doc 文档的,* 叫做「通配符」。顾名思义,就是所有以 .doc 结尾的文件,通通匹配。

正则表达式与这类似,即通过符号规则,对字符串进行更为精确的匹配。

寻找一个单词

he,这就是一个非常简单的正则表达式,但他匹配的是 hehello, hey, her, he, she......真的不太精确。

我们如果只想匹配 he,就需要加上一些条件:\bhe\b

这里的 \b 就是一个正则表达式的规则(叫做元字符),代表单词的开头或者结尾

需要注意的是,这里 \b 匹配的并不是空格或者逗号之类的字符,而仅仅是匹配单词的开头、结尾的「位置」。

寻找两个相邻单词

我们想要找到一句话中,相邻的两个单词:hello world。这该怎么做呢?

使用 \bhello\b *\bworld\b

别急着晕!这其实并不难。

前半部分和后半部分我们都认识,两对 \b 分别框住了 hello 与 world,是刚刚学到的匹配单词始末的符号。

那么问题就是中间的 *,空格我们不陌生,主要就是这个 *

* 是元字符(\b 就是元字符),但它跟前面的两者不同——它不代表符号,也不代表位置,代表的是数量。它意味着在自己之前的内容可以连续重复任意次数,使得整个表达式得到匹配。

也就是说这就意味着我们可以匹配 hello world, hello world, hello 一百万个空格 world!中间任意多的空格我们都可以忽略。

+ 也有同样的作用,但与 * 不同的是,+ 号只能匹配重复一次或以上* 则可以匹配 0 次。

寻找首字母

我想寻找 a 开头的字符串该如何?

\ba\w*\b 即可。\w 顾名思义,代表一个 word。我们寻找「字符起始-a-任意个任意字符-结束」就可以找到 a 开头的字符串了。

寻找始末

经常会有 start 啦啦啦,啦啦啦 end 这样的段落,前面一个标签、后面一个标签。

如果我们想要匹配这两个标签以及之间的所有东西该怎么办?

.\bstart\b.*\bend\b 即可。

. 也是一个元字符,它匹配的是除了换行符意外的任意字符。比如字母 a, b;符号 &, , !;空格 ......他都能匹配。

比如 .* 就可以匹配整段文字!因为 . 代表非换行符,* 代表重复任意次数,那么此时就会匹配最大次数的非换行符,也就是整段文字。

对于这里,就会匹配 start 与 end 以及中间的所有字符。

我与重复不共戴天

\b 表示一个数字,那我如果需要匹配电话号码,岂不是需要 \d\d\d\d\d\d\d\d\d\d\d

当然不可以。

我们可以写成 \d{11} 就可以了。

有时候我们需要模糊匹配,例如昵称不得超过 12 个字符,但也不得少于 3 个字符,我们就可以使用 ^.{3,12}$ 了。逗号后不要加空格

这里 ^ 代表句子开头,$ 代表句子结尾,. 代表任意换行符外的字符,{3,12} 代表 . 匹配的字符数量为 3 到 12 个。

那我们来匹配 QQ 号试一试吧。

对于 QQ 号而言,首先有一个最短位数,姑且当他是 5 吧。其次 QQ 号不可以以 0 开头。

那我们开头就可以写 [1-9],后面写上 \d{4,} 就可以了。

这里的 [] 可以理解是一个集合。你想要匹配什么符号,放进去就可以了。

例如 [,!?] 可以匹配三个符号,[0-9] 的意思和 \d 是一样的。

但这只是单个符号的重复,我们希望几个符号成组的重复,就需要 (\w\d-5003){5} 这种写法了。

他的意思就是,将「一个字母 一个数字 -5003」重复五次。

两种情况

如果需要同时匹配多个国家的手机号码,需要怎么办呢?

正则A|正则B 就可以了。不要带空格

我不要 xxx 情况

我们还会经常遇到一个问题,比如:不带 a 字符的单词。

正则表达式当然可以轻易的完成这种需求,例如 \W 匹配任意不是字母,数字,下划线,汉字的字符;\S 匹配任意不是空白符的字符;\D 匹配任意非数字的字符\B 匹配不是单词开头或结束的位置;[^x] 匹配除了 x 以外的任意字符;[^aeiou] 匹配除了 aeiou 这几个字母以外的任意字符。

我需要重复的两个字母

这个和之前的 {2} 可不一样,对于 {2} 而言,我们是无法判断两个重复的任意字符的。

这个时候就需要后向引用了。

后向引用用于重复搜索前面某个分组匹配的文本。什么意思呢?

还是看标题的问题,我们如何用正则做到这一点?

([a-zA-Z])\1

首先不看小括号,[a-zA-Z] 指匹配一个字母,用括号扩起来就是一个「组」。整个正则从左到右,由小括号扩起来的就是一个组,编号是从 1 开始计算。(分组 0 对应整个正则表达式)

我们用 \1 表示之前组 1 匹配到的东西——也就是组 1 匹配到的字符。这就重复了一个字母两次了。

当然这样一个个数组数是很痛苦的,而且不可读。正则表达式可以通过 (?<组名>正则表达式) 将该组命名为尖括号内的名字了。(也可以用 '' 代替 <>

零宽断言

我们会经常去查找在某些内容之前或之后的东西,但并不包括该内容,类似于 \b ^ $

(?=表达式) 也叫零宽度正预测先行断言,是不是看不懂?他的意思就是我要匹配:该条件出现的位置的后面能匹配表达式。

还是看不懂?

例子来了:匹配以 ing 结尾的单词,但是不要 ing。

\b\w+(?=ing\b)

现在理解了吧,也就是我们将不想要的结尾,丢到 (?=) 里面,就可以即匹配到,又不带上它。

除了结尾,我们还有开头:(?<=exp),也叫零宽度正回顾后发断言。

还是例子,我们需要匹配 re 开头的单词,但我不要 re。

(?<=\bre)\w+\b

我们将不想要的开头,丢到 (?<) 里面,就可以即匹配到,又不带上它。

同样,和「反义」一样,我们一样拥有负向零宽断言,可以确保某个字符没有出现,但并不去匹配它。

(?!表达式)

\d{3}(?!\d) 即为匹配三位数字,并且这三位数字的后面不能是数字。

(?<!exp)

(?<![a-z])\d{7} 匹配前面不是小写字母的七位数字。

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,他会去匹配尽可能多的字符。

以这个表达式为例:a.*b

它将会匹配最长的以 a 开始,以 b 结束的字符串。

如果用它来搜索 aabab 的话,它会匹配整个字符串 aabab。

这被称为「贪婪匹配」。

但有时,我们更需要「懒惰匹配」,也就是匹配尽可能少的字符。

前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。

这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。

例如 a.*?b 匹配最短的,以 a 开始,以 b 结束的字符串。

如果把它应用于 aabab 的话,它会匹配 aab(第一到第三个字符)和 ab(第四到第五个字符)。

我们还可以用到重复上,{n,}?,意为重复 n 次以上,但尽可能少重复。

注释

(?#comment),小括号加上 ?# 就能写上注释。

实践一下

如何匹配身份证号?

^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$

我们如何匹配 email 地址?

[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)_@(?:[\w](?:[\w-]_[\w])?\.)+[\w](?:[\w-]*[\w])?

我们如何匹配网址?

[a-zA-z]+://[^\s]*

(完)

posted @ 2020-09-08 14:26  徐航宇  阅读(187)  评论(0编辑  收藏  举报