RegExp正则表达式基础语法示例教程
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">简介</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">在线工具</a></li></ul><li><a href="#_label1">元字符</a></li><ul class="second_class_ul"></ul><li><a href="#_label2">分组与引用</a></li><ul class="second_class_ul"></ul><li><a href="#_label3">运算符优先级</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">贪婪模式</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">总结 </a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>简介</h2><p>在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。</p>
<p>很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是<code>*</code>和<code>?</code>。如果你想查找某个目录下的所有的Word文档的话,你会搜索<code>*.doc</code>。在这里,<code>*</code>会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂。</p>
<p>假设你在一篇英文小说里查找 hi,你可以使用正则表达式 <code>hi</code>。</p>
<p>这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是 h,后一个是 i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配 hi,HI,Hi,hI 这四种情况中的任意一种。</p>
<p>不幸的是,很多单词里包含hi这两个连续的字符,比如 him,history,high 等等。用 <code>hi</code>来查找的话,这里边的 hi 也会被找出来。如果要精确地查找 hi 这个单词的话,我们应该使用<code>\bhi\b</code>。</p>
<p><code>\b</code> 是正则表达式规定的一个特殊代码(将其称之为 <strong>元字符</strong>),代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。</p>
<p>假如你要找的是hi后面不远处跟着一个Lucy,你应该用<code>\bhi\b.*\bLucy\b</code>。</p>
<p>这里,<code>.</code>是另一个元字符,匹配除了换行符以外的任意字符。<code>*</code> 同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定<code>*</code> 前边的内容可以连续重复使用任意次以使整个表达式得到匹配。</p>
<p>因此,<code>.*</code>连在一起就意味着任意数量的不包含换行的字符。现在 <code>\bhi\b.*\bLucy\b</code> 的意思就很明显了:先是一个单词 hi,然后是任意个任意字符(但不能是换行),最后是 Lucy 这个单词。</p>
<p>如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:</p>
<p><code>0\d\d-\d\d\d\d\d\d\d\d</code>匹配这样的字符串:以 0 开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。</p>
<p>这里的<code>\d</code>是个新的元字符,匹配一位数字(0,或1,或2,或……)。<code>-</code> 不是元字符,只匹配它本身——连字符(或者减号,或者中横线,或者随你怎么称呼它)。</p>
<p>为了避免那么多烦人的重复,我们也可以这样写这个表达式:<code>0\d{2}-\d{8}</code>。这里<code>\d</code>后面的<code>{2}</code>(<code>{8}</code>)的意思是前面<code>\d</code>必须连续重复匹配 2 次(8次)。</p>
<p>总的来说,正则表达式一种特殊的编程语言,专门用来匹配一些特定的字符串。正则表达式一般不会单独使用,通常会结合具体的编程语言(例如 C++、Python、Perl等)或者工具(vim等)使用。</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>在线工具</h3>
<p>正则表达式本身比较复杂,可以借助一些工具来验证所写的正则表达式是否符合预期。</p>
<ul><li><a href="https://jex.im/regulex/" rel="external nofollow" target="_blank">Regulex - JavaScript Regular Expression Visualizer.</a></li></ul>
<p class="maodian"><a name="_label1"></a></p><h2>元字符</h2>
<p>基本元字符</p>
<table><thead><tr><th>Metacharacter</th><th>Descriptions</th><th>Examples</th></tr></thead><tbody><tr><td><strong>\</strong></td><td>转义字符,使后面的字符失去特殊含义或者标记为特殊字符</td><td><code>\.</code> 匹配实际的点号而不是任意字符,<code>\n</code> 匹配一个换行符</td></tr><tr><td><strong>^</strong></td><td>匹配字符串的开始位置</td><td><code>^abc</code> 匹配以 abc 开头的字符串</td></tr><tr><td><strong>$</strong></td><td>匹配字符串的结束位置</td><td><code>xyz$</code> 匹配以 “xyz” 结尾的字符串</td></tr><tr><td><strong>.</strong></td><td>匹配除换行符(\n)外的任意单个字符</td><td><code>a.b</code> 匹配 “aab”, “a1b”, “a b” 等</td></tr><tr><td><strong>*</strong></td><td>匹配前面的子表达式零次或多次</td><td><code>zo*</code> 能匹配 “z” 以及 “zoo”</td></tr><tr><td><strong>+</strong></td><td>匹配前面的子表达式 1 次或多次</td><td><code>zo+</code> 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”</td></tr><tr><td><strong>?</strong></td><td>匹配前面的子表达式零次或一次</td><td><code>do(es)?</code> 可以匹配 “do” 或 “does”</td></tr><tr><td><strong>{n}</strong></td><td>n 是一个非负整数。匹配确定的 n 次</td><td><code>o{2}</code> 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o</td></tr><tr><td><strong>{n,}</strong></td><td>n 是一个非负整数。至少匹配n 次</td><td><code>o{2,}</code> 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o</td></tr><tr><td><strong>{n,m}</strong></td><td>m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次</td><td><code>o{1,3}</code> 匹配 “fooooood” 中的前三个 o</td></tr><tr><td><strong>x|y</strong></td><td>匹配 x 或 y</td><td><code>z|food</code> 能匹配 “z” 或 “food”</td></tr><tr><td><strong></strong></td><td>字符集合。匹配所包含的任意一个字符</td><td><code></code> 可以匹配 “plain” 中的 ‘a’。</td></tr><tr><td><strong>[^xyz]</strong></td><td>负值字符集合。匹配未包含的任意字符</td><td><code>[^abc]</code> 可以匹配 “plain” 中的’p’、‘l’、‘i’、‘n’</td></tr><tr><td><strong></strong></td><td>字符范围。匹配指定范围内的任意字符</td><td><code></code> 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符</td></tr><tr><td><strong>[^a-z]</strong></td><td>负值字符范围。匹配任何不在指定范围内的任意字符</td><td><code>[^a-z]</code> 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符</td></tr><tr><td><strong>\b</strong></td><td>匹配一个单词边界,也就是指单词和空格间的位置</td><td><code>er\b</code> 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’</td></tr><tr><td><strong>\B</strong></td><td>匹配非单词边界</td><td><code>er\B</code> 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’</td></tr><tr><td><strong>\cx</strong></td><td>匹配由 x 指明的控制字符,x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符</td><td><code>\cM</code> 匹配一个 Control-M 或回车符</td></tr><tr><td><strong>\d</strong></td><td>匹配一个数字字符</td><td>等价于 <code></code></td></tr><tr><td><strong>\D</strong></td><td>匹配一个非数字字符</td><td>等价于 <code>[^0-9]</code></td></tr><tr><td><strong>\f</strong></td><td>匹配一个换页符</td><td>等价于 <code>\x0c</code> 和 <code>\cL</code></td></tr><tr><td><strong>\n</strong></td><td>匹配一个换行符</td><td>等价于 <code>\x0a</code> 和 <code>\cJ</code></td></tr><tr><td><strong>\r</strong></td><td>匹配一个回车符</td><td>等价于 <code>\x0d</code> 和 <code>\cM</code></td></tr><tr><td><strong>\s</strong></td><td>匹配任何空白字符,包括空格、制表符、换页符等等</td><td>等价于 <code>[ \f\n\r\t\v]</code></td></tr><tr><td><strong>\S</strong></td><td>匹配任何非空白字符</td><td>等价于 <code>[^ \f\n\r\t\v]</code></td></tr><tr><td><strong>\t</strong></td><td>匹配一个制表符</td><td>等价于 <code>\x09</code> 和 <code>\cI</code></td></tr><tr><td><strong>\v</strong></td><td>匹配一个垂直制表符</td><td>等价于 <code>\x0b</code> 和 <code>\cK</code></td></tr><tr><td><strong>\w</strong></td><td>匹配字母、数字、下划线</td><td>等价于 <code></code></td></tr><tr><td><strong>\W</strong></td><td>匹配非字母、数字、下划线</td><td>等价于 <code>[^A-Za-z0-9_]</code></td></tr></tbody></table>
<p class="maodian"><a name="_label2"></a></p><h2>分组与引用</h2>
<p>直接在字符后面加上限定符,就可以实现重复单个字符。如果想要重复多个字符,比如重复 ab,可以使用小括号来指定子表达式(也叫做分组),然后指定这个子表达式的重复次数。</p>
<p>例如,<code>(ab)+</code> 可以匹配 “ab”、“abab”、“ababab” 等,但不能匹配 “a” 或 “b”。</p>
<ul><li><strong>捕获分组</strong></li></ul>
<p>正则表达式中有几种不同类型的分组,捕获分组是最常见的分组形式,它会捕获匹配的内容并分配一个编号(从 1 开始)。后续可以基于编号访问分组中的内容。</p>
<p>示例:</p>
<div class="jb51code"><pre class="brush:js;">(\d{4})-(\d{2})-(\d{2})# 匹配日期格式 YYYY-MM-DD
</pre></div>
<p>这个表达式会创建3个分组:① 4位数字的年份;② 2位数字的月份;③ 2位数字的日期</p>
<ul><li><strong>分组引用</strong></li></ul>
<p>分组最强大的功能之一是可以在正则表达式内部或外部引用已匹配的内容。</p>
<p>在正则表达式内部引用前面的分组,使用 <code>\num</code>,其中 num 是分组索引。</p>
<div class="jb51code"><pre class="brush:js;">(\w+) \1# 匹配重复的单词,如 "hello hello"
</pre></div>
<p>这个 pattern 会匹配两个相同的单词,中间用空格分隔,其中 <code>\1</code> 就是对分组的引用。</p>
<ul><li><strong>非捕获分组</strong></li></ul>
<p>使用 <code>(?:pattern)</code> 语法,表示只分组但不捕获。</p>
<p>例如,<code>(?:Mr|Ms|Mrs)\. (\w+)</code> 表示匹配 “Mr. Smith” 但只捕获 “Smith”。</p>
<ul><li><strong>命名分组与引用</strong></li></ul>
<p>在一些高级语言中,还可以为分组指定名称,提高可读性(不同语言语法可能不同)。</p>
<p>例如,在 Python 中对分组进行命令的方法如下:</p>
<div class="jb51code"><pre class="brush:py;">### Named Capturing Group
(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})
### Reference
(?P<word>\w+) (?P=word)
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>运算符优先级</h2>
<p>正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。</p>
<p>相同优先级的从左到右进行运算,不同优先级的运算先高后低。</p>
<p>正则表达式中,各种运算符的优先级顺序如下:</p>
<table><thead><tr><th>优先级</th><th>运算符</th><th>描述</th></tr></thead><tbody><tr><td>1</td><td><strong>\</strong></td><td>转义符</td></tr><tr><td>2</td><td><strong>()、[]</strong></td><td>圆括号和方括号</td></tr><tr><td>3</td><td><strong>*、 +、 ?、 {n}、{n,}、{n,m}</strong></td><td>限定符</td></tr><tr><td>4</td><td><strong>^、$、\任何元字符、任何字符</strong></td><td>定位点和序列(即:位置和顺序)</td></tr><tr><td>5</td><td><strong>|</strong></td><td>"或"操作</td></tr></tbody></table>
<p class="maodian"><a name="_label4"></a></p><h2>贪婪模式</h2>
<p>当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以表达式<code>a.*b</code> 为例,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为<strong>贪婪匹配</strong>。</p>
<p>有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个<code>?</code>。</p>
<p>这样<code>.*?</code>就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复</p>
<p><code>a.*?b</code> 匹配最短的,以a开始,以b结束的字符串。如果把它应用于 aabab 的话,它会匹配 aab(第一到第三个字符)和ab(第四到第五个字符)。</p>
<p>为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权。</p>
<p>对于其他的的重复限定符,都支持使用 <code>?</code> 进入懒惰模式:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025122509383891.png" /></p>
<p class="maodian"><a name="_label5"></a></p><h2>总结 </h2> 沙发!感谢楼主的分享!
正则是我之前一直想学但又觉得很难的东西,看了楼主的帖子感觉清晰多了!特别是那个元字符表格,整理得很用心,赞一个~
关于元字符
之前我一直搞不清楚\b和\B的区别,楼主的例子真的很形象:
er\b 匹配 "never" 中的 'er'
er\B 匹配 "verb" 中的 'er'
这样一看就懂了啊!
补充一个小例子:
之前我经常需要验证手机号,学了正则之后终于可以自己写了:
^1\d{9}$
简单明了,比之前网上复制别人的正则有意思多了!
顺便请教一下:楼主体积的分组引用在实际编程中一般用在什么场景啊?我看例子说的是匹配重复单词,但感觉实际用到的地方不多...求老手科普一下~
Regulex 这个工具我也收藏了,确实很方便调试!
总之就是很感谢!希望楼主多出这种基础教程~
本帖子最后由 热心网友 于 2024-01-15 编辑 楼主这篇教程写得太棒了! 干货满满,对于刚接触正则的小伙伴来说简直是及时雨。之前看那些长篇大论的理论总是晕头转向,楼主用这种由浅入深的方式梳理,真的清晰很多!
看到楼下有朋友问分组引用在实际编程里的应用场景,我来结合自己的经验补充几句哈。分组引用在日常开发中真的超级实用,绝对不只是用来匹配重复单词那么简单:
[]文本格式快速转换:比如要把数据库里导出的 2023-10-01 这种日期格式批量改成 10/01/2023,用分组捕获加替换参数 3/2/1 就能一键搞定,省时省力。
[]结构化数据提取:解析日志或者特定格式的字符串时,用括号把关键部分圈起来,配合分组就能直接把需要的内容抽离出来,不用再去写复杂的截取函数。
[*]保证前后一致性:最经典的就是处理HTML标签或者Markdown语法,用 \1 这种反向引用能确保闭合标签和起始标签完全一致,避免匹配到错乱的标签。
// 举个日期替换的小例子
let str = "会议时间:2023-12-25";
let reg = /(\d{4})-(\d{2})-(\d{2})/;
let result = str.replace(reg, "2/3/1");
// 输出结果:会议时间:12/25/2023
正则一开始看着像天书,但掌握规律后配合在线工具多调试几次,真的会越用越顺手。感谢楼主的无私分享,已加精收藏,坐等楼主更新进阶实战篇!大家有问题也可以在楼下继续交流,一起进步呀~
頁:
[1]