00 01 元字符 正则表达式 —— 字符串的规则。
元字符就是指那些在正则表达式中具有特殊意义的专用字符。
特殊单字符.
任意字符(换行除外)\d
任意数字 \D
任意非数字\w
A-Za-z0-9_ \W
\s
空白符 \S
空白符\r
回车符\n
换行符\f
换页符\t
制表符\v
垂直制表符 范围|
或[abc]
多选一[a-z]
之间[^abc]
取反,不能是括号中的任意单个元素 量词*
0<=+
1<=?
0或1{m}
m{m,}
m<={m,n}
m-n 02 量词与贪婪 贪婪(Greedy) *
:匹配最长。在贪婪量词模式下,正则表达式会尽可能长地去匹配符合规则的字符串,且会回溯。
preg_match_all ("/a*/i" , "aaabb" , $matches );var_dump ($matches );
非贪婪(Reluctant) +?
:匹配最短。在非贪婪量词模式下,正则表达式会匹配尽可能短的字符串。
ENV:Python3
import rere.findall(r'a*' , 'aaabb' ) re.findall(r'a*?' , 'aaabb' ) re.findall(r'".+"' , '"the little cat" is a toy, it lokks "a little bad"' ) re.findall(r'".+?"' , '"the little cat" is a toy, it lokks "a little bad"' )
独占模式(Possessive) ++
:同贪婪一样匹配最长。不过在独占量词模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而 不会回溯 。
import rere.findall(r'xy{1,3}z' , 'xyyz' ) re.findall(r'xy{1,3}?z' , 'xyyz' )
import regexregex.findall(r'xy{1,3}+z' , 'xyyz' ) regex.findall(r'xy{1,3}+yz' , 'xyyz' )
03 分组与引用 import regexregex.sub(r'(\d{4})-(?:\d{2})-(\d{2})' , r"年:\1 日:\2" , '2023-03-01' ) regex.sub(r'(\w+)(\s\1)+' , r"\1" , 'the little cat cat is in the hat hat hat, we like it.' )
04 匹配模式 指改变元字符匹配行为。
不区分大小写模式(Case-Insensitive)(?模式标识)
(?i)
。
import regexregex.findall(r"(?i)cat" , "cat Cat CAt" )
# https: # 二次重复时的大小写一致 ((?i)cat) \1
点号通配模式(Dot All)(?s)
让英文的点 .
可以匹配上包括换行的任何字符。等价 [\s\S]
[\d\D]
[\w\W]
。
多行匹配模式(Multiline)(?m)
使 ^
和 $
能匹配上每行的开头或结尾。
注释模式(Comment)(?#)
(\w+)(?#word) \1 (?#word repeat again)
05 断言 Assertion 对要匹配的文本的位置也有一定的要求。只用于匹配位置,而不是文本内容本身,这种结构就是断言。
边界(Boundary)
import rere.sub(r'\btom\b' , 'jerry' , "tom asked me if I would go fishing with him tomorrow." ) re.sub(r'\Atom' , 'jerry' , "tom asked me if I would go fishing with him tomorrow." )
re.findall(r'[1-9]\d{5}' , "138001380002" ) re.findall(r'(?<!\d)[1-9]\d{5}(?!\d)' , "138001380002" ) re.findall(r'(?<!\d)[1-9]\d{5}(?!\d)' , "code138001code" )
06 转义 转义字符 Escape Character 后面的字符,不是原来的意思了。
import rere.findall(r'\\d' , 'abc\\d123d\\' ) re.findall('\\' , 'a*b+c?\\d123d\\' ) re.findall('\\\\' , 'a*b+c?\\d123d\\' ) re.findall(r'\\' , 'a*b+c?\\d123d\\' ) re.findall('\(\)\[]\{}' , '()[]{}' )
import rere.escape('\d' ) re.findall(re.escape('\d' ), '\d' ) re.escape('[+]' ) re.findall(re.escape('[+]' ), '[+]' )
import rere.findall(r'[^ab]' , '^ab' ) re.findall(r'[^cd]' , '^ab' ) re.findall(r'[\^ab]' , '^ab' ) re.findall(r'[a-c]' , 'abc-' ) re.findall(r'[a\-c]' , 'abc-' ) re.findall(r'[-ac]' , 'abc-' ) re.findall(r'[ac-]' , 'abc-' ) re.findall(r'[]ab]' , ']ab' ) re.findall(r'[a]b]' , ']ab' ) re.findall(r'[a\]b]' , ']ab' ) re.findall(r'[.*+?()]' , '[.*+?()]' ) re.findall(r'[\d]' , 'd12\\' )
import rere.findall('\n' , '\\n\n\\' ) re.findall('\\n' , '\\n\n\\' ) re.findall('\\\n' , '\\n\n\\' ) re.escape('\n' ) re.findall('\\\\n' , '\\n\n\\' ) re.escape('\\n' )
07 流派及其特性 POSIX Portable Operating System Interface。不能使用 \d
。BRE Basic Regular Expression 基本正则表达式。grep
sed
花园问管家 {}()?|+
要转义。 ERE Extended Regular Expression 扩展正则表达式。egrep
grep -E
sed -E
。 PCRE Perl Compatible Regular Expressions。可以使用 \d
\w
\s
。grep -P
sed -P
。 grep --help | grep PATTERN
Linux/Unix 工具与正则表达式的 POSIX 规范 | 余晟
08 处理 Unicode 文本 Unicode 相当于规定了字符对应的码值,这个码值得编码成字节的形式去传输和存储。最常见的编码方式是 UTF-8,另外还有 UTF-16,UTF-32 等。UTF-8 之所以能够流行起来,是因为其编码比较巧妙,采用的是变长的方法。也就是一个 Unicode 字符,在使用 UTF-8 编码表示时占用 1 到 4 个字节不等。最重要的是 Unicode 兼容 ASCII 编码,在表示纯英文时,并不会占用更多存储空间。而汉字呢,在 UTF-8 中,通常是用三个字节来表示。
import reu'极客' .encode('utf-8' )u'时间' .encode('utf-8' )re.search(r'[时间]' , '极客' ) is not None re.compile (r'[时间]' , re.DEBUG) re.compile (r'[极客]' , re.DEBUG) re.compile (ur'[时间]' , re.DEBUG) re.search(ur'[时间]' , '时间' ) is not None False re.search(ur'[时间]' , u'时间' ) is not None True
import rere.findall(r'^.$' , '学' ) re.findall(r'^.$' , u'学' ) re.findall(ur'^.$' , u'学' ) print (unichr(0x5B66 ))
import rere.findall(r'^.$' , '学' ) re.findall(r'(?a)^.$' , '学' ) chr (0x5B66 )
import rere.findall(r'客{3}' , '极客客客客' ) re.findall(ur'客{3}' , '极客客客客' ) re.findall(r'客{3}' , u'极客客客客' ) re.findall(ur'客{3}' , u'极客客客客' ) re.findall(r'(客){3}' , '极客客客客' )
re.findall(r'客{3}' , '极客客客客' )
09 编辑器中使用正则 竖向编辑:MacOS alt + 鼠标纵向滑动。
10 语言中用正则 校验文本内容:
import rereg = re.compile (r'\A\d{4}-\d{2}-\d{2}\Z' ) reg.search('2020-06-01' ) is not None reg.match ('2020-06-01' ) is not None reg = re.compile (r'\d{4}-\d{2}' ) reg.findall('2020-05 2020-06' )
/^\d{4 }-\d{2 }-\d{2 }$/.test ("2020-06-01" ) var regex = new RegExp (/^\d{4}-\d{2}-\d{2}$/ )regex.test ("2020-01-01" ) var regex = /^\d{4}-\d{2}-\d{2}$/ "2020-06-01" .search (regex)
$regex = '/^\d{4}-\d{2}-\d{2}$/' ;$ret = preg_match ($regex , "2020-06-01" );var_dump ($ret );
提取文本内容:
import rereg = re.compile (r'\d{4}-\d{2}' ) reg.findall('2020-05 2020-06' ) reg = re.compile (r'(\d{4})-(\d{2})' ) reg.findall('2020-05 2020-06' ) [('2020' , '05' ), ('2020' , '06' )] reg = re.compile (r'(\d{4})-(\d{2})' ) for match in reg.finditer('2020-05 2020-06' ): print ('date: ' , match [0 ]) print ('year: ' , match [1 ]) print ('month:' , match [2 ])
"2020-06 2020-07" .match (/\d{4}-\d{2}/g )"2020-06 2020-07" .match (/\d{4}-\d{2}/ )
$regex = "/\d{4}-\d{2}/" ;$str = "2020-05 2020-04" ;$matchs = [];preg_match_all ($regex , $str , $matchs , PREG_SET_ORDER);var_dump ($matchs );
替换文本内容:
reg = re.compile (r'(\d{2})-(\d{2})-(\d{4})' ) reg.sub(r'\3年\1月\2日' , '02-20-2020 05-21-2020' ) reg.sub(r'\g<3>年\g<1>月\g<2>日' , '02-20-2020 05-21-2020' ) reg.subn(r'\3年\1月\2日' , '02-20-2020 05-21-2020' )
"02-20-2020 05-21-2020" .replace (/(\d{2})-(\d{2})-(\d{4})/g , "$3年$1月$2日" )"02-20-2020 05-21-2020" .replace (/(\d{2})-(\d{2})-(\d{4})/ , "$3年$1月$2日" )
$ret = preg_replace ('/(\d{2})-(\d{2})-(\d{4})/' , '\3年\1月\2日' , "02-20-2020 05-21-2020" );var_dump ($ret );
切割文本内容:
reg = re.compile (r'\W+' ) reg.split("apple, pear! orange; tea" ) reg.split("apple, pear! orange; tea" , 1 )
"apple, pear! orange; tea" .split (/\W+/ )"apple, pear! orange; tea" .split (/\W+/ , 1 )"apple, pear! orange; tea" .split (/\W+/ , 2 )"apple, pear! orange; tea" .split (/\W+/ , 10 )
$ret = preg_split ('/\W+/' , 'apple, pear! orange; tea' );var_dump ($ret );$ret = preg_split ('/\W+/' , 'apple, pear! orange; tea' , 2 );var_dump ($ret );
11 匹配原理以及优化原则 回溯不可怕,我们要尽量减少回溯后的判断
import rex = '-' * 1000000 + 'abc' timeit re.search('abc' , x)
提前编译好正则。 尽量准确表示匹配范围:匹配引号里面的内容 .+?
改写为 [^"]+
。 提取出公共部分:(abcd|abxy)
=> ab(cd|xy)
,(^this|^that)
=> ^th(is|at)
。 出现可能性大的放左边:\.(?:com|net)\b
。 只在必要时才使用子组:把不需要保存子组的括号中加上 ?:
来表示只用于归组。 警惕嵌套的子组重复:(.*)*
匹配的次数会呈指数级增长,尽量不要写这样的正则。 避免不同分支重复匹配。 NFA 是以表达式为主导的,先看正则表达式,再看文本。而 DFA 则是以文本为主导的,先看文本,再看正则表达式。POSIX NFA 是指符合 POSIX 标准的 NFA 引擎,它会不断回溯,以确保找到最左侧最长匹配。
12 常见问题 import rere.match (r'^(?:(?!\d\d)\w){6}$' , '11abcd' ) re.match (r'^(?:\w(?!\d\d)){6}$' , '11abcd' )
正负号、可二位小数、小数位末尾 0 无影响 Regulex :^[-+]?\d+(?:\.(?:\d){0,2}0*)?$
手机号码:1(?:3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[1389])\d{8}
身份证:[1-9]\d{14}(\d\d[0-9Xx])?
邮政编码:(?<!\d)\d{6}(?!\d)
中文字符:[\u4E00-\u9FFF]
\p{Han}
邮箱:a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+
程语言的角度来理解正则 命令式编程的世界观是:程序是由若干行动指令组成的有序列表; 命令式编程的方法论是:用变量来存储数据,用语句来执行指令。 声明式编程的世界观是:程序是由若干目标任务组成的有序列表; 声明式编程的方法论是:用语法元素来描述任务,由解析引擎转化为指令并执行。 References 《精通正则表达式(第三版)》 《正则指引(第二版)》 – EOF –