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

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

作者: somenzz | 来源:发表于2018-11-09 23:34 被阅读52次

    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] 匹配一个数字
    ^ 匹配字符串的开始位置
    $ 匹配字符串的结束位置

    比如

    • .* 代表匹配任意一行
    • \d\d 匹配连续的两个数字
    • ^[0-9] 匹配字符串开始位置是数字的字符串
    • \s$ 匹配字符串结尾是空格的字符串
    • ^$ 匹配不含空格的空行
    • ^\s*$ 匹配含空格空行

    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 种断言方式:

    • 先行断言 (?=exp)//表示匹配表达式 exp 前面的位置

    • 后发断言 (?<=exp) //表示匹配表达式 exp 后面的位置

    • 负向零宽断言 (?!exp) // 匹配一个不含 exp 前面的位置,这个有点不太好理解,举个例子吧:有以下字符串:
      baidu.com
      sina.com.cn
      那么正则:^(?!baidu).*$ 匹配结果就是第 2 行,也就是第 1 行被排除了,意思就是查找不以 baidu 开头的字符串。

    • 负向零宽后发断言为 (?<!exp) // 匹配一个不含 exp 后面的位置,举个例子,有以下字符串
      www.sina.com.cn
      www.educ.org
      www.hao.cc
      www.baidu.com
      www.123.com
      那么正则 ^.*(?<!(com))$ 表示匹配不以 com 结尾的字符串,也就是前三个,使用 grep 时注意 表达式要加小括号,执行结果如下所示:

    [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 寻求帮助。

    资源分享:

    (完)

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/gxpbxqtx.html