admin 管理员组

文章数量: 887021

了解

正则表达式

啦啦啦 我是一个搬运工,搬错打轻点儿 _-

  • 正则表达式
    • 正则他祖宗十八代
      • 表达式术语
      • 流派和特性
        • 起源
          • Tips
      • 正则模式&匹配模式
      • 一些元字符和特性
        • 字符缩略表示法
        • 字符组
          • 普通字符组 [a-z]和[^a-z]
          • 点号
          • Unicode组合字符序列: \x
          • 字符组简记法 法\w \d \s \w \D \S
          • Unicode属性 字母表以及区块 \p{xxx} \P{xxx}
          • 字符组减法
          • 字符组减法
          • POSIX方括号表示法
          • 锚点和零点断言
          • 匹配的起始位置/上一次结束的位置:\G
          • 单词分解符 \b \B \< >
          • 环视
          • 模块作用范围
          • 注释
          • 捕获分组 & 分组括号 & 命名捕获
          • 固化分组
          • 匹配优先量词
          • 忽略优先量词


正则他祖宗十八代

表达式术语

  • 正则:正则表达式(regular expression)简写为 regex
  • 匹配:正则表达式「a」不能匹配cat,但是能匹配cat中的a
  • 元字符:又叫元字符序列,取决于应用的具体情况,「*」是元字符 而「*」是转义 不是元字符
    而且不同流派的元字符也不一样,Perl的字符串也有自己的元字符,它们完全不同于正则表达式元字符
    • 坑:Perl里面的@表示数组名,需要转义
  • 流派:类似方言,匹配规则、优化规则、元字符库什么的都不一样

流派和特性

使用一个正则,不能只会一种,也不能一个工具用到死,适当的了解各种流派的各种特性比较好,需要关注以下三点:

  • 元字符库(数量+含义,也就是流派)
  • 语言和相应工具的交互方式: 有那些操作、怎么操作、操作的激励(文本)是什么
  • 引擎的工作方式:就是实现原理
  • 准备好吃的 慢慢水几分钟

起源

  • 最初的想法来自 20 世纪 40 年代的两位神经学家,他们研究出一种模型,认为神经系统在神经元层面上就是这样工作的。若干年后,有人在代数学中正式描述了这种被他称为“正则集合”。后来又归纳出了一套简洁的表示正则集合的方法,叫做“正则表达式”。
  • 1968年,有人在发明一个正则表达式编译器 qed,并生成了IBM7094的工程代码,后来成为Unix中ed编译器的基础。
  • 后来因为ed的一条很牛叉的命令,“g/RegularExpression/p”,读作“Global Regular Expression Print”(应用正则表达式的全局输出)。这个功能非常实用,最终成为独立的工具grep
  • AT&T的贝尔实验室之后又产生了egrep——扩展的grep
  • egrep演变的同时,其他程序,例如awk、lex和sed,也在按各自的脚步前进,改改功能 加加特性,搞成自己的流派了。

因为个流派太乱,后面出现了POSIX标准,目前为止 几乎所有流派都遵循这个标准。比如local,给出了日期和时间的格式、货币币值、字符编码对应的意义等,让程序变得标准化,虽然不是专门为正则设定的,但是对正则影响灰常大。
后面,出现了一个开发工具,Perl,柔和了众多语言的特性,提供了一套实用的匹配机制。先后进行了5次改版,因为它旨在进行文本处理,而当时兴起的web页面也是字符流,所以马上就火了。后面Python、.NET、Ruby、PHP、C/C++等都发布了一堆正则包(其实就是perl的兼容版),而且1997年还出现了一个套兼容Perl的正则库 PCRE 全面模仿Perl的语法和语义 引擎质量很高。PHP、Apache 2等比较出名的工具 都使用过PCRE。

  • 集成式:表达式直接内建在语言中,比如Perl –> $line =~ m/^Subject: (.*)/i,好处就是 让一些操作透明化:例如正则表达式的预处理,准备匹配,应用正则表达式,返回结果
  • 程序式 + 面向对象式:普通函数/构造方法,并没有专属于正则表达式的操作符,只有平常的字符串,普通的函数、构造函数和方法把这些字符串作为正则表达式来处理
    • JAVA中使用的是java.util.regex 包,Pattern.compile(“^Sujbcet: (.*)”, Pattern.CASE_INSENSITIVE),然后用matcher,然后是find,然后是group等。
Tips
  • 字符&组合字符序列:某些看起来的字符,并不是字符,比如à在Unicode中是a和钝重音’构成。这种叫做组字符 组成他们的叫做码点。
    • 但是有些程序,字符 = 代码点。比如à(U+0061 加上U+0300)能够由「^..$」匹配,而不是「^.$」
    • 不过为保证Unicode和Latin-1 映射,à它也可以用单个代码点U+00E0表示。
    • 表示蛋疼。。。多百度

正则模式&匹配模式

下面说的这些模式,可以用于整个表达式 也可以只用于子表达式。

  • 常见的/i、/x
  • JAVA中的Pattern.CASE_INSENSITIVE
  • 子表达式是通过一些正则结构实现的,开始:(?i) 结束:(?-i),有些流派也支持这种(?i:…)和(?-i:…)

下面介绍一些匹配模式,以及一些坑!!!

  • 不区分大小写: /i
    • 某些字符大小写不是一对一的:希腊字母西格马Σ,它有两个小写形式ζ和σ,目前只有Perl和Java可以处理
    • Unicode的 J(U+01F0)没有对应的大写形式的单字符,而且还需要组合字符U+004A 和 U+030C,还有一些一对三的,不过都不是通用字符
  • 宽松排列和注释模式:/x.忽略外部的所有空格,字符组内部的空白字符仍然有效(java的regex例外),#符号和换行符之间的内容视为注释
    • JAVA中,字符组之外的空格被看成是一个无意义的字符,比如\12 3,是\12后面加了一个3 而不是\123
  • 单行模式:/s。点不匹配换行符,此模式就是说点可以匹配任何字符
  • 多行文本模式:/m。影响到行锚点「^」和「$」的匹配。
    • 通常情况下,锚点「^」不能匹配字符串内部的换行符,而只能匹配目标字符串的起始位置。此模式下,它能够匹配字符串中内嵌的文本行的开头位置
    • 通常只能匹配换行符之前的位置,「$」可以匹配字符串内部的换行符!!!!!!
    • 「\ A」和「\Z」,它们的作用与普通的「^」和「$」一样,只是在此模式下它们的意义不会发生变化。也就是说「\A」和「\Z」永远不会匹配字符串内部的换行符。有些实现方式中,「$」和「\Z」能够匹配字符串不过它们通常会提供「\z」,唯一匹配整个字符串的结尾位置
    • 和单行模式没有一毛钱关系0.0
  • 文本匹配模式:不识别任何表达式元字符,搜索这个字符串 而不是匹配这个正则表达式!!!!!!!

一些元字符和特性

这就是一些总结,算是正则的梳理吧,没有水 放下零食 准备接受干货吧

字符缩略表示法

制表符介绍
\a在“打印”时扬声器发声,ASCII中的BEL字符,八进制编码007
\b退格 通常对应ASCII中的BS字符,八进制编码010,某些地方只有字符组内才有用,其他表示单词分界符
\eEscape字符 通常对应ASCII中的ESC字符
\n换行符
\r通常对应ASCII的CR字符。IOS中对应到ASCII的LF字符
\t水平制表符 对应ASCII的HT字符
\v垂直制表符 对应ASCII的VT字符
\f进纸符 通常对应ASCII中的字符(问号脸???)

不过类似 m r这种,随系统变化的,可以使用\012(比如HTTP协议中)

字符组

普通字符组 [a-z]和[^a-z]
  • 元字符:在字符组内 *不是元字符,二 - 是元字符,\b 在字符自内外也不一样
  • [^LMNOP]等价于[\x00-kQ-\xFF],但是在在Unicode之类字符的值可能大于 255(\xFF)的系统中,前者只是不包括L、M、N、O和P
  • [a-zA-Z] 不一定等于 [a-Z]
点号
  • java的regex包,不能匹配行终结符
  • 匹配模式,会修改匹配规则
  • POSIX,不能匹配nul(值为0的字符)
  • [^x]是可以匹配换行符的
Unicode组合字符序列: \x

通常情况,表示\P{M}\p{M}*,可以匹配换行符+Unicode行终结符,但是不能匹配以组合字符开头的字符(这里可以和点好比较一波)

字符组简记法 法\w \d \s \w \D \S
标记介绍
\d数字 登记于[0-9]
\D非数字 [^\d]
\w单词中的字符,[a-zA-Z0-9],有些流派不支持下划线,但是都支持locale \p{L}等
\W非单词字符
\s空白字符,等价于[空格\f\n\r\t\v],支持Unicode的换行符 U+0085
\S非空白
Unicode属性 字母表以及区块 \p{xxx} \P{xxx}

属性表,貌似和字母表差不多 如下

区块

  • 类似字母表,就是某一范围的代码点,比如Tibetan块,表示从U+0F00到U+0FFF的256个代码点。Perl和java.utn.regex中可以用\p{ InTibetan)来匹配,在.NET中的是\p{ IsTibetan)
  • 区块一般是按照书写系统来的,比如拉丁语、希伯来语、特殊字符(货币、箭头、印刷符号)
字符组减法

比如:[a-z] - [aeiou] \p{P} - [\p{Pe}\p{Ps}]

字符组减法

比如:[a-z] && [aeiou] ,这里也可以是OR和AND 对应就是[adsfa] = [[asd][fa]] [[asd] && [fa]] ,前者是加 后者是交集
或者是多选结构: aaa|bbb,表示多分支

POSIX方括号表示法

我们说的字符组,在该标准中 是方括号表达式,其中也有一些约定,比如[:lower:]表示locale中的所有小写字母,a-z

  • 只有在方括号表达式内才有用, [[:lower:]]
  • 尽管locale会变,但是这些一般都会支持的
锚点和零点断言
  • 行 字符串起始位置:^ \A
  • 行 字符串起始位置:$ \Z \z
    • 文本结束为止/换行之前的位置,但是匹配模式会修改 文 本 结 束 为 止 / 换 行 之 前 的 位 置 , 但 是 匹 配 模 式 会 修 改 含义 匹配任何换行符
匹配的起始位置/上一次结束的位置:\G

对迭代很有用的,可以匹配上一次匹配结束的位置

  • 第一次迭代 锁定开头 和\A一样
  • 匹配不成功,重新指向起始位置,这样如果重复使用某个表达式 就不会受前一次的影响了
  • 在某些流派中,有一些特性(比如Perl)

    • 指向目标字符串的属性,而不是这设置这个位置的表达式的属性,这样N个表达式就都可以匹配同一个字符串了,都是从上一轮匹配之后设置的G位置
    • 有些给出/c,在匹配失败后不重新设定位置(也就是开头)

    这里出现一个问题:这样的匹配是之前结束的位置?还是当前匹配开始的位置呢?很多时候是一样的,但是有个特例:用 x? 匹配 ‘abcde’。
    不会报错,但是没有匹配到任何文本,匹配位置就是开头,如果进行全局查找-替换时,正则表达式会重复应用,每次处理上一次操作之后的文本 就会死循环,因为“上次匹配完成的位置”总是它开始的位置
    为了避免无穷循环,有些流派的传动装置会强行前进到下一个字符,比如: ‘abcde’应用s/x?/!/g之后,为 !a!b!c!d!e!

单词分解符 \b \B \< >
环视
模块作用范围

前面讲的 (?I:…..)

注释

(?#…) 或者 #…

捕获分组 & 分组括号 & 命名捕获
  • 捕获分组:(…) \1 \1,JS里面可以是 $1 $2
  • 分组括号: (?:…..),非捕获型括号,表达式清晰 多个儿子构建表达式
  • 命名捕获:(?<\name>) group(“name”)
    • .NET\k<\name> —这里name前没有\
    • js中的命名捕获
固化分组

&aafda! 匹配 &.*!,到结尾 然后因为要匹配! .会释放某些内容,到最短匹配位置为止

  • 占有优先量词:*+ ++ ?+ {1,3}+,目前只有java的regex包 PCRE PHP支持
匹配优先量词

* + ? {1,3}

忽略优先量词

*? +? ?? {1,3}?

本文标签: 了解