了解-正则表达式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34009967/article/details/80687167

正则表达式

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


正则他祖宗十八代

表达式术语

  • 正则:正则表达式(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,某些地方只有字符组内才有用,其他表示单词分界符
\e Escape字符 通常对应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}

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

区块

  • 类似字母表,就是某一范围的代码点,比如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会变,但是这些一般都会支持的
    POSIX方括号表示法
锚点和零点断言
  • 行 字符串起始位置:^ \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”)
固化分组

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

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

* + ? {1,3}

忽略优先量词

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

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页