学会正则表达式,玩弄文本于股掌之中

2018-11-09  本文已影响52人  somenzz

1950 年, 一位叫 斯蒂芬·科尔·克莱尼的数学家发表了一篇标题为《神经网事件的表示法》的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为"正则集的代数"的表达式,因此采用"正则表达式"这个术语。

随后,肯·汤普逊将这一符号系统引入 Unix 中的 qed 编辑器 ,肯·汤普逊也是 Unix 的主要发明人。正则表达式的第一个实用应用程序诞生。

目前,正则表达式已经在很多软件中得到广泛的应用,包括 *nix(Linux, Unix等)、HP 等操作系统,PHP、C#、Java、 Python、javascript 等编程语言,以及很多的文本处理软件中,都可以看到正则表达式的影子。

今天,无论你是否从事 IT 工作,你都应该学习正则表达式,因为它不仅能让你处理文本信息时事半功倍,更能为你提供一种思维方式,更重要的是,它是通用的知识,不因具体的文本编辑软件而不同,也不因具体的编程语言而不同。

大多数的 IT 青年都知道正则表达式,也能通过 grep 来查找含有相应字符串的文本信息,但是能使用正则表达式的高级功能的,却是少数,一个重要的原因就是正则表达式的符号有点难以记忆,也很不直观。看到别人写的正则表达式,就像看天书一般。虽然正则表达式是有点丑陋,但却是最优秀的文本处理工具。学会使用正则表达式,就算你不会编程,你也轻松高效地处理文本。

假如这样的需求:有一个近上万行内容的文本文件,内容是中英文混合,毫无规律,现在要求把所有的中文全部删除,你会怎么做呢?

如果不会正则表达式,你只能一行一行地删除,会不会觉得很累?但是如果会用正则表达式,只要几秒的时间的时间即可完成。下次如果有人有类似这样的问题请你帮忙,你可以使用正则表达式,弹指间,不需要的字符串已灰飞烟灭,从此,你在别人眼里深藏功与名。(正则表达式是装逼利器 _)。

下面我尝试让你入门正则表达式,如有疑问可加微信 somenzz 交流。

1、要匹配什么

相信你肯定用过 windows 里的文件搜索功能吧,在搜索栏输入"*.doc",然后所有后缀为 doc 的文件都查找了出来,这里的 * 就是通配符。在正则表达式也是一样,* 表示通配符,表示任意数目的字符,这点相信大家都不陌生。正则表达式也有一些通配符,我们叫它元字符,这是需要记忆的,不过很容易记忆 ,如下所示:

常用的元字符

代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空格
\d 或 [0-9] 匹配一个数字
^ 匹配字符串的开始位置
$ 匹配字符串的结束位置

比如

2、要匹配多少次

有时要匹配很多次数,比如11位的手机号码,可以简单地这样写

\d\d\d\d\d\d\d\d\d\d\d
或
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]

这样写显然是非常麻烦的,正则表达式提供了匹配次数简洁语法,很容易记忆,如下所示:

重复

代码/语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

11位的手机号码的正则表达式可以简单地这样写

\d{11}
或
[0-9]{11}

假如你要匹配 5位 至 8 位的 QQ 号则可以使用:

\d{5,12}
或
[0-9]{5,12}

可能你会问了,如果要匹配*,?,+ 这些本身属于正则表达式里的字符呢? 也很简单,使用\来转义即可。要查找 * 就使用 * ,要查找 \ 就使用 \。例如:github.com 匹配 github.com,C:\Windows 匹配 C:\Windows。

3、反义

有时需要匹配不是某些字符的字符,如匹配非数字字符串,查找不含 aeiou 这 5 个字符的字符串,这时需要用到反义。

常用的反义代码

代码/语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

例子:\S+ 匹配不包含空白符的字符串,[^aeiou] 匹配不包含a,e,i,o,u 这五个字符的字符串

4 、括号表达式,或

(TEMP|TMP|TEST)+.*\d$ 表示匹配含有 TEMP 或 TMP 或 TEST ,并且以数字结尾的字符串,可用于运维中查询一些命名不规范的表或一些垃圾表,从而进行处理。如下所示:

SELECT TABNAME
FROM syscat.tables 
where REGEXP_LIKE(tabname,'(TEMP|TMP|TEST)+.*\d$','i')>0  

查询结果如下:

F_DEP_DGLS_TMP_2
F_NIN_TB_TAX_BANK_KIND_TEMP2
TEST20180828
TMP_CLTNBR_18
TMP_CZKH1
TMP_RPT_RMBGRDKLLSPB_01
TMP_RPT_RMBTXLLSPB_01
TMP_ZH1
TMP_ZH2
VT_TMP_JJK_ZJHM_15
TMP_1
TMP_SX500
TMP_ZFMMKXH2

这里我们用到了小括号(),小括号可以指定子表达式,本例中 (TEMP|TMP|TEST) 就是一个表达式,里面的 | 连接多个选项,是或的关系。后面跟 + 表示这个子表达式代表的字符至少出现 1 次。后续会详细介绍如何在 db2 中添加自定义的正则表达式函数 REGEXP_LIKE,请关注。

5、使用零宽断言

零宽断言有点不太好理解,我以一个实用的例子来说明。

实例获取本机 IP 地址

通过一个获取本机 IP 地址例子,对正则表达式有个更深入的认识,不需记忆,理解即可。
IP 地址是这样的一种格式,xxx.xxx.xxx.xxx
我们分成两部分
第一部分 xxx.xxx.xxx. 相当于是 3 个 xxx. 应该写成 (xxx.){3} , xxx. 代表一至三位的数字,可以是一位、两位或三位。因此 xxx. 的正则表达式为 [0-9]{1-3}. ,因此合起来就是 ([0-9]{1,3}.){3}
第二部分 xxx,非常简单,就是 [0-9]{1,3}
这两部分加起来,完整的正则表达式就是:

([0-9]{1,3}\.){3}[0-9]{1,3}

下面在一台 linux 机器上验证下:

[aaron@ubuntu]$ ifconfig -a | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}"
192.168.167.40
255.255.255.0
192.168.167.255
127.0.0.1
255.0.0.0
192.168.122.1
255.255.255.0
192.168.122.255

可以看出所有的 IP 地址都打印了出来。假如果要获取某一块网卡的 IP 地址,可以这样

[aaron@ubuntu]$ ifconfig eth0 | grep -oP "([0-9]{1,3}\.){3}.*(?=  netmask)"
192.168.167.40
[aaron@ubuntu]$ ifconfig eth0 | grep -oP "([0-9]{1,3}\.){3}.*(?=  broadcast)"
192.168.167.40  netmask 255.255.255.0

这里使用先行断言 (?=exp) 来匹配表达式前面的位置 ,即“ netmask”,前的位置,这样就打印出了 eth0 真正的 IP 地址,可以做为参数传递给程序使用。

零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b ^ $ < > 这样的定位作用,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。有以下 4 种断言方式:

[aaron@ubuntu]$ cat t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
www.baidu.com
www.123.com
[aaron@ubuntu]$ grep -oP "^.*(?<!(com))$" t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
[aaron@ubuntu]$ 

匹配不含某个字符串的文本

比如,匹配不含 baidu 的字符串

[aaron@ubuntu]$ grep -oP "^(?!(.*baidu)).*$" t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
www.123.com
[aaron@ubuntu]$ 

比如,匹配不含 baidu 或 hao 的字符串

[aaron@ubuntu]$ grep -oP "^(?!(.*(baidu|hao))).*$" t.txt
ww.sina.com.cn
www.educ.org
www.123.com
[aaron@ubuntu]$ 

这些正则表达式的知识是通用的,无论你用 grep 或是 ue,还是 vim,他们都天然支持正则表达式,很多编程语言也支持,因此正则表达式的知识是通用的,作为程序员一定要知道。

现在回答本文开头提到的问题,如何在文本中删除中文字符。这里我使用的是文本编辑工具是 vim,你可以使用其他文本编辑工具,只要它支持正则表达式即可。
假如文本内容如下:

 1 数字:^[0-9]*$
 2 n位的数字:^\d{n}$
 3 至少n位的数字:^\d{n,}$
 4 m-n位的数字:^\d{m,n}$
 5 零和非零开头的数字:^(0|[1-9][0-9]*)$
 6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
 7 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
 8 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
 9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
11 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
12 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
13 非负整数:^\d+$ 或 ^[1-9]\d*|0$
14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
15 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
16 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
17 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
18 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
19 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

若要去除中文信息,首先我在网上查到匹配中文的正则表达式为 [\u4e00-\u9fa5],于是在 vim 中执行命令

:%s/[\u4e00-\u9fa5]//g

其实就是查找字符串 [\u4e00-\u9fa5] 将其替换为 空即可。执行前后对比如下所示:

执行前 执行后

这里 [\u4e00-\u9fa5] 不需要记忆,一些常用的复杂的正则表达式,网上都是可以搜索到的,在做稍复杂的文本处理时,首先要想到通过正则表达式怎么解决,如果写不出相应的正则表达式,可以查询 google 或 bing 寻求帮助。

资源分享:

(完)

上一篇 下一篇

猜你喜欢

热点阅读