正则表达式

你有你的规则,我有我的正则

开发中经常用到正则表达式,但总是靠搜索,有的结果过时或者不正确,导致浪费很多时间。与其将时间浪费在搜索上倒不如自己好好地掌握它。

正则规则


分四大类来说明: 常用符号说明符号数目匹配逻辑语句符号匹配目标匹配符号

一、常用符号说明

  1. ^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与”\n”或”\r”之后的位置匹配。
  2. $匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与”\n”或”\r”之前的位置匹配。
  3. \将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,”n”匹配字符”n”。”\n”匹配换行符

二、符号数目匹配

  1. *零次或多次 匹配前面的字符或子表达式。例如,”zo*“ 匹配”z”和”zoo”。* 等效于{0,}
  2. +一次或多次 匹配前面的字符或子表达式。例如,”zo+”与”zo”和”zoo”匹配,但与”z”不匹配。+ 等效于 {1,}
  3. ?零次或一次 匹配前面的字符或子表达式。例如,”do(es)?”匹配”do”或”does”中的”do”。? 等效于 {0,1}
  4. {n}n 是非负整数。正好匹配 n 次。例如,”o{2}”表示此匹配两个o
  5. {n,}n 是非负整数。至少匹配 n 次。
  6. {n,m}n 和 m 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。

三、逻辑语句符号匹配

  1. x|y匹配 x 或 y。例如,’z|food’ 匹配”z”或”food”。’(z|f)ood’ 匹配”zood”或”food”。
  2. ()括号
    • (pattern): 匹配 pattern 并捕获该匹配的子表达式
    • (?:pattern): 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。例如,industr(?:y|ies) 是比 industry|industries 更经济的表达式。
    • (?=pattern)?=pattern
    • (?!pattern)?!pattern

四、目标匹配符号

  1. [xyz]字符集。匹配包含的任一字符。例如,”[abc]”代表当前位只能是abc三个字符中的一个
  2. [^xyz]反向字符集。匹配未包含的任何字符。例如,”[^abc]“ 则当前位不能是abc中的任意一个。
  3. [a-z]字符范围。匹配指定范围内的任何字符。如:
    • [a-z]: 当前位匹配从a到z字符
    • [A-Z]: 当前位匹配从A到Z字符
    • [0-9]: 当前位匹配从0到9字符
    • [a-zA-Z0-9]: 当前位匹配从a到z,从A到Z以及从0-9的字符
    • [a-zA-Z0-9_]: 当前位匹配从a到z,从A到Z以及从0-9的字符和_字符
    • [a-zA-Z0-9_-]: 当前位匹配从a到z,从A到Z以及从0-9的字符和_字符
  4. [^a-z]反向范围字符。匹配不在指定的范围内的任何字符.
  5. \d匹配数字,与[0-9]等效.
  6. \D匹配非数字字符,与[^0-9]等效.
  7. \w匹配任何字类字符,包括下划线。与”[A-Za-z0-9_]“等效.
  8. \W与任何非单词字符匹配。与”[^A-Za-z0-9_]“等效。
  9. .匹配除”\r\n”之外的任何单个字符。若要匹配包括”\r\n”在内的任意字符,请使用诸如”[\s\S]”之类的模式。
  10. \b匹配一个字边界,即字与空格间的位置。例如,”er\b”匹配”never”中的”er”,但不匹配”verb”中的”er”。多用于java中的正则查找与替换
  11. \B非字边界匹配。”er\B”匹配”verb”中的”er”,但不匹配”never”中的”er”。
  12. \s匹配任何空白字符,包括空格、制表符、换页符等。与 [\f\n\r\t\v] 等效。
  13. \S匹配任何非空白字符。与 [^\f\n\r\t\v]等效。
  14. 各种空白符:
    • \f: 换页符匹配。等效于 \x0c 和 \cL。
    • \n: 换行符匹配。等效于 \x0a 和 \cJ。
    • \r: 匹配一个回车符。等效于 \x0d 和 \cM。
    • \t: 制表符匹配。与 \x09 和 \cI 等效。
    • \v: 垂直制表符匹配。与 \x0b 和 \cK 等效。
  15. \xn匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,”\x41”匹配”A”。允许在正则表达式中使用 ASCII 代码。
  16. \un匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。附:Unicode编码说明,汉字Unicode编码表

常见正则


下面分别对常见的邮箱和手机号举例。

邮箱:

常见邮箱形式:

  1. `[email protected]`
  2. `[email protected]`
  3. `[email protected]`
  4. `[email protected]`
  5. `[email protected]`

网络上正则,仅仅能匹配 1,2 两种格式:

^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

满足需求正则:

  1. 匹配全部:^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$
  2. 等价匹配:^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
  3. 或者:^(\w)+(\.\w+)*@(\w)+((\.\w{2,3}){1,3})$
  4. 注意java代码里需要将所有 \ 转义,要在所有 \ 前多加一个 \

java 代码:

1
2
3
4
public static boolean isEmail(String email) {
	String regex = "^(\\w)+(\\.\\w+)*@(\\w)+((\\.\\w{2,3}){1,3})$";
	return email.matches(regex);
}

手机号码:

手机基本格式如下:

  1. 共11位:
  2. 13(0-9):13开头手机号第三位0-9全可以
  3. 14(5,7):14开头的第三位只有5(联通),7(移动)
  4. 15(-4):15开头的第三位除了4,其他都有,因为(154有谐音“要我死”)
  5. 17(3,5,6,7,8):17开头的三位有3,5,6,7,8;其中3,7为电信,5,6为联通,8为移动。
  6. 18(0-9):18开头的第三位0-9均可以
  7. 而170和171的为虚拟运营商:
    1
    2
    3
    4
    1700/1701/1702(电信)
    1703/1705/1706(移动)
    1704/1707/1708/1709(联通)
    171(联通)

虚拟商除外,匹配前三个位置数字正则:

  1. 1与5可以一起写: 1[3,8][0-9]
  2. 第二种: 14[5,7]
  3. 第三种: 15[^4]
  4. 第四种: 17[3,5-8]
  5. 合并为: ^((1[3,8][0-9])|(14[5,7])|(15[^4])|(17[3,5-8]))\d{8}$
  6. 注意:\d代表数字,如果要写入 java 代码,则需要多加一条 \ 来转义。如果不想写转义因为\d等价于[0-9]所以也可以换为:^((1[3,8][0-9])|(14[5,7])|(15[^4])|(17[3,5-8]))[0-9]{8}$

Java 代码:

1
2
3
4
5
6
7
public static boolean isChinaPhoneLegal(String str) throws PatternSyntaxException {  
	String regExp = "^((1[3,8][0-9])|(14[5,7])|(15[^4])|(17[3,5-8]))\\d{8}$"; 
	//String regExp = "^((1[3,8][0-9])|(14[5,7])|(15[^4])|(17[3,5-8]))[0-9]{8}$";  
	Pattern p = Pattern.compile(regExp);  
	Matcher m = p.matcher(str);  
	return m.matches();  
}

占用端口进程

我们用 Linux 命令获取占用 22 端口信息如下:

1
2
3
[root@vm]# netstat -tnlp | grep ':22 '
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      21392/sshd          
tcp6       0      0 :::22                   :::*                    LISTEN      21392/sshd

只需要用 Python 中 commands 包执行对应命令,取得第一行结果即可得到字符串,我们直接使用该字符用正则提出进程名:

1
2
3
4
tcp_str = "tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      21392/sshd"
reg_p = re.compile(r'.+ (\d+)/(\w+)')
m = reg_p.match(tcp_str)
ps_str = m.group(2) # 0 为原字符串,1为进程ID,2为进程名,这里为 sshd

获取磁盘挂载信息

fdisk 命令

获取插入主机的所有磁盘 fdisk 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@vm]# fdisk -l

Disk /dev/vda: 42.9 GB, 42949672960 bytes, 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0008b9e9

   Device Boot      Start         End      Blocks   Id  System
/dev/vda1   *        2048     1026047      512000   83  Linux
/dev/vda2         1026048    83886079    41430016   8e  Linux LVM

Disk /dev/vdb: 107.4 GB, 107374182400 bytes, 209715200 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/centos-root: 40.2 GB, 40227569664 bytes, 78569472 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/centos-swap: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

我们只需要 /dev/vdx 这样的信息,因此需要过滤:

1
2
3
[root@vm]# fdisk -l | egrep '^Disk /dev/[a-z]d[a-z]' | awk -F: '{print $1}' | awk '{print $2}'
/dev/vda
/dev/vdb

其中 egrep 是选取符合的行如下:

1
2
3
[root@vm]# fdisk -l | egrep '^Disk /dev/[a-z]d[a-z]'
Disk /dev/vda: 42.9 GB, 42949672960 bytes, 83886080 sectors
Disk /dev/vdb: 107.4 GB, 107374182400 bytes, 209715200 sectors

后面两个 awk 则不断 split 字符串选取出最终的字符。

df 命令

上面的 fdisk 是获取所有的磁盘信息,包括已经挂载以及未挂载的磁盘,而 df 则获取仅仅挂载的信息,这样对于企业中的监控获取未挂载的磁盘,则只需用 fdisk 结果减 df 的结果,这里只介绍用 df 获取磁盘信息:

1
2
3
4
5
6
7
8
9
10
[root@vm]# df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   38G  6.5G   32G  18% /
devtmpfs                 3.9G     0  3.9G   0% /dev
tmpfs                    3.9G     0  3.9G   0% /dev/shm
tmpfs                    3.9G  401M  3.5G  11% /run
tmpfs                    3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/vda1                497M  150M  348M  31% /boot
/dev/vdb                  99G   61M   94G   1% /data
tmpfs                    783M     0  783M   0% /run/user/0

我们也只需要 /dev/vdx 这样的信息:

1
2
3
[root@vm]# df -Th | /usr/bin/egrep '^/dev/[a-z]d[a-z]\d*' | awk '{print $1}'
/dev/vda1
/dev/vdb

/proc/partitions 文件

这个文件也记录了 Centos 系统的磁盘挂载信息,与 fdisk 命令的结果一致,但是用文件搜索方式比通过 commands 包调用 fdisk 命令效率更高,因此,推荐用此方式来查系统挂载信息:

1
2
3
[root@vm]# cat /proc/partitions | egrep ' [a-z]d[a-z]+$' | awk '{print $4}'
vda
vdb

/proc/mounts 文件

这个文件则是与 df 命令相似,只记录已经挂载的磁盘信息,并且效率高于 df 命令:

1
2
3
[root@vm]# cat /proc/mounts | egrep '^/dev/[a-z]d[a-z]+\d*'
/dev/vda1 /boot xfs rw,relatime,attr2,inode64,noquota 0 0
/dev/vdb /data ext4 rw,relatime,data=ordered 0 0

这个先查询出行,再用 awk 对行进行截取,这个我就不写了,自己大家可以 awk

参考