分类
leetcode

快速掌握grep命令及正则表达式(grep的正则表达式)

Linux系统自带了支持拓展正则表达式的 GNU 版本 grep 工具,所有的Linux发行版中均默认安装grep ,grep 命令被用来检索一台服务器或工作站上任何位置的文本信息,如何在 Linux 系统和类 Unix 的操作系统中使用带正则表达式的 grep 命令呢?

快速了解正则表达式

1、如何匹配你要查找的内容?

正则表达式只不过是每个输入行匹配的模式。

在 ‘/etc/passswd’ 中检索 ‘vivek’ 。

grep vivek /etc/passwd

输出结果案例:

vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh
gitevivek:x:1002:1002::/home/gitevivek:/bin/sh

在任何情况下都搜索 ‘vivek’ (即不区分大小):

grep -i -w vivek /etc/passwd

不区分大小写地检索 ‘vivek’ 和 ‘raj’ :

grep -E -i -w 'vivek|raj' /etc/passwd

在最后一个例子中,使用了扩展正则表达式的模式。

固定检索内容的位置:

你可以使用 ^ 和 $ 符号强制一个正则表达式分别匹配一行的开始或结束的位置。下面的示例显示以 ‘vivek’ 开头的文本。

grep ^vivek /etc/passwd

输出结果示例:

vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh

你可以只显示以 vivek 开头的文本行。举例说就是不显示 vivekgite , vivekg 这样单词开头的。

grep -w ^vivek /etc/passwd

检索以 ‘foo’ 结尾的文本格式:

grep 'foo
FILENAME

你还可以用下面这样的方式搜索空白行:

grep '^
FILENAME

2、如何匹配具体字符?

匹配 ‘Vivek’ 或 ‘vivek’ :

grep '[vV]ivek' FILENAME

或者可以这样:

grep '[vV][iI][Vv][Ee][kK]' FILENAME

你可以匹配数字(例如匹配 vivek1 或 Vivek2 ):

grep -w '[vV]ivek[0-9]' FILENAME

你可以匹配两位数(例如匹配 foo11 , foo12 ):

grep 'foo[0-9][0-9]' FILENAME

不仅仅是数字,你可以匹配字母:

grep '[A-Za-z]' FILENAME

显示所有包含 “w” 或 “n” 字母的文本行:

grep [wn] FILENAME

在括号内的表达式中,在“ [: ”和“ :] ”中所附的字符类的名称:代表属于该类的所有字符的列表。标准字符类名称:

[:alnum:] – 字母数字字符。
[:alpha:] – 字母顺序
[:blank:] – 空格和制表符。
[:digit:] – 数字: ‘0 1 2 3 4 5 6 7 8 9’。
[:lower:] – 小写字母:‘a b c d e f ‘。
[:space:] – 特殊字符:制表符,换行符,垂直制表符、换页,回车,和空间。
[:upper:] – 大写字母:‘A B C D E F G H I J K L M N O P Q R S T U V W X Y Z’。

在下面这个例子中,匹配所有大写字母:

grep '[:upper:]' FILENAME

3、如何使用通配符?

你可以用 “.” 来代替单个字符。在下面的例子中,查询了所有以字母 “b” 开头、字母 “t” 结尾的三个字符的单词。

grep '\<b.t\>' FILENAME

在上面的例子中:

\< 在单词的开始位置匹配空格字符串
\> 在单词的结尾匹配空格字符串

检索并输出所有两个字母的结果:

grep '^..
FILENAME

检索并显示所有以 ‘.’ 和数字开头的结果:

grep '^\.[0-9]' FILENAME
转义字符’.’

下面的正则表达式查找 IP 地址 192.168.1.254 将不能获得预期的结果:

grep '192.168.1.254' /etc/hosts

其中三个点都需要被转义:

grep '192\.168\.1\.254' /etc/hosts

以下示例将只匹配一个地址:

egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' FILENAME

以下将不分大小写地匹配单词 Linux 或 Unix :

egrep -i '^(linux|unix)' FILENAME

深入探索 grep 高级查找模式

1、如何检索一个具有以 ‘-‘ 开头的的模式?

使用 -e 选项搜索所有匹配 ‘–test–‘ 的结果。grep 会尝试把 ‘–test–‘ 作为一个选项解析:

grep -e '--test--' FILENAME

2、如何在grep中使用 OR 的逻辑运算 ?

grep -E 'word1|word2' FILENAME### OR ###egrep 'word1|word2' FILENAME

或者可以这样做

grep 'word1\|word2' FILENAME

3、如何在grep中使用 AND 的逻辑运算 ?

按照下面的语法显示所有包含了单词 ‘word1′ 和 ‘word2′ 的结果:

grep 'word1' FILENAME | grep 'word2'

或者可以这样:

grep 'foo.*bar\|word3.*word4' FILENAME

4、如何测试序列?

你可以使用下面的语法测试一个字符在序列中的重复的次数:

{N}
{N,}
{min,max}

匹配包含两个字母 v 的字符串结果:

egrep "v{2}" FILENAME

下面的例子中将检索文件内包含 “col” 和 “cool” 的字符串结果:

egrep 'co{1,2}l' FILENAME

下面的例子中将匹配至少含有3个字母 c 的结果:

egrep 'c{3,}' FILENAME

下面的示例将匹配 “91-1234567890″ 格式的手机号码(即 “两位数字-十位数字”)

grep "[[:digit:]]\{2\}[ -]\?[[:digit:]]\{10\}" FILENAME

5、如何使 grep 的输出结果高亮标注?

使用下面例子的语法:

grep --color regex FILENAME

6、如何使 grep 的输出只显示匹配的部分而不是整行?

使用下面例子的语法:

grep -o regex FILENAME

正则表达式操作符总结

正则表达式:操作符 含义

. 匹配任何单个字符。
? 匹配前一个字符0次或1次。
* 匹配前一个字符≥0次。
+ 匹配前一个字符≥1次。
{N} 匹配前一个字符N次。
{N,} 匹配前一个字符≥m次。
{N,M} 匹配前一个字符 N 到 M次。
– 如果在列表中的某个列表或某个范围内的结束点,表示该范围。
^ 开始标记,表示在开始位置匹配一个空字符串。也表示不在列表的范围内的字符。
$ 结束标记。匹配一个空的字符串。
\b 单词锁定符。在一个单词的边缘位置匹配空字符串。
\B 在一个单词的非边缘位置匹配空字符串。
\< 匹配单词开始的空字符串。
\> 匹配单词结尾的空字符串。
分类
leetcode

python 编程:正则表达式(Python正则表达式教程)

(1) 正则表达式

【是什么】:特殊的序列。检测字符串是否与序列匹配。

【有什么作用】:实现快速检索文本,或实现替换文本的操作。

实战:查看字符串是否包含python

  • findal查找全部
  • re.findall(需要匹配的内容, 查找的字符串)
a = "C|Java|C#|python|PHP|JS"
# 方法一
print(a.index("python"))
# 方法二
import re
r = re.findall("python", a)
if len(r) != 0:
    print("字符串中包含python")

输出:
10
字符串中包含python

(2) 元字符与普通字符

实战:在字符串中把所有的数字提取出来

  • ‘python’ 是普通字符
  • ‘\d’ 是元字符,表示数字:0-9 ,代表一定的意义
  • '\D' 表示‘\d’的相反,不是0-9
a = "C123Java321C#455python1321PHP1243JS"

import re
r = re.findall("\d", a)
print(r)

r = re.findall("\D", a)
print(r)

输出:
['1', '2', '3', '3', '2', '1', '4', '5', '5', '1', '3', '2', '1', '1', '2', '4', '3']
['C', 'J', 'a', 'v', 'a', 'C', '#', 'p', 'y', 't', 'h', 'o', 'n', 'P', 'H', 'P', 'J', 'S']

(3) 字符集

实战:查找中间单词是 c,或者 f 的字符集

  • [] 可以抽象字符集,可以抽象c或者f,借助普通字符a和c来进行定界
  • ^ 表示否的意思
              • – 表示顺序,匹配 a 到 f 的字符
import re
a = "abc,acc,afc,adc,arc"
r1 = re.findall("a[cf]c", a)
r2 = re.findall("a[^cf]c", a)
r3 = re.findall("a[c-f]c", a)
print(r1)
print(r2)
print(r3)

输出:
['acc', 'afc']
['abc', 'adc', 'arc']
['acc', 'afc', 'adc']

(4) 元字符还有什么

\w : 表示字母 a-z、A-Z 和数字0-9 和 _

import re
a = "a [email protected]!arc_"
r1 = re.findall("\w", a)
r2 = re.findall("\W", a)
print(r1)
print(r2)

输出:
['a', 'b', 'c', 'a', 'c', 'c', 'a', 'f', 'c', 'a', 'd', 'c', 'a', 'r', 'c', '_']
[' ', '!', '#39;, '@', '!']

\s : 表示空白字符

import re
a = "a [email protected]!arc_"
r1 = re.findall("\s", a)
r2 = re.findall("\S", a)
print(r1)
print(r2)

输出:
[' ']
['a', 'b', 'c', '!', 'a', 'c', 'c', '#39;, 'a', 'f', 'c', '@', 'a', 'd', 'c', '!', 'a', 'r', 'c', '_']

. :表示除换行符之外的所有字符

(5) 数量词

实战:把python匹配出来

  • 表示重复的字符集,可以用数字表示
  • {} 里面写的数字,表示重复几次
import re
a = "c123java321C#455python1321php1243JS"
r1 = re.findall("[a-z][a-z][a-z][a-z][a-z][a-z]", a)
r2 = re.findall("[a-z]{6}", a)
print(r1)
print(r2)

输出:
['python']
['python']

实战:把 python,java,php 匹配出来

  • {} 里面写的数字,可以写有多少到多少的数字
import re
a = "c123java321C#455python1321php1243JS"
r1 = re.findall("[a-z]{3,6}", a)
print(r1)

输出:
['java', 'python', 'php']

(6) 贪婪与非贪婪

默认情况,python是贪婪模式,匹配最多的合适的数量。

? :在数字范围后表示非贪婪

实战: 获取3个字母

import re
a = "c123java321C#455python1321php1243JS"
r1 = re.findall("[a-z]{3,6}?", a)
r2 = re.findall("[a-z]{3}", a)
print(r1)
print(r2)

输出:
['jav', 'pyt', 'hon', 'php']
['jav', 'pyt', 'hon', 'php']

(7) 匹配0次1次或者无限多次

*表示匹配前面的字符0次或者多次

+表示匹配前面的字符1次或者多次

?表示匹配前面的字符0次或者1次,可以做去除的操作

import re
a = "pytho0python1pythonn2pythonnn333"
r1 = re.findall("python*", a)
r2 = re.findall("python+", a)
r3 = re.findall("python?", a)
r4 = re.findall("python{1,3}?", a) # 表示非贪婪模式
print(r1)
print(r2)
print(r3)
print(r4)
输出:
['pytho', 'python', 'pythonn', 'pythonnn']
['python', 'pythonn', 'pythonnn']
['pytho', 'python', 'python', 'python']
['python', 'python', 'python']

(8) 边界匹配符

实战: 验证位数是否符合

  • ^ 表示从字符串前面开始匹配
  • $ 表示从字符串后面开始匹配
import re
a = "100086"
r1 = re.findall("\d{3,5}", a)
r2 = re.findall("^\d{3,5}#34;, a)
print(r1)
print(r2)

输出:
['10008']
[]
import re
a = "100086"
r1 = re.findall("086#34;, a)
r2 = re.findall("^1000", a)
r3 = re.findall("000#34;, a)
r4 = re.findall("^000", a)
print(r1)
print(r2)
print(r3)
print(r4)

输出:
['086']
['1000']
[]
[]

(9) 组

判断字符串中是否包含 2个python

  • python{3} : 表示单个n重复3次
  • () :表示组,可以有多个组
  • (python){3}表示一组的单词,重复3次
  • [] 是或者的关系,()是且的关系
import re
a = "pythonpython2pythonpython2nvvpythonpythonn"
r1 = re.findall("(python){2}", a)
r2 =  re.findall("(python){2}(2)", a)
print(r1)
print(r2)

输出:
['python', 'python', 'python']
[('python', '2'), ('python', '2')]

(10) 匹配模式参数

flags=re.I : 表示不区分大小写

| : 连接多个模式,且

import re
a = "pc#ythonpython2pythonpython2nvvpythonpythonn"
r1 = re.findall("C#", a,flags=re.I) 
print(r1)

输出:
['c#']

re.S : 表示 .匹配所有字符,包括换行符

import re
a = "pc#\nythonpython2pythonpython2nvvpythonpythonn"
r1 = re.findall("C#.{1}", a, flags=re.I | re.S)
print(r1)

输出:
['c#\n']

(11) re.sub正则替换

re.sub("正则匹配规则",“需要换成的字符串”,“查找的字符串”,count=0,flag)

实战 : c# 换成 python

import re
a = "c#phpc#javac#"
r1 = re.sub("c#", "python", a)

输出:
pythonphppythonjavapython

count=0 :默认是0,所能被替换的最大选项

import re
a = "c#phpc#javac#"
r1 = re.sub("c#", "python", a,count=1)

输出:
pythonphpc#javac#

内置函数 replace 同样可以实现替换

a = "c#phpc#javac#"
print(a.replace("c#", "python"))
输出:
pythonphppythonjavapython

(12) 把函数作为参数传递

sub第二个参数,可以接收函数。

根据不同的匹配结果做不同的操作。

import re
def conver(value):
    print(value)
    matched = value.group()
    print(matched)
    return "111" + matched + "111"
a = "c#phpc#javac#"
r1 = re.sub("c#", conver, a)
print(r1)

输出:
<re.Match object; span=(0, 2), match='c#'>
c#
<re.Match object; span=(5, 7), match='c#'>
c#
<re.Match object; span=(11, 13), match='c#'>
c#
111c#111php111c#111java111c#111

实战:匹配字符串,数字大于60变成9,否则变成0

import re
def conver(value):
    matched = value.group()
    if int(matched) >= 60:
        return "9"
    else:
        return "0"
a = "ABC123987456DE"
r1 = re.sub("\d{2}", conver, a)
print(r1)

输出:
ABC00906DE

(13) search与match函数

  • 搜索到了一个,就停止搜索
  • 返回结果都是组
  • match 从字符串首字母开始匹配
  • search 搜索字符串,直到找到满足条件的字符串
a = "1490ABC123987456DE"
r1 = re.match("\d", a)  #从字符串首字母开始匹配 
print(r1.group())
r2 = re.search("\d", a)  #搜索字符串,直到找到满足条件的字符串
print(r2.group())
print(r2.span())  # 返回位置

输出:
1
1
(0, 1)

(14) group分组

实战:获取 A 和 E 中间的字符

0 是默认取值 ,获取完整匹配结果。

a = "A 14956 63E234234E"
r1 = re.match("A(.*)E", a)
print(r1.group(1))   1 是第一个组

r1 = re.match("A(.*)E(.*)E", a)  # 可以有很多个组
print(r1.group(0, 1, 2))

print(r1.groups())  # 把匹配到的内容输出

print(re.findall("A(.*)E(.*)E", a))

输出:
 14956 63E234234
('A 14956 63E234234E', ' 14956 63', '234234')
(' 14956 63', '234234')
[(' 14956 63', '234234')]

(15) JSON

【Json】 是一种轻量级的数据交换格式。

【JSON字符串】:符合json格式的字符串

  • 字符串是json的一种表现形式。

【优势】:易于阅读,解析,网络传输效率高。跨语言交换数据。

(16)序列化与反序列化

【反序列化】json.loads()

json字符串需要用双引号。

实战:json字符串转换python的字典

import json
j_str = '{"name":"CiCi","age":18}'
print(type(json.loads(j_str)))

输出:
<class 'dict'>

json字符串的 true 和 false是小写开头的。

json字符串的 null 是 None。

import json

j_str = '[{"name":"CiCi","flag":true},{"name":"CiCi","flag":false}]' 
print(json.loads(j_str))

输出:[{'name': 'CiCi', 'flag': True}, {'name': 'CiCi', 'flag': False}]

【序列化】json.dumps()

import json
dict_p = {"name": "CiCi", "flag": True}
print(json.dumps(dict_p))

输出:{"name": "CiCi", "flag": true}

分类
leetcode

不说概念了!直接告诉你这些常用正则表达式是怎么写出来的

作为一名程序员,不会写正则表达式总感觉少了点什么,不要求你能把正则玩出花来,但最起码要对常用的正则表达式手到擒来,刚毕业的我对于正则也是一头雾水,不过学会它也就一篇教程的事情[1]

本文我不会再浪费带宽把正则的规则再次重复一遍,而是从实际入手,直接告诉你为什么这么写

16进制颜色

按照规则来

  1. # 开头
  2. 后面紧跟着6个字符或者3个字符作为结尾,这些字符可以是 a-f 的小写字母、A-F的大写字母、数字

第一句,可以写成 /^#/;第二句,[a-fA-F0-9] 表示任意的 a-fA-F0-963的个数可以用 {6}{3}进行表示,那么3个字符就是 [a-fA-F0-9]{3},6个字符就是 [a-fA-F0-9]{6},这两个都有可能,用一个或(|)符号来连接:([a-fA-F0-9]{6}|[a-fA-F0-9]{3}),最后结尾可以用个 $

所有合到一起就是 /^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/

链接

目标是匹配出协议、域名、端口号portpathsearch

  1. 协议

合法的协议有 httphttps,还有一个是自适应协议,即不明确加协议,跟当前页面的协议保持一致,所以以下都是合法的:http://toutiao.comhttps://toutiao.com//toutiao.com。这三个协议组成的链接共同点是肯定有 // 字符串,在 //的前面可能是 https: 也可能是 http: 也可以没有任何字符串 先按照 https:// 这种写规则:^https:\/\/,其中的 s 字符可能有也可能没有,所以使用 ? 修饰:^https?:\/\/,又因为 https?:可能没有,所以这个字符串也用 ?修饰:^(https?:)?\/\/

  1. 域名

域名的前面可能是 //,从 //往后面匹配,只要没有代表 :port、代表 search?、代表 path/,那么就都属于域名:[^?:/]+

  1. 端口号 port

端口号肯定以 : 开头,后面跟着的只要是数字就都属于 port:\d+,由于不一定有端口号,所以用 ? 修饰:(:\d+)?

  1. path

肯定以 / 开头,只要不遇到代表 search?,那么就都属于 path\/[^?]*,由于可能没有 path,所以用 ? 修饰:(\/[^?]*)? 5. search 肯定以 ? 开头,后面所有的字符都属于 search(不考虑 hash 路由):\?(.*),由于可能没有 search,所以用 ? 修饰:(\?.*)?

最后把上面所有规则合起来就是提取链接的完整正则了,考虑到需要精确提取所需要的部分,所以会对所需要提取的部分加上小括号,结果为:/^((https?):)?\/\/([^?:/]+)(:(\d+))?(\/[^?]*)?(\?(.*))?/

邮箱

以前在知乎上看到过一段邮箱正则,号称是最符合标准的正则表达式,那条正则的体积好像有几十KB吧,总之很长,现在找不到了,这里只关注常用的邮箱格式,规则:名称允许汉字、字母、数字,下划线,中划线,域名可以有数字、字母、下划线、中划线组成

汉字的范围是 [\u4e00-\u9fa5],字母的范围是 [a-zA-Z],数字的范围是 [0-9],合起来组成邮箱的名称 ^[A-Za-z0-9-_\u4e00-\u9fa5]+

域名是 [a-zA-Z0-9_-]+,域名后缀可以是多级域名 (\.[a-zA-Z0-9_-]+)+

上面组合起来就是 ^[A-Za-z0-9-_\u4e00-\u9fa5][email protected][a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

手机号

手机号的号段可能是会增加的,所以在实际场景中不建议限制得太死了

本正则按照以下规则编写:

  1. 11位数字,以数字 1 开头,即 ^1
  2. 接下来的数字如果是 3,那么 3后面可以跟一个任意数字,即 3\d;如果是 4,那么 4后面可以跟一个5-9之间的数字,即 4[5-9];如果是 5,那么 5后面可以跟一个0-35-9之间的数字,即 5[0-35-9];如果是 6,那么 6后面可以跟 2567 其中一个数字,即 6[2567];如果是 7,那么 7后面可以跟一个0-8之间的数字,即 7[0-8];如果是 8,那么 8后面可以跟一个任意数字,即 8\d;如果是 9,那么 9后面可以跟一个0-35-9之间的数字,即 9[0-35-9]
  3. 最后 8位可以是任意数字

上述三步合起来就是 /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/

数字/货币金额

  1. 支持负数

负号用 负号,且必须在第一位,即 ^-,再加个 ? 用于表示这个负号可以有也可以没有,即 ^-? 2. 支持千分位分隔(没有也没关系) 如果有千分位,则千分位的后面必然跟着三位数字(否则这个千分号就不应该加了),千分位前面最少一位、最多三位数字,那么可以写成 \d{1,3},\d{3},再精简下,千分位前面的数字其实可以不用限制,因为只要超过三位肯定就有千分位,就会被 \d{3}捕获,所以 \d{1,3}换成 \d+就行了,因为符合千分位的可以有多个也可能没有,所以写成 \d+(,\d{3})* 3. 如果有小数,则小数点后最多两位 小数点就是 \.,后面跟着最多两位数字 \d{1,2},可能有小数也可能没有,所以整体需要再加个 ? 符号,即 (\.\d{1,2})?

最终规则 /^-?\d+(,\d{3})*(\.\d{1,2})?$/

身份证号

这里只看 2代身份证,18位数字 最后一位是校验位,可能为数字或字符X

  1. 第一位数字在 [1-9] 闭区间内,后面紧跟着5位任意数字,写成 ^[1-9]\d{5}
  2. 再紧跟着的四位数字代表年份(YYYY),因为目前有身份证的人最早是19世纪最晚21世纪,所以这四个数字中的前两位只看是 181920,即 (18|19|20),后两位则可以是任意数字,即 \d{2}
  3. 再紧跟着两位数字是月(MM),月份只可能是 [1-12]闭区间,所以可以写成 (01|02|03|04|05|06|07|08|09|10|11|12),前九位的开头都是 0,第二位是 [1-9] 内的数字,所以简化成 (0[1-9]|10|11|12)
  4. 再紧跟着两位数字是日(DD),范围是 [01-31],可以将这31个数字罗列出来,当然也可以精简下,看成是 [00-09][10-29][30-31]的组合,即 (0[1-9]|[1-2]\d|30|31)
  5. 再紧跟着三位数字是顺序码,即 \d{3}
  6. 最后一位是校验码,可以是数字也可以是 X,即 [\dX]

最终规则 /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|10|11|12)(0[1-9]|[1-2]\d|30|31)\d{3}[\dX]$/

密码校验

最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符

对于 至少1个大写字母 这条规则,这个大写字母的位置是不固定的,只要有就行,如果只有这一条规则的话,正则可以写成 ^\S*[A-Z]+\S*$\S 匹配任意非空白字符,这个规则即代表大写字母的前面、后面可以跟着任意个(包括0个)非空白字符

但除此之外还需要满足最少1个小写字母,1个数字,1个特殊字符,最少6位,你可以将这几条规则都单独写出正则,然后目标字符串跟这5条正则一一匹配,只要全部能匹配上就是对的,写成 js 代码就是:

function match(s: string) {
  return /^\S*[A-Z]+\S*$/.test(s)
    && /^\S*[a-z]+\S*$/.test(s)
    && /^\S*[0-9]+\S*$/.test(s)
    && /^\S*[[email protected]#$%^&*?]+\S*$/.test(s)
    && /^\S*\S{6,}\S*$/.test(s)
}
复制代码

如果就想在一条正则里实现这些校验呢,也是可以的,需要借助 零宽度正预测先行断言(?=exp)),代表 匹配exp前面的位置

有了这个东西,就可以把上面5条规则写到一起去了:/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[[email protected]#$%^&*?])\S*$/

这条正则前面后面的 \S* 还是之前的意思不变,中间是提取了5条规则的个性部分,然后通过 ?= 放在一起了,把规则里所有的 ?=去掉行不行?不行,因为如果去掉的话,首先就有顺序上的冲突了,例如上面的规则,如果把所有的 ?=去掉,就代表着 数字必须在大写字母前面,大写字母必须在小写字母前面,特殊字母必须在小写字母前面(除此之外整个正则也是有问题的)

你可以认为 ?= 在匹配的时候会忽略掉其他的 ?=,只关心自己的前面能不能匹配成功,有多个 ?=,则这多个 ?= 都是只关心自己,忽略其他,但整条正则最后的结果是所有 ?= 匹配结果的并集,计算逻辑和上面的 js 是差不多的

提取 HTML 标签数据

要提取的标签字符串类似于 <div class="header-box" name="header">

只是正则话无法完成,需要借助 js

首先,把标签的属性提取出来

这段标签包含标签开始符号、tag、属性字符串、标签结束符号

开始符号是 <,标签名紧跟着开始符号,且只要没遇到空白符就都是标签名,所以连起来就是 <\w+\s*

在开始符号+标签名,和 结束符号的中间,都是属性,结束符号是 >,所以只要没遇到结束符号 >,就认为是属性字符串,用到了反向选择 [^>]*\s*>,合起来就是 /<\w+\s*[^>]*\s*>/,为了能捕获属性字符串,加个小括号,即 /<\w+\s*([^>]*)\s*>/

const str = `<div class="header-box" name="header">`
const mt = str.match(/<\w+\s*([^>]*)\s*>/)
// properties 即 属性字符串,即 class="header-box" name="header"
const properties = mt[1]
复制代码

取到了 class="header-box" name="header" 之后,再对其进行处理,观察规律,每个属性的键值对之间肯定存在空白符,不过却不能通过空白符来直接分割,因为属性值是可以存在空白符的,例如 class="a b"

由于可能是自闭合标签,自闭合标签的最后有没有 / 都是合法的,例如 <hr>hr />都是合法的,所以需要兼容下:/<\w+\s*([^>]*)\s*\/?>/

但属性名是可以确定的,它可能是 = 左边不包括空白符的内容,再次用到反向选择,从左往右匹配,反向选择既不是=也不是不是空白符的内容,即 [^\s=]+

虽然不确定属性值是否包含空白符,但有个是确定的,即属性值必然被引号包围,所以直接取 = 右侧所有引号的内容即可,=".*?"

不过还有个问题,引号不仅可以是单引号还可以是双引号,即 =".*?"='.*?' 都行,如果第一个引号是双引号开头那么对应的第二个引号也必然是双引号,反义单引号亦然,这里需要用到捕获的规则了 =(["']).*?\1\1的意思是这块匹配的内容跟第一捕获组一样,第一捕获组也就是 ["'],如果第一捕获组匹配的是双引号,那么 \1 就代表双引号,否则就代表单引号

至此整个正则为 [^\s=]+=(["']).*?\1

不过还有个问题,属性是可以没有属性值的,例如 <input type="checkbox" checked />,这里 checked 就是可以不写属性值的,所以再兼容下:/[^\s=]+(=(["']).*?\2)?/,又因为希望捕捉属性和属性值,所以给属性和属性值加个小括号:/([^\s=]+)(=(["'])(.*?)\3)?/

上面的代表就可以继续写了

const str = `<div class="header-box" name="header">`
const mt = str.match(/<\w+\s*([^>]*)\s*>/)
// properties 即 属性字符串,即 class="header-box" name="header"
const properties = mt[1]
const mt1 = properties.match(/([^\s=]+)(=(["'])(.*?)\3)?/g)
const obj = {}
if (mt1) {
  mt1.forEach(p => {
    const kv = p.trim().split('=')
    obj[kv[0].trim()] = kv[1].trim().slice(1, -1)
  })
}
// obj => { class: 'header-box', name: 'header' }
复制代码

小结

学会正则的最接途径就是勤加练习,平时遇到可以用正则解决的问题,就尝试着正则解决,或许你一开始写不出来,但可以去网上看看别人是怎么写的,再自己独立写一遍,写得多了自然就会了,没什么诀窍,无非就是对正则规则的熟练掌握罢了

关于本文

来自:清夜

https://juejin.cn/post/7073360739410378760


分类
leetcode

正则表达式(正则表达式在线测试工具)

  • 1. 正则表达式分类
  • 2. 基本正则表达式
  • 3. 扩展正则表达式

1. 正则表达式分类

正则表达式:REGEXP,REGular EXPression。
正则表达式分为两类:

  • Basic REGEXP(基本正则表达式)
  • Extended REGEXP(扩展正则表达式)

2. 基本正则表达式

//元字符

. //任意单个字符

[] //匹配指定范围内的任意单个字符

[^] //匹配指定范围外的任意单个字符//匹配次数(贪婪模式)

* //匹配其前面的任意单个字符任意次

.* //任意长度的任意字符

\? //匹配其前面的任意单个字符1次或0次

\+ //匹配其前面的任意单个字符至少1次

\{m,n\} //匹配其前面的任意单个字符至少m次,至多n次//位置锚定

^ //锚定行首,此字符后面的任意单个字符必须出现在行首

$ //锚定行尾,此字符前面的任意单个字符必须出现在行尾

^$ //空白行

\<或\b //锚定词首,其后面的任意单个字符必须作为单词首部出现

\>或\b //锚定词尾,其前面的任意单个字符必须作为单词尾部出现

/分组

\(\)

例:\(ab\)*

//后向引用

\1 //引用第一个左括号以及与之对应的右括号所包括的所有内容

\2 //引用第二个左括号以及与之对应的右括号所包括的所有内容

3. 扩展正则表达式

//字符匹配

. //匹配任意单个字符

[] //匹配指定范围内的任意单个字符

[^] //匹配指定范围外的任意单个字符//次数匹配

* //匹配其前面的任意单个字符任意次

? //匹配其前面的任意单个字符1次或0次

+ //匹配其前面的任意单个字符至少1次

{m,n} //匹配其前面的任意单个字符至少m次,至多n次

//位置锚定

^ //锚定行首,此字符后面的任意单个字符必须出现在行首

$ //锚定行尾,此字符前面的任意单个字符必须出现在行尾

^$ //空白行

\<或\b //锚定词首,其后面的任意单个字符必须作为单词首部出现

\>或\b //锚定词尾,其前面的任意单个字符必须作为单词尾部出现//分组

() //分组

\1,\2,\3,….

例:(ab)*

//后向引用

\1 //引用第一个左括号以及与之对应的右括号所包括的所有内容

\2 //引用第二个左括号以及与之对应的右括号所包括的所有内容//或者

| //or 默认匹配|的整个左侧或者整个右侧的内容

//例:C|cat表示C或者cat,要想表示Cat或者cat则需要使用分组,如(C|c)at

分类
leetcode

10分钟精通正则表达式(精通正则表达式 百度云)

概述

?正则表达式是强大、便捷、高效的文本处理工具。

?正则表达式能够添加、删除、分离、叠加、插入和修改各种类型的文本和数据。

?使用正则表达式节省下来的时间或许并不能让人很激动,但总比把时间消耗在文本编辑器中要好

?正则表达式由元字符文字组成,元字符指的是*、+、^等字符,文字指的是字母、数字等字符;元符通常用来描述文本。

元字符-匹配单个字符的元字符

元字符

匹配对象

.

点号

匹配单个任意字符

[…]

字符组

匹配单个列出的字符,如[abc]

[^…]

排除型字符组

匹配单个未列出的字符,如[^abc]

\char

转义字符

若char是元字符,或转义序列无特殊含义时,匹配char对应的普通字符

注:在字符组内部(并且不在第一个位置和最后一个位置),

‘-’连字符作为元字符表示一个范围,如[a-z];如果不在字符组内部,

连字符代表普通字符。

元字符-提供计数功能的元字符

元字符

匹配对象

问号

容许匹配一次,但非必须

*

星号

可以匹配任意多次,也可能不匹配

+

加号

至少匹配一次,至多可能任意多次

{min,max}

区间量词

到少需要min次,至多容许max次

元字符-匹配位置的元字符

元字符

匹配对象

^

脱字符

匹配一行的开头位置

$

美元符

匹配一行的结束位置

\<

单词分界符

匹配单词的开始位置

\>

单词分界符

匹配单词的结束位置

元字符-其它元字符

元字符

匹配对象

|

alternation

匹配任意分隔的表达式

(…)

括号

限定多选结构的范围,为反向应用捕获文本

\1,\2.. $1,$2..

反向引用

匹配之前的第一、第二组括号内的表达式匹配的文本

各种量词

?匹配优先量词

*、+、?、{min,max}

用ab*c去匹配abcde,肯定会匹配abc

?忽略优先量词

*?、+?、??、{min,max}?

用ab*?c去匹配acde肯定会匹配ac,不会返回什么都没匹配

?占有优先量词

*+、++、?+、{min,max}+

用(.*+)([0-9]+)去匹配aaaaa1,什么也匹配不到,一旦占有不再交还,目前只有java和php有

捕获分组和反向引用

?捕获分组:将某些规律看成是一组,然后进行组级别的重复,可以得到意想不到的效果,正则表达式中分组用()表示,不参与捕获分组又要使用括号用(?:)表示。

?反向引用:反向应用实际上应用的是正则表达式所匹配的文本值,而不是正则表达式字面值。

捕获分组和反向引用-示例1

文本样本(从中抽取出ip信息):
:10.153.135.53:8611:4::
:10.153.110.123:8611:1::
:10.15.242.66:8611:4::
:10.15.242.65:8611:4::
:10.153.110.120:8611:1::
:10.153.110.122:8611:1::
:10.153.110.121:8611:1::
正则表达式::([0-9.]+):([0-9]+):(.+)

捕获分组和反向引用-示例2

文本样本(为图片相对路径增加绝对路径):
<html>
<body>
	<div>12321321</div>
	<img src="/tv/pic/1.jpg"></img>
	<img src="/tv/pic/2.jpg"></img>
	<div>lasjdfljsadfpofdsahdsaf</div>
	<img src="/tv/pic/3.jpg"></img>
	<img src="/tv/pic/4.jpg"></img>
	<div>pwqipqpwepqweo;sdkf</div>
	<img src="/tv/pic/5.jpg"></img>
</body>
</html>
正则表达式:<img\s+src="([0-9a-z/.]+)">
替换为:<img src="http://www.pic0.baidu.com\1">

捕获分组和反向引用-示例3

正则表达式:([a-z]+)\s+\1
替换为:\1

If – then -else

正则表达式中的if-else结构用(?(condition)(reg1)|(reg2))
举例(匹配img标签):
<div>
<img src="123">
</div>

正则表达式:(<div>\s*)?(<img\s+[^>]+>)(?(1)\s*</div>)

环视

环视不匹配任何字符,只匹配文本中的特定位置。

类型

正则表达式

匹配成功的条件

肯定逆序环视

(?<=…..)

子表达式能够匹配左侧文本

否定逆序环视

(?<!….)

子表达式不能匹配左侧文本

肯定顺序环视

(?=….)

子表达式能够匹配右侧文本

否定顺序环视

(?!….)

子表达式不能匹配右侧文本

环视-肯定顺序示例

提取特征数据
样本示例:
1234.txt
456.java
abc.cpp
9876.java
正则表达式  .+(?=\.java) 

环视-格式化数字

样本示例(每三位逗号分隔数字):
1234567890
12345
6789056
456823

正则表达式:(?<=\d)(?=(\d{3})+$)

固化分组

固化分组使用(?>…)实现,固化分组匹配与正常的匹配并无差别,但是如果匹配进行到此结构之后(也就是,进行到闭括号之后),那么此结构体中的所有备用状态都会被放弃(不能被回溯)。
处理数字小数点,保留三位小数,第三位不能为零。
样本:
123123.456000000
12345.670000
1234789.78
134.500000

正则表达式:(\.\d\d(?>[1-9]?))\d+     替换:\1

分类
leetcode

史上最全的正则表达式-匹配中英文、字母和数字

在做项目的过程中,使用正则表达式来匹配一段文本中的特定种类字符,是比较常用的一种方式,下面是对常用的正则匹配做了一个归纳整理。

1、匹配中文:[\u4e00-\u9fa5]

2、英文字母:[a-zA-Z]

3、数字:[0-9]

4、匹配中文,英文字母和数字及下划线:^[\u4e00-\u9fa5_a-zA-Z0-9]+$

同时判断输入长度:

[\u4e00-\u9fa5_a-zA-Z0-9_]{4,10}

5、

(?!_) 不能以_开头

(?!.*?_$) 不能以_结尾

[a-zA-Z0-9_\u4e00-\u9fa5]+ 至少一个汉字、数字、字母、下划线

$ 与字符串结束的地方匹配

6、只含有汉字、数字、字母、下划线,下划线位置不限:

^[a-zA-Z0-9_\u4e00-\u9fa5]+$

7、由数字、26个英文字母或者下划线组成的字符串

^\w+$

8、2~4个汉字

“^[\u4E00-\u9FA5]{2,4}$”;

9、最长不得超过7个汉字,或14个字节(数字,字母和下划线)正则表达式

^[\u4e00-\u9fa5]{1,7}$|^[\dA-Za-z_]{1,14}$

10、匹配双字节字符(包括汉字在内):[^x00-xff]

评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

11、匹配空白行的正则表达式:ns*r

评注:可以用来删除空白行

12、匹配HTML标记的正则表达式:<(S*?)[^>]*>.*?|<.*? />

评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

13、匹配首尾空白字符的正则表达式:^s*|s*$

评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式

14、匹配Email地址的正则表达式:^[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$

评注:表单验证时很实用

15、手机号:^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\d{8}$

16、身份证:(^\d{15}$)|(^\d{17}([0-9]|X|x)$)

17、匹配网址URL的正则表达式:[a-zA-z]+://[^s]*

评注:网上流传的版本功能很有限,上面这个基本可以满足需求

18、匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

评注:表单验证时很实用

19、匹配国内电话号码:d{3}-d{8}|d{4}-d{7}

评注:匹配形式如 0511-4405222 或 021-87888822

20、匹配腾讯QQ号:[1-9][0-9]{4,}

评注:腾讯QQ号从10000开始

21、匹配中国邮政编码:[1-9]d{5}(?!d)

评注:中国邮政编码为6位数字

22、匹配身份证:d{15}|d{18}

评注:中国的身份证为15位或18位

23、匹配ip地址:d+.d+.d+.d+

评注:提取ip地址时有用

24、匹配特定数字:

^[1-9]d*$ //匹配正整数

^-[1-9]d*$ //匹配负整数

^-?[1-9]d*$ //匹配整数

^[1-9]d*|0$ //匹配非负整数(正整数 + 0)

^-[1-9]d*|0$ //匹配非正整数(负整数 + 0)

^[1-9]d*.d*|0.d*[1-9]d*$ //匹配正浮点数

^-([1-9]d*.d*|0.d*[1-9]d*)$ //匹配负浮点数

^-?([1-9]d*.d*|0.d*[1-9]d*|0?.0+|0)$ //匹配浮点数

^[1-9]d*.d*|0.d*[1-9]d*|0?.0+|0$ //匹配非负浮点数(正浮点数 + 0)

^(-([1-9]d*.d*|0.d*[1-9]d*))|0?.0+|0$ //匹配非正浮点数(负浮点数 + 0)

评注:处理大量数据时有用,具体应用时注意修正

25、匹配特定字符串:

^[A-Za-z]+$ //匹配由26个英文字母组成的字符串

^[A-Z]+$ //匹配由26个英文字母的大写组成的字符串

^[a-z]+$ //匹配由26个英文字母的小写组成的字符串

^[A-Za-z0-9]+$ //匹配由数字和26个英文字母组成的字符串

^w+$ //匹配由数字、26个英文字母或者下划线组成的字符串

26、

在使用RegularExpressionValidator验证控件时的验证功能及其验证表达式介绍如下:

只能输入数字:“^[0-9]*$”

只能输入n位的数字:“^d{n}$”

只能输入至少n位数字:“^d{n,}$”

只能输入m-n位的数字:“^d{m,n}$”

只能输入零和非零开头的数字:“^(0|[1-9][0-9]*)$”

只能输入有两位小数的正实数:“^[0-9]+(.[0-9]{2})?$”

只能输入有1-3位小数的正实数:“^[0-9]+(.[0-9]{1,3})?$”

只能输入非零的正整数:“^+?[1-9][0-9]*$”

只能输入非零的负整数:“^-[1-9][0-9]*$”

只能输入长度为3的字符:“^.{3}$”

只能输入由26个英文字母组成的字符串:“^[A-Za-z]+$”

只能输入由26个大写英文字母组成的字符串:“^[A-Z]+$”

只能输入由26个小写英文字母组成的字符串:“^[a-z]+$”

只能输入由数字和26个英文字母组成的字符串:“^[A-Za-z0-9]+$”

只能输入由数字、26个英文字母或者下划线组成的字符串:“^w+$”

验证用户密码:“^[a-zA-Z]w{5,17}$”正确格式为:以字母开头,长度在6-18之间,

只能包含字符、数字和下划线。

验证是否含有^%&’,;=?$”等字符:“[^%&’,;=?$x22]+”

只能输入汉字:“^[u4e00-u9fa5],{0,}$”

验证Email地址:“^w+[-+.]w+)*

验证InternetURL:“^http://([w-]+.)+[w-]+(/[w-./?%&=]*)?$”

验证身份证号(15位或18位数字):“^d{15}|d{}18$”

验证一年的12个月:“^(0?[1-9]|1[0-2])$”正确格式为:“01”-“09”和“1”“12”

验证一个月的31天:“^((0?[1-9])|((1|2)[0-9])|30|31)$”

正确格式为:“01”“09”和“1”“31”。

匹配中文字符的正则表达式: [u4e00-u9fa5]

匹配双字节字符(包括汉字在内):[^x00-xff]

匹配空行的正则表达式:n[s| ]*r

匹配HTML标记的正则表达式:/<(.*)>.*|<(.*) />/

匹配首尾空格的正则表达式:(^s*)|(s*$)

匹配Email地址的正则表达式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*

匹配网址URL的正则表达式:http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?

分类
leetcode

深入浅出之正则表达式(如何理解正则表达式)

本文是Jan Goyvaerts为RegexBuddy写的教程的译文。

1. 什么是正则表达式

基本说来,正则表达式是一种用来描述一定数量文本的模式。Regex代表Regular Express。本文将用<<regex>>来表示一段具体的正则表达式。

一段文本就是最基本的模式,简单的匹配相同的文本。

2. 不同的正则表达式引擎

正则表达式引擎是一种可以处理正则表达式的软件。通常,引擎是更大的应用程序的一部分。在软件世界,不同的正则表达式并不互相兼容。本教程会集中讨论Perl 5 类型的引擎,因为这种引擎是应用最广泛的引擎。同时我们也会提到一些和其他引擎的区别。许多近代的引擎都很类似,但不完全一样。例如.NET正则库,JDK正则包。

3. 文字符号

最基本的正则表达式由单个文字符号组成。如<<a>>,它将匹配字符串中第一次出现的字符“a”。如对字符串“Jack is a boy”。“J”后的“a”将被匹配。而第二个“a”将不会被匹配。

正则表达式也可以匹配第二个“a”,这必须是你告诉正则表达式引擎从第一次匹配的地方开始搜索。在文本编辑器中,你可以使用“查找下一个”。在编程语言中,会有一个函数可以使你从前一次匹配的位置开始继续向后搜索。

类似的,<<cat>>会匹配“About cats and dogs”中的“cat”。这等于是告诉正则表达式引擎,找到一个<<c>>,紧跟一个<<a>>,再跟一个<<t>>。

要注意,正则表达式引擎缺省是大小写敏感的。除非你告诉引擎忽略大小写,否则<<cat>>不会匹配“Cat”。

· 特殊字符

对于文字字符,有12个字符被保留作特殊用途。他们是:

[ ] \ ^ $ . | ? * + ( )

这些特殊字符也被称作元字符。

如果你想在正则表达式中将这些字符用作文本字符,你需要用反斜杠“\”对其进行换码 (escape)。例如你想匹配“1+1=2”,正确的表达式为<<1\+1=2>>.

需要注意的是,<<1+1=2>>也是有效的正则表达式。但它不会匹配“1+1=2”,而会匹配“123+111=234”中的“111=2”。因为“+”在这里表示特殊含义(重复1次到多次)。

在编程语言中,要注意,一些特殊的字符会先被编译器处理,然后再传递给正则引擎。因此正则表达式<<1\+2=2>>在C++中要写成“1\\+1=2”。为了匹配“C:\temp”,你要用正则表达式<<C:\\temp>>。而在C++中,正则表达式则变成了“C:\\\\temp”。

· 不可显示字符

可以使用特殊字符序列来代表某些不可显示字符:

<<\t>>代表Tab(0x09)

<<\r>>代表回车符(0x0D)

<<\n>>代表换行符(0x0A)

要注意的是Windows中文本文件使用“\r\n”来结束一行而Unix使用“\n”。

4. 正则表达式引擎的内部工作机制

知道正则表达式引擎是如何工作的有助于你很快理解为何某个正则表达式不像你期望的那样工作。

有两种类型的引擎:文本导向(text-directed)的引擎和正则导向(regex-directed)的引擎。Jeffrey Friedl把他们称作DFA和NFA引擎。本文谈到的是正则导向的引擎。这是因为一些非常有用的特性,如“惰性”量词(lazy quantifiers)和反向引用(backreferences),只能在正则导向的引擎中实现。所以毫不意外这种引擎是目前最流行的引擎。

你可以轻易分辨出所使用的引擎是文本导向还是正则导向。如果反向引用或“惰性”量词被实现,则可以肯定你使用的引擎是正则导向的。你可以作如下测试:将正则表达式<<regex|regex not>>应用到字符串“regex not”。如果匹配的结果是regex,则引擎是正则导向的。如果结果是regex not,则是文本导向的。因为正则导向的引擎是“猴急”的,它会很急切的进行表功,报告它找到的第一个匹配 。

· 正则导向的引擎总是返回最左边的匹配

这是需要你理解的很重要的一点:即使以后有可能发现一个“更好”的匹配,正则导向的引擎也总是返回最左边的匹配。

当把<<cat>>应用到“He captured a catfish for his cat”,引擎先比较<<c>>和“H”,结果失败了。于是引擎再比较<<c>>和“e”,也失败了。直到第四个字符,<<c>>匹配了“c”。<<a>>匹配了第五个字符。到第六个字符<<t>>没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配。

5. 字符集

字符集是由一对方括号“[]”括起来的字符集合。使用字符集,你可以告诉正则表达式引擎仅仅匹配多个字符中的一个。如果你想匹配一个“a”或一个“e”,使用<<[ae]>>。你可以使用<<gr[ae]y>>匹配gray或grey。这在你不确定你要搜索的字符是采用美国英语还是英国英语时特别有用。相反,<<gr[ae]y>>将不会匹配graay或graey。字符集中的字符顺序并没有什么关系,结果都是相同的。

你可以使用连字符“-”定义一个字符范围作为字符集。<<[0-9]>>匹配0到9之间的单个数字。你可以使用不止一个范围。<<[0-9a-fA-F] >>匹配单个的十六进制数字,并且大小写不敏感。你也可以结合范围定义与单个字符定义。<<[0-9a-fxA-FX]>>匹配一个十六进制数字或字母X。再次强调一下,字符和范围定义的先后顺序对结果没有影响。

· 字符集的一些应用

查找一个可能有拼写错误的单词,比如<<sep[ae]r[ae]te>> 或 <<li[cs]en[cs]e>>。

查找程序语言的标识符,<<A-Za-z_][A-Za-z_0-9]*>>。(*表示重复0或多次)

查找C风格的十六进制数<<0[xX][A-Fa-f0-9]+>>。(+表示重复一次或多次)

· 取反字符集

在左方括号“[”后面紧跟一个尖括号“^”,将会对字符集取反。结果是字符集将匹配任何不在方括号中的字符。不像“.”,取反字符集是可以匹配回车换行符的。

需要记住的很重要的一点是,取反字符集必须要匹配一个字符。<<q[^u]>>并不意味着:匹配一个q,后面没有u跟着。它意味着:匹配一个q,后面跟着一个不是u的字符。所以它不会匹配“Iraq”中的q,而会匹配“Iraq is a country”中的q和一个空格符。事实上,空格符是匹配中的一部分,因为它是一个“不是u的字符”。

如果你只想匹配一个q,条件是q后面有一个不是u的字符,我们可以用后面将讲到的向前查看来解决。

· 字符集中的元字符

需要注意的是,在字符集中只有4个 字符具有特殊含义。它们是:“] \ ^ -”。“]”代表字符集定义的结束;“\”代表转义;“^”代表取反;“-”代表范围定义。其他常见的元字符在字符集定义内部都是正常字符,不需要转义。例如,要搜索星号*或加号+,你可以用<<[+*]>>。当然,如果你对那些通常的元字符进行转义,你的正则表达式一样会工作得很好,但是这会降低可读性。

在字符集定义中为了将反斜杠“\”作为一个文字字符而非特殊含义的字符,你需要用另一个反斜杠对它进行转义。<<[\\x]>>将会匹配一个反斜杠和一个X。“]^-”都可以用反斜杠进行转义,或者将他们放在一个不可能使用到他们特殊含义的位置。我们推荐后者,因为这样可以增加可读性。比如对于字符“^”,将它放在除了左括号“[”后面的位置,使用的都是文字字符含义而非取反含义。如<<[x^]>>会匹配一个x或^。<<[]x]>>会匹配一个“]”或“x”。<<[-x]>>或<<[x-]>>都会匹配一个“-”或“x”。

· 字符集的简写

因为一些字符集非常常用,所以有一些简写方式。

<<\d>>代表<<[0-9]>>;

<<\w>>代表单词字符。这个是随正则表达式实现的不同而有些差异。绝大多数的正则表达式实现的单词字符集都包含了<<A-Za-z0-9_]>>。

<<\s>>代表“白字符”。这个也是和不同的实现有关的。在绝大多数的实现中,都包含了空格符和Tab符,以及回车换行符<<\r\n>>。

字符集的缩写形式可以用在方括号之内或之外。<<\s\d>>匹配一个白字符后面紧跟一个数字。<<[\s\d]>>匹配单个白字符或数字。<<[\da-fA-F]>>将匹配一个十六进制数字。

取反字符集的简写

<<[\S]>> = <<[^\s]>>

<<[\W]>> = <<[^\w]>>

<<[\D]>> = <<[^\d]>>

· 字符集的重复

如果你用“?*+”操作符来重复一个字符集,你将会重复整个字符集。而不仅是它匹配的那个字符。正则表达式<<[0-9]+>>会匹配837以及222。

如果你仅仅想重复被匹配的那个字符,可以用向后引用达到目的。我们以后将讲到向后引用。

6. 使用?*或+ 进行重复

?:告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。

+:告诉引擎匹配前导字符1次或多次

*:告诉引擎匹配前导字符0次或多次

<[A-Za-z][A-Za-z0-9]*>匹配没有属性的HTML标签,“<”以及“>”是文字符号。第一个字符集匹配一个字母,第二个字符集匹配一个字母或数字。

我们似乎也可以用<[A-Za-z0-9]+>。但是它会匹配<1>。但是这个正则表达式在你知道你要搜索的字符串不包含类似的无效标签时还是足够有效的。

· 限制性重复

许多现代的正则表达式实现,都允许你定义对一个字符重复多少次。词法是:{min,max}。min和max都是非负整数。如果逗号有而max被忽略了,则max没有限制。如果逗号和max都被忽略了,则重复min次。

因此{0,}和*一样,{1,}和+ 的作用一样。

你可以用<<\b[1-9][0-9]{3}\b>>匹配1000~9999之间的数字(“\b”表示单词边界)。<<\b[1-9][0-9]{2,4}\b>>匹配一个在100~99999之间的数字。

· 注意贪婪性

假设你想用一个正则表达式匹配一个HTML标签。你知道输入将会是一个有效的HTML文件,因此正则表达式不需要排除那些无效的标签。所以如果是在两个尖括号之间的内容,就应该是一个HTML标签。

许多正则表达式的新手会首先想到用正则表达式<< <.+> >>,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能期望会返回<EM>,然后继续进行匹配的时候,返回</EM>。

但事实是不会。正则表达式将会匹配“<EM>first</EM>”。很显然这不是我们想要的结果。原因在于“+”是贪婪的。也就是说,“+”会导致正则表达式引擎试图尽可能的重复前导字符。只有当这种重复会引起整个正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,然后处理正则表达式余下的部分。

和“+”类似,“?*”的重复也是贪婪的。

· 深入正则表达式引擎内部

让我们来看看正则引擎如何匹配前面的例子。第一个记号是“<”,这是一个文字符号。第二个符号是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的结束。然后到了换行符,匹配失败(“.”不匹配换行符)。于是引擎开始对下一个正则表达式符号进行匹配。也即试图匹配“>”。到目前为止,“<.+”已经匹配了“<EM>first</EM> test”。引擎会试图将“>”与换行符进行匹配,结果失败了。于是引擎进行回溯。结果是现在“<.+”匹配“<EM>first</EM> tes”。于是引擎将“>”与“t”进行匹配。显然还是会失败。这个过程继续,直到“<.+”匹配“<EM>first</EM”,“>”与“>”匹配。于是引擎找到了一个匹配“<EM>first</EM>”。记住,正则导向的引擎是“急切的”,所以它会急着报告它找到的第一个匹配。而不是继续回溯,即使可能会有更好的匹配,例如“<EM>”。所以我们可以看到,由于“+”的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。

· 用懒惰性取代贪婪性

一个用于修正以上问题的可能方案是用“+”的惰性代替贪婪性。你可以在“+”后面紧跟一个问号“?”来达到这一点。“*”,“{}”和“?”表示的重复也可以用这个方案。因此在上面的例子中我们可以使用“<.+?>”。让我们再来看看正则表达式引擎的处理过程。

再一次,正则表达式记号“<”会匹配字符串的第一个“<”。下一个正则记号是“.”。这次是一个懒惰的“+”来重复上一个字符。这告诉正则引擎,尽可能少的重复上一个字符。因此引擎匹配“.”和字符“E”,然后用“>”匹配“M”,结果失败了。引擎会进行回溯,和上一个例子不同,因为是惰性重复,所以引擎是扩展惰性重复而不是减少,于是“<.+”现在被扩展为“<EM”。引擎继续匹配下一个记号“>”。这次得到了一个成功匹配。引擎于是报告“<EM>”是一个成功的匹配。整个过程大致如此。

· 惰性扩展的一个替代方案

我们还有一个更好的替代方案。可以用一个贪婪重复与一个取反字符集:“<[^>]+>”。之所以说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每一个字符进行回溯。而使用取反字符集则不需要进行回溯。

最后要记住的是,本教程仅仅谈到的是正则导向的引擎。文本导向的引擎是不回溯的。但是同时他们也不支持惰性重复操作。

7. 使用“.”匹配几乎任意字符

在正则表达式中,“.”是最常用的符号之一。不幸的是,它也是最容易被误用的符号之一。

“.”匹配一个单个的字符而不用关心被匹配的字符是什么。唯一的例外是新行符。在本教程中谈到的引擎,缺省情况下都是不匹配新行符的。因此在缺省情况下,“.”等于是字符集[^\n\r](Window)或[^\n]( Unix)的简写。

这个例外是因为历史的原因。因为早期使用正则表达式的工具是基于行的。它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去。在这些工具中,字符串是不包含新行符的。因此“.”也就从不匹配新行符。

现代的工具和语言能够将正则表达式应用到很大的字符串甚至整个文件上去。本教程讨论的所有正则表达式实现都提供一个选项,可以使“.”匹配所有的字符,包括新行符。在RegexBuddy, EditPad Pro或PowerGREP等工具中,你可以简单的选中“点号匹配新行符”。在Perl中,“.”可以匹配新行符的模式被称作“单行模式”。很不幸,这是一个很容易混淆的名词。因为还有所谓“多行模式”。多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。

其他语言和正则表达式库也采用了Perl的术语定义。当在.NET Framework中使用正则表达式类时,你可以用类似下面的语句来激活单行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)

· 保守的使用点号“.”

点号可以说是最强大的元字符。它允许你偷懒:用一个点号,就能匹配几乎所有的字符。但是问题在于,它也常常会匹配不该匹配的字符。

我会以一个简单的例子来说明。让我们看看如何匹配一个具有“mm/dd/yy”格式的日期,但是我们想允许用户来选择分隔符。很快能想到的一个方案是<<\d\d.\d\d.\d\d>>。看上去它能匹配日期“02/12/03”。问题在于02512703也会被认为是一个有效的日期。

<<\d\d[-/.]\d\d[-/.]\d\d>>看上去是一个好一点的解决方案。记住点号在一个字符集里不是元字符。这个方案远不够完善,它会匹配“99/99/99”。而<<[0-1]\d[-/.][0-3]\d[-/.]\d\d>>又更进一步。尽管他也会匹配“19/39/99”。你想要你的正则表达式达到如何完美的程度取决于你想达到什么样的目的。如果你想校验用户输入,则需要尽可能的完美。如果你只是想分析一个已知的源,并且我们知道没有错误的数据,用一个比较好的正则表达式来匹配你想要搜寻的字符就已经足够。

8. 字符串开始和结束的锚定

锚定和一般的正则表达式符号不同,它不匹配任何字符。相反,他们匹配的是字符之前或之后的位置。“^”匹配一行字符串第一个字符前的位置。<<^a>>将会匹配字符串“abc”中的a。<<^b>>将不会匹配“abc”中的任何字符。

类似的,$匹配字符串中最后一个字符的后面的位置。所以<<cgt;>匹配“abc”中的c。

· 锚定的应用

在编程语言中校验用户输入时,使用锚定是非常重要的。如果你想校验用户的输入为整数,用<<^\d+gt;>。

用户输入中,常常会有多余的前导空格或结束空格。你可以用<<^\s*>>和<<\s*gt;>来匹配前导空格或结束空格。

· 使用“^”和“$”作为行的开始和结束锚定

如果你有一个包含了多行的字符串。例如:“first line\n\rsecond line”(其中\n\r表示一个新行符)。常常需要对每行分别处理而不是整个字符串。因此,几乎所有的正则表达式引擎都提供一个选项,可以扩展这两种锚定的含义。“^”可以匹配字串的开始位置(在f之前),以及每一个新行符的后面位置(在\n\r和s之间)。类似的,$会匹配字串的结束位置(最后一个e之后),以及每个新行符的前面(在e与\n\r之间)。

在.NET中,当你使用如下代码时,将会定义锚定匹配每一个新行符的前面和后面位置:Regex.Match(“string”, “regex”, RegexOptions.Multiline)

应用:string str = Regex.Replace(Original, “^”, “> “, RegexOptions.Multiline)–将会在每行的行首插入“> ”。

· 绝对锚定

<<\A>>只匹配整个字符串的开始位置,<<\Z>>只匹配整个字符串的结束位置。即使你使用了“多行模式”,<<\A>>和<<\Z>>也从不匹配新行符。

即使\Z和$只匹配字符串的结束位置,仍然有一个例外的情况。如果字符串以新行符结束,则\Z和$将会匹配新行符前面的位置,而不是整个字符串的最后面。这个“改进”是由Perl引进的,然后被许多的正则表达式实现所遵循,包括Java,.NET等。如果应用<<^[a-z]+gt;>到“joe\n”,则匹配结果是“joe”而不是“joe\n”。

分类
leetcode

30分钟入门正则表达式!超详细的学习笔记整理,建议收藏

REGEXP: Regular Expressions,由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符) 不表示字符字面意义,而表示控制或通配的功能,类似于增强版的通配符功能

正则表达式被很多程序和开发语言所广泛支持:vim, less,grep,sed,awk, nginx,mysql 等

正则表达式分两类:

  • 基本正则表达式:BRE
  • 扩展正则表达式:ERE

正则表达式引擎:

采用不同算法,检查处理正则表达式的软件模块,如:PCRE(Perl Compatible Regular Expressions)

正则表达式的元字符分类:字符匹配、匹配次数、位置锚定、分组

帮助:man 7 regex

基本正则表达式元字符

1、字符匹配

.   匹配任意单个字符
[]   匹配指定范围内的任意单个字符,示例:[wang]   [0-9]   [a-z]   [a-zA-Z]
[^] 匹配指定范围外的任意单个字符,示例:[^wang] 
[:alnum:] 字母和数字
[:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:] 小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:] 大写字母
[:blank:] 空白字符(空格和制表符)
[:space:] 水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
[:digit:] 十进制数字
[:xdigit:]十六进制数字
[:graph:] 可打印的非空白字符
[:print:] 可打印字符
[:punct:] 标点符号

范例:

[[email protected] ~]#ls /etc/ | grep 'rc[.0-6]'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
rc.d
rc.local
[[email protected] ~]#ls /etc/ | grep 'rc[.0-6].'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
rc.d
rc.local
[[email protected] ~]#ls /etc/ | grep 'rc[.0-6]\.'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d

2、匹配次数

用在要指定次数的字符后面,用于指定前面的字符要出现的次数

* 匹配前面的字符任意次,包括0次,贪婪模式:尽可能长的匹配
.* 任意长度的任意字符
\? 匹配其前面的字符0或1次,即:可有可无
\+ 匹配其前面的字符至少1次,即:肯定有
\{n\} 匹配前面的字符n次
\{m,n\} 匹配前面的字符至少m次,至多n次
\{,n\} 匹配前面的字符至多n次
\{n,\} 匹配前面的字符至少n次

范例:

[[email protected] ~]#echo /etc/ |grep -E "/etc/?"
/etc/
[[email protected] ~]#echo /etc |grep -E "/etc/?"
/etc

3、位置锚定

^ 行首锚定,用于模式的最左侧
$ 行尾锚定,用于模式的最右侧
^PATTERN$ 用于模式匹配整行
^$ 空行
^[[:space:]]*$ 空白行
\< 或 \b 词首锚定,用于单词模式的左侧
\> 或 \b 词尾锚定,用于单词模式的右侧
\<PATTERN\> 匹配整个单词

4、分组其它

分组:() 将一个或多个字符捆绑在一起,当作一个整体处理,如:\(root\)+

分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些变量的命名方式为: \1, \2, \3, …

\1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符

示例:

\(string1\(string2\)\)
\1 :string1\(string2\)
\2 :string2

后向引用:引用前面的分组括号中的模式所匹配字符,而非模式本身

或者:\|

示例:

a\|b #a或b  
C\|cat #C或cat   
\(C\|c\)at #Cat或cat

正则表达式练习

  1. 显示/proc/meminfo文件中以大小s开头的行(要求:使用两种方法)
  2. 显示/etc/passwd文件中不以/bin/bash结尾的行
  3. 显示用户rpc默认的shell程序
  4. 找出/etc/passwd中的两位或三位数
  5. 显示CentOS7的/etc/grub2.cfg文件中,至少以一个空白字符开头的且后面有非空白字符的行
  6. 找出“netstat -tan”命令结果中以LISTEN后跟任意多个空白字符结尾的行
  7. 显示CentOS7上所有UID小于1000以内的用户名和UID
  8. 添加用户bash、testbash、basher、sh、nologin(其shell为/sbin/nologin),找出/etc/passwd用户 名和shell同名的行
  9. 利用df和grep,取出磁盘各分区利用率,并从大到小排序

扩展正则表达式

1、字符匹配元字符

. 任意单个字符
[wang] 指定范围的字符
[^wang] 不在指定范围的字符
[:alnum:] 字母和数字
[:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:] 小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:] 大写字母
[:blank:] 空白字符(空格和制表符)
[:space:] 水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
[:digit:] 十进制数字
[:xdigit:]十六进制数字
[:graph:] 可打印的非空白字符
[:print:] 可打印字符
[:punct:] 标点符号

2、次数匹配

*   匹配前面字符任意次
? 0或1次
+ 1次或多次
{n} 匹配n次
{m,n} 至少m,至多n次

3、位置锚定

^ 行首
$ 行尾
\<, \b 语首
\>, \b 语尾

4、分组其它

() 分组
后向引用:\1, \2, ...
| 或者
a|b #a或b
C|cat #C或cat
(C|c)at #Cat或cat

扩展正则表达式练习

  • 显示三个用户root、mage、wang的UID和默认shell
  • 找出/etc/rc.d/init.d/functions文件中行首为某单词(包括下划线)后面跟一个小括号的行
  • 使用egrep取出/etc/rc.d/init.d/functions中其基名
  • 使用egrep取出上面路径的目录名
  • 统计last命令中以root登录的每个主机IP地址登录次数
  • 利用扩展正则表达式分别表示0-9、10-99、100-199、200-249、250-255
  • 显示ifconfig命令结果中所有IPv4地址
  • 将此字符串:welcome to magedu linux 中的每个字符去重并排序,重复次数多的排到前面
分类
易语言视频教程

精易论坛正则表达式教程

第1课:认识了解正则表达式.mp4

第2课:点号应用之简单的数据提取.mp4

第3课:星号正则的认识与应用.mp4

第4课:问号和加号的认识与应用.mp4

第5课:连字符和范围描述符的认识与应用.mp4

第6课:脱字符和美元符的认识与应用.mp4

第7课:转义符的认识与应用.mp4

第8课:字符组简记法1截取.mp4

第9课:字符组简记法2截取.mp4

第10课:大括号与选择符的认识与应用.mp4

第11课:子表达式的认识与应用.mp4

第12课:贪婪和懒惰的匹配.mp4