正则表达式必知必会

Ben Forta
正则表达式是一种威力无比强大的武器,几乎在所有的程序设计语言里和计算机平台上都可以用它来完成各种复杂的文本处理工作。本书从简单的文本匹配开始,循序渐进地介绍了很多复杂内容,其中包括回溯引用、条件性求值和前后查找,等等。每章都为读者准备了许多简明又实用的示例,有助于全面、系统、快速掌握正则表达式,并运用它们去解决实际问题。本书适合各种语言和平台的开发人员。
王勇博

书写的挺好,通俗易懂,推荐阅读,简单总结如下: 单字符匹配 ●abc字符串匹配abc字符串本身 ●.可以匹配任何字符 ●\是转义字符,比如匹配.本身,这么写\. ●[]来定义字符集合,其本身无意义,比如[sa]表示匹配s或a ●字符集合可以用字符区间表示,比如[0123456789]和[0-9]等价,[A-Z],匹配从A到Z的所有大写字母。[a-z],匹配从a到z的所有小写字母。[A-F],匹配从A到F的所有大写字母。 ●-(连字符)是一个特殊的元字符,作为元字符它只能用在[和]之间。在字符集合以外的地方,-只是一个普通字符,只能与-本身相匹配。 ●在同一个字符集合里可以给出多个字符区间,如[A-Za-z0-9]匹配所有大小写字母和数字。 ●用元字符^来表明你想对一个字符集合进行取非匹配。[^0-9]匹配的是任何不是数字的字符。 ●^的效果将作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟在^字符后面的那一个字符或字符区间。 ●在一个完整的正则表达式里,转义字符\的后面永远跟着另一个字符。 ●空白元字符:\n 换行符,\r 回车符,\t 制表符(Tab键),\f 换页符,\v 垂直制表符,[\b] 回退(并删除)一个字符(backspace键) ●如果要把文本里的空白行去掉,使用\r\n\r\n,其中\r\n匹配一个“回车+换行”组合,使用正则表达式\r\n\r\n进行的搜索将匹配两个连续的行尾标签,而那正是两条记录之间的空白行。 ●Unix和Linux系统只使用一个换行符来结束一个文本行;换句话说,在Unix/Linux系统上匹配空白行只使用\n\n即可,不需要加上\r。 ●元字符让正则表达式看起来更简洁,如匹配任何一个数字字符 \d 等价于[0-9],匹配任何一个非数字字符 \D 等价于[^0-9] ●\w表示匹配任何一个大小写字母、数字、下划线,等价于[A-Za-z0-9_],\W表示匹配任何一个非大小写字母、数字、下划线,等价于[^A-Za-z0-9_] ●空白字符元字符,\s表示匹配任何一个空白字符,等价于[\f\r\n\t\v],\S表示匹配任何一个非空白字符,等价于[^\f\r\n\t\v] ●在正则表达式里,十六进制(逢16进1)数值要用前缀\x来给出。比如说,\x0A对应于ASCII字符10(换行符),其效果等价于\n。八进制(逢8进1)数值要用前缀\0来给出,数值本身可以是两位或三位数字。比如说,\011对应于ASCII字符9(制表符),其效果等价于\t。(不常用) ●POSIX字符类,写法[[:xdigit:]],是中括号加上冒号,如 [:alnum:] 等价于 [A-Za-z0-9],[:alpha:] 等价于 任何一个字母,[:blank:] 等价于 空格或制表符[\t ],后边有一个空白字符,[:cntrl:] 等价于 ascii控制字符,[:digit:] 等价于 任何一个数字,[:print:] 等价于 任何一个可打印字符,[:graph:] 等价于 [:print:]但不包括空格,[:lower:] 等价于 任何一个小写字母,[:upper:] 等价于 任何一个大写字母,[:punct:] 等价于 既不属于[:alnum:]又不属于[:cntrl:]的任何字符,[:space:] 等价于 任何一个空白字符[\f\r\n\t\v ],后边有一个空白字符,[:xdigit:] 等价于 任何一个十六进制数字[A-Fa-f0-9] 重复匹配 ●匹配一个或多个字符,a+将匹配一个或多个连续出现的a。类似地,[0-9]匹配任意单个数字,[0-9]+将匹配一个或多个连续的数字。+是一个元字符。如果需要匹配+本身,就必须使用它的转义序列\+。但是如果+位于[]中则不一定要转义(.也同理)。 ●匹配零个或多个字符,使用*元字符,*的用法与+完全一样——只要把它放在一个字符(或一个字符集合)的后面,就可以匹配该字符(或字符集合)连续出现零次或多次的情况。.*表示匹配任意字符。 ●匹配零个或一个字符,使用?元字符,?只能匹配一个字符(或字符集合)的零次或一次出现,最多不超过一次。 ●元字符{和},重复次数要用{和}字符来给出——把数值写在它们之间。表示前一个字符或字符集需要连续出现n次才能匹配上。 ●{}语法还可以用来为重复匹配次数设定一个区间——也就是为重复匹配次数设定一个最小值和一个最大值。这种区间必须以{2, 4}这样的形式给出——{2, 4}的含义是最少重复2次、最多重复4次。 ●为避免不必要的麻烦,在需要匹配/字符本身的时候,最好总是使用它的转义序列\/。 ●匹配“至少重复多少次”,{3, }表示至少重复3次,与之等价的说法是“必须重复3次或更多次”。 防止过度匹配 ●+和*都是“贪婪型”元字符,它们在进行匹配时的行为模式是多多益善而不是适可而止的。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。 ●懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个?后缀即可。(“懒惰”在这里的含义是匹配尽可能少的字符——与“贪婪型”元字符的行为模式刚好相反)如 *?,+?,{n,}? 位置匹配 ●位置匹配用来解决在什么地方进行字符串匹配操作的问题。 ●单词边界,\b用来匹配一个单词的开始或结尾。单词cat的前后都有一个空格,而这将与模式\bcat\b相匹配(空格是用来分隔单词的字符之一)。单词scattered中的字符序列cat不能与这个模式相匹配,因为它的前一个字符是s、后一个字符是t(这两个字符都不能与\b相匹配)。如果你想匹配一个完整的单词,就必须在你想要匹配的文本的前后都加上\b限定符。模式\bcap将匹配以字符序列cap开头的任何一个单词。模式cap\b将匹配以字符序列cap结束的任何一个单词。 ●如果你想表明不匹配一个单词边界,请使用\B。即非字母数字下划线的边界,如xx - xx想匹配-使用\B-\B。同一个元字符的大写形式与它的小写形式在功能上往往刚好相反。 ●字符串边界,^匹配一个字符串的开头位置,所以^\s*将匹配一个字符串的开头位置和随后的零个或多个空白字符(这解决了标签前允许有空格、制表符、换行符等空白字符的问题)。\s*$匹配一个字符串结尾处的零个或多个空白字符。 ●分行匹配模式,在使用时,(? m)必须出现在整个模式的最前面。^\s*//.*$将匹配一个字符串的开始,然后是任意多个空白字符,再后面是//(JavaScript代码里的注释标签),再往后是任意文本,最后是一个字符串的结束。不过,这个模式只能找出第一条注释(并认为这条注释将一直延续到文件的末尾,因为*是一个“贪婪型”元字符)。加上(? m)前缀之后,(? m)^\s*//.*$将把换行符视为一个字符串分隔符,这样就可以把每一行注释都匹配出来了。 使用子表达式 ●把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用(和)括起来。和)是元字符。如果需要匹配(和)本身,就必须使用它的转义序列\(和\)。 ●匹配ip地址,常规做法\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3},使用(和)元字符后(\d{1,3}\.){3}\d{1,3} ●匹配1987-01-01的1987或2023使用(19|20)\d{2},不能使用19|20\d{2},因为|表示或,分开的是该符号前后的两部分。 ●子表达式允许嵌套。事实上,子表达式允许多重嵌套,这种嵌套的层次在理论上没有限制,但在实际工作中还是应该遵循适可而止的原则。 回溯引用 ●例子:匹配html中的所有h标题,这里使用的是.*?(懒惰型)而不是.*(贪婪型)。*和其他几个元字符是“贪婪型”元字符,所以模式<[hH][1-6]>.*有可能会从第2行的

一直匹配到第6行的

,这可不是我们想要的结果;使用“懒惰型”元字符.*?解决了这个问题。这个特定的例子里即便是使用了“贪婪型”元字符也不一定会有问题。一般来说,元字符.不匹配换行符,而上例中的每个标题都各自占据一行。 ●如果有不匹配的标签如html中的

的错误组合,使用<[Hh][1-6]>.*?无法精准的判别这种组合,就要用到回溯引用。 ●假设你有一段文本,你想把这段文本里所有连续重复出现的单词(打字错误,其中有一个单词输了两遍)找出来。显然,在搜索某个单词的第二次出现时,这个单词必须是已知的。回溯引用允许正则表达式模式引用前面的匹配结果(具体到这个例子,就是前面匹配到的单词)。 ●回溯引用只能用来引用模式里的子表达式(用(和)括起来的正则表达式片段)。回溯引用匹配通常从1开始计数(\1、\2,等等)。在许多实现里,第0个匹配(\0)可以用来代表整个正则表达式。比如a block of of text and and sth找出重复出现的单词,使用[ ]+(\w+)[ ]+\1匹配,[ ]+匹配一个或多个空格,\w+匹配一个或多个字母数字字符,[ ]+匹配随后的空格。注意,\w+是括在括号里的,它是一个子表达式。这个模式的最后一部分是\1;这是一个回溯引用,而它引用的正是前面划分出来的那个子表达式:当(\w+)匹配到单词of的时候,\1也匹配单词of;当(\w+)匹配到单词and的时候,\1也匹配单词and。回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。\1到底代表着什么?它代表着模式里的第1个子表达式,\2代表着第2个子表达式、\3代表着第3个;依次类推。于是,在上面那个例子里,[ ]+(\w+)[ ]+\1将匹配同一个单词的连续两次重复出现。上边html例子中使用<[Hh]([1-6])>.*?来排除h2和h3组合的情况,用(和)把[1-6]括了起来,使它成为了一个子表达式。这样一来,我们就可以在用来匹配标题结束标签的用\1来引用这个子表达式了。子表达式([1-6])匹配数字1~6, \1只匹配与之相同的数字。这样一来,原始文本里的

This is not valid HTML

就不会被匹配到了。 ●回溯引用在替换操作中的应用,比如文本中把邮箱地址xxx@gmail.com变为超链接,即xxx@gmail.com,替换操作需要用到两个正则表达式:一个用来给出搜索模式,另一个用来给出匹配文本的替换模式。回溯引用可以跨模式使用,在第一个模式里被匹配的子表达式可以用在第二个模式里。这里使用的模式(\w+[\w\ .]*@[\w\ .]+\ .\w+)与我们以前使用的完全一样(匹配电子邮件地址),但这次把它写成了一个子表达式。这样一来,被匹配到的文本就可以用在替换模式里了,$1,同一个子表达式可以被引用任意多次——只要在需要用到它的地方写出它的回溯引用就行了。 ●在看例子,对文字进行重新排版,电话号码被保存为313-555-1234。现在需要把电话号码重新排版为(313)555-1234。(\d{3})(-)(\d{3})(-)(\d{4})用来匹配一个电话号码,它被划分为5个子表达式,负责重新排版电话号码的替换模式($1)$3-$5只用到了它们当中的3个。在对文本进行重新排版的时候,把文本分解成多个子表达式的做法往往非常有用,这可以让我们对文本的排版效果做出更精确的控制。 ●大小写转换,元字符\l和\u只能把下一个字符(或子表达式)转换为小写或大写。\L和\U将把它后面的所有字符转换为小写或大写,直到遇上\E为止。例子,把一级标题(

...

)的标题文字转换为大写,模式(<[Hh]1>)(.*?)()把一级标题分成了3个子表达式,然后替换$1\U$2\E$3 前后查找 ●前后查找的作用:比如只想返回被匹配的html标签里边的内容,不需要返回标签本身 ●向前查找,从语法上看,一个向前查找模式其实就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。比如匹配url中的协议名,使用.+(?=:),模式.+匹配任意文本(第1个匹配是http),子表达式(? =:)匹配:。注意,被匹配到的:并没有出现在最终的匹配结果里;我们用?=向正则表达式引擎表明:只要找到:就行了,不要把它包括在最终的匹配结果里——用术语来说,就是“不消费”它。模式.+(:)查找到并且匹配结果包含:,模式.+(? =:)查找到但匹配结果不包含:。 ●向后查找,操作符是?<=。比如匹配价格$236.85,使用\$[0-9.]+,不想要$只要236.85使用(?<=\$)[0-9.]+,(? <=\$)只匹配$,但不消费它;最终的匹配结果里只有价格数字(没有前缀的$字符)。向前查找模式的长度是可变的,它们可以包含.和+之类的元字符,所以它们非常灵活。而向后查找模式只能是固定长度——这是一条几乎所有的正则表达式实现都遵守的限制。 ●结合向前向后查找,匹配This is a title中的纯text使用 (? <=<[tT][iI][tT][lL][eE]>).*(? =) ,其中(? <=<[tT][iI][tT][lL][eE]>)是一个向后查找操作,它匹配(但不消费);而(? =</[tT][iI][tT][lL][eE]>)是一个向前查找操作,它匹配(但不消费)。最终返回的匹配结果包含且仅包含标题文字(用术语来说,就是只有标题文字被消费了)。 ●对前后查找取非,负向前查找(negative lookahead)将向前查找不与给定模式相匹配的文本,负向后查找(negative lookbehind)将向后查找不与给定模式相匹配的文本。正向前查找(?=),负向前查找(?!),正向后查找(?<=),负向后查找(?
李文山

# Meta - 正则表达式入门级别的一本不错的书, 更像是一本用户入门学习手册而非一本字典级参考手册, 正如作者所说, 在学习和使用正则表达式的时候,重要的并不是你知道多少个特殊字符,而是你会不会运用它们去解决实际问题, 学以致用, 致用以学, 最近我的问题在于为了学习而学习, 而非为了应用而学习, 事倍功半, 人的精力有限, 时间有限, 又哪能做到样样深耕呢, 对于接触的大多领域都只需掌握一些核心概念和基本语法就好 # 正则表达式必知必会 - 本书架构, 从简单的字符串匹配开始入手, 后序介绍一些复杂一些的专题, e.g. 回溯引用/后向引用(backreference), 条件性求值(conditional evaluation)和前后查找(looking-around) ## C1 正则表达式入门 - regular expression -> regex - 用途, 进行文本匹配+[替换] - 比较迷你, 一般嵌入到其他工具中, 应用程序大多用菜单访问regex, 程序设计语言大都通过函数or类来使用regex - 计算机从来都是做中学学科, 不经过实践的学习在CS领域事倍功半 ## C2 匹配单个字符 - . 除换行符以外的任意单个字符, 如果需要.的本身含义, 用转义字符\ ## C3 匹配一组字符 - 用[]定义一个字符集合, 里面可以写想要匹配的字符, e.g. [ab], - 在[]中使用-可以定义一个字符区间, e.g. [A-Za-z0-9] 在[]外使用-就是一个普通的字符, 所以在regex中使用-不需要被转义 - [^a-z0-9], 先定义一个字符区间, 而后对其取非, 而不是只对^后跟着的字符区间取非 ## C4 使用元字符 - 前面接触到的元字符, `. [ ] \` - 空白元字符 - [\b] 回退(并删除)一个字符(Backspace键), 这个是用来匹配退格字符的, 不在\s中 - \f 换页符 - \n 换行符, \r 回车符, \t 制表符 - \v 垂直制表符 - \d is [0-9], \D is [^0-9] - \w is [a-zA-Z0-9_], \W is ^ of \w - \s is [\f\n\r\t\v], 任意一个空白字符, \S任意非空字符 - POSIX字符类, 一种字符集合中的简写形式, 并非所有的regex的实现都支持 ## C5 重复匹配 - +, 一到多, [0-9]+, 代表1-多个连续数字 - 注意区分[0-9+], 匹配一个字符, 可以是数字或+ - *, 零到多 - ?, 0 or 1 - 解决Windows/Linux系统下回车问题就可以用 [\r]?\n - 写\r?\n也是一样的, 第一种写法定义一个字符集合, 集合里只有\r这样的一种字符, 和第二种写法功能相同, 对读者更友好一点, 读起来不会有歧义. - 上述三种都是元字符, 匹配本身需要转义, 放到字符集合里也是本身, 加了转义也没什么问题 - 制定精确的重复次数{} - {a} 重复a次 - {a, b} 重复[a, b]次 - {a, } 至少重复a次 - *, +, {a, }这三个都没有上限, 都是贪婪型的, 尽可能匹配的长 - *?, +?, {a, }?是其对于的懒惰版本, 尽可能匹配的短 ## C6 位置匹配 - the border of a word(w in Vim) - \< matches the pos of \W and \w, \> matches that of \w and \W - \b matches the pos of \w and \W or that of \W and \w - the border of a string(W in Vim) - ^ matches the start of a string, $ matches end - (?m), regard \n as a start/end of string ## C7 子表达式 - 用元字符()进行分组 ## C8 回溯引用backreference - 解决HTML中Head标签匹配, 用`<[hH][1-6]>.*?`来找出所有的head标签, 现在存在的问题是

a

也会被匹配进来, 也就是结束标签对开始标签的情况一无所知, 回溯引用就是用来解决这类问题的 - 比如匹配两个连续的单词怎么实现, 需要先知道第一个单词是什么, 才能控制第二个和它一不一样, 实现 `[ ]+(\w+)[ ]+\1` - [ ]+代表1-多个space, 用()把\w+括起来, 做成一个子表达式, 然后用\1引用pattern中的第一个子表达式, 想一下Java中对正则表达式的实现, 把子表达式的sIndex和eIndex+1存到groups[2]和groups[3]中, 后续即可引用 - 这种按位置的捕获方式用\数字进行引用, 当想改逻辑的时候可能要改的地方有点多, 可以用`命名捕获`, 给子表达式起个名字, 这样改变相对位置也不用改后面引用位置的语句了, 命名捕获是否支持要看具体的实现 ## C9 前后查找lookaround - regex常见的实现都支持向前查找lookahead, 部分支持向后查找lookbehind, Java是支持的 ## C10 嵌入条件 - 当前面是不同的匹配时向后找不同的匹配, 类似于编程语言中的三元运算符 ## 附录A ### A.12 Perl - m/pattern/ - s/pattern/pattern/

肚子饱了

这本书太棒了。我之前是在网上看过正则表达式的文章,虽然都看得懂,但就是不能灵活运用,符号记忆也不深刻。但是这本书,从头到尾,一步一步的让你搞懂每个字符是干什么的,一步一步的让你理解这个字符加那个字符拼在一起会怎么样。在这个过程中,先带你学习知识,然后运用知识答题,再提出已有知识无法满足的问题,引出新知识,就这样一步一步的引导你学习,完完全全的由浅入深。总之,这本书,太棒了👏👏

拂晓东夕

主要功能:搜索和替换

憨人

通俗易懂,结构清晰,列举的示例都很经典具有代表性。

去码头整点薯条

对于刚入门的新手,可以起到一个了解的作用,帮助你打开视野。

北纬30度

通俗易懂,非常好的一本正则表达式入门书。

Miche

通过案例分析给出适用的表达式,然后分解该表达式,仔细介绍每一部分分别是什么含义。书里还有一些提示注意一些意外情况,总之很实用的一本书

麦黄与微风

循序渐进地讲解了如何从编写简单的正则表达式到复杂的正则表达式,整本书都非常通俗易懂。 这本书更偏实践向,缺少技术原理性内容。

不知归处

由浅入深,循序渐进

LN

虽然有些难懂但是收获颇多

威尔亨特

正则基础知识,可以作为工具书 随时查阅

帅帅

清楚明了,入门好书

pl

这本书从易到难系统介绍了一遍正则表达式的基础知识,用来入门还是很不错的,推荐阅读

卷毛耶✌️

罗湖图书馆京东配送借的书籍📚。 由于对正则表达式完全没基础,以前看到一些正则表达式的编程语言就会一脸懵逼,看完这本书,终于算有了个头绪。 笔记记录自己需要记住的一些内容: 1、 .可以匹配任意的单个字符、字母、数字甚至.字符本身

暂时没有数据