Linux之旅 9:正则表达式与文件格式化
(图片来自常用正则表达式)
什么是正则表达式
正则表达式(简称为正则)可以看做是一种微型标记语言,通过定义一系列符号来灵活地设定一种匹配模式,对目标字符串进行匹配,匹配出你想要获取的部分,然后进行下一步处理。
其目的相当明确,就是字符串匹配,当然,往往使用正则的程序也会在匹配的基础上提供替换或者删除的功能,但那些都可以看做是通过正则匹配出结果之后的动作。
我在前边说了,通配符通常可以看做一个精简版的正则,因为他们的目的相同,都是设定一个匹配模式进行匹配,不过前者要简单的多,而后者有完整而复杂的语义,老实说,初学者要把正则搞明白并不是件容易的事。
正则有什么用
但是正则的确相当有用,如果使用正则你会省不少事,比如说网页编程最常见的,需要判断某个输入框的合法性,是不是合法的电话,是不是合法的身份证,等等,如果从头开始编写一个字符串判断和处理的函数简直不可想象,而如果你懂正则,只需要按一定规则编写一段正则表达式即可,或者更多的是去网上copy一段。
再比如我经常用的一个小工具EasyPub,可以将txt电子书转换为kindle的专有格式mobi,转换的时候可以自动识别章节进行切割,但是有时候有些txt章节并非常见的第一章 xxx
,而是### 1 xxx ###
,默认的章节分割就不好使了,但庆幸的是该工具支持正则表达式切割章节,只要你会正则,只要txt中的章节标题都有迹可循,任何内容都不在话下。
如何学习
本篇文章的切入点是从Bash中支持正则表达式的命令入手,介绍一些简单的正则表达式在Bash命令中的实际使用,如果想系统地学习正则,各大厂商都有自己的正则教程或者参考手册,可以挑一个自己喜欢的:
- 微软:正则表达式语言 - 快速参考
- Mozilia:正则表达式
- 揭开正则表达式的神秘面纱
如果想检查自己写的正则哪里出错了,可以使用这个工具debuggex。
基础正则表达式
正则表达式的语法分为基础部分和扩展部分(可以看做是高级语法),虽然说我使用过的大部分主流编程语言,如javascript
、Java
、PHP
、Python
中都支持完整的正则语法,但是Linux中某些应用,比如grep
仅支持基础语法,扩展语法需要使用额外参数grep -E
才能支持。所以这里关于正则表达式的语法介绍也分为基础和扩展两部分。
编码对正则表达式的影响
正则的某些语法是和具体编码直接相关的,比如[a-z]
,指的是编码表上从a
字符到z
字符的所有字符中的某一个。在标准的ASCII编码中,其范围自然是全部的小写字母,因为ASCII中小写字母是连续的(对应编号为97~122),相应的,对于兼容ASCII的字符集也会是相同的结果,比如各种UTF-8编码。但如果你使用的是某个不兼容ASCII的字符集,比如big5
这种繁体字字符集,其编码的顺序是:
0 1 2 3 4 ... a A b B c C d D ... z Z
如果在这种编码下使用正则,[a-z]
的范围就会变成a A b B c C d D ... z
,显然是不正确的。
关于更多ASCII字符集的内容,可以阅读ASCII。
幸运的是GB2312或者big5这些不兼容ASCII的老式编码现在已经越来越少用了,从操作系统到应用都越来越多地使用UTF-8这种新一代统一规范且兼容ASCII的编码,所以我们大部分时间都无需对此担心。
除了使用[a-z]
这种方式指定小写字母,还可以使用一些正则预定义的符号集:
特殊字符 | 定义 |
---|---|
[:lower:] |
小写字母(Lower-case letters),即a-z |
[:upper:] |
大写字母(Upper-case letters),即A-Z |
[:alpha:] |
所有字母,即a-zA-Z(可能有人知道Google的母公司叫alphabet ,即字母表,那么alpha 的意思就是字母) |
[:digit:] |
数字,即0-9 |
[:alnum:] |
字母和数字(即alpha+number的意思) |
[:blank:] |
空白符(blank characters),包含空格与制表符 |
[:cntrl:] |
键盘上的控制按键,包括CR, LF, Tab, Del 等 |
[:punct:] |
标点符号(Punctuation characters),比如\# $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ' { |} ~ 等 |
[:graph:] |
图形字符(Graphical characters),包括[:alnum:] 和[:punct:] ,也就是字母数字和标点符号(可以理解为有图形的字符(相对于空白符等),对于UTF字符集来说可能会包含一些奇奇怪怪的字符,比如笑脸或者象棋棋子) |
[:print:] |
可打印字符(Printable characters),包括[:alnum:] 、[:punct:] 和空格 |
[:space:] |
空格符(Space characters),包括制表符、换行符、垂直制表符、换页、回车、空格 |
[:xdigit:] |
16进制数字(Hexadecimal digits),包括0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f |
需要说明的是,这些特殊字符之所以能使用,是因为字符集本身会自带一个数据库,在数据库中标记哪些字符属于数字,哪些字符属于字母,哪些又是可打印的,诸如此类,所以实际使用效果根据字符集的不同可能会有不同的结果。
因为上边的原因,使用特殊字符相对于0-9
或a-z
这样的写法有个额外的好处,即就算当前字符集中小写字母不是连续编码,中间插入了大写字母,也不会影响使用特殊字符(比如:lower:
)的匹配结果。
grep的一些进阶选项
之前我们介绍过grep
的一些用法,可以正选或者反选出匹配的行,此外,grep
还可以打印匹配结果的行号:
[icexmoon@xyz ~]$ dmesg | grep -n ens33
1821:[ 19.585777] IPv6: ADDRCONF(NETDEV_UP): ens33: link is not ready
1822:[ 19.620521] IPv6: ADDRCONF(NETDEV_UP): ens33: link is not ready
1828:[ 21.601791] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
1829:[ 21.608352] IPv6: ADDRCONF(NETDEV_CHANGE): ens33: link becomes ready
1830:[ 21.708661] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
甚至可以额外打印出匹配结果的上下文:
[icexmoon@xyz ~]$ dmesg | grep -n ens33 -A 2 -B2
1819-[ 16.342351] Bluetooth: BNEP socket layer initialized
1820-[ 19.334452] ip6_tables: (C) 2000-2006 Netfilter Core Team
1821:[ 19.585777] IPv6: ADDRCONF(NETDEV_UP): ens33: link is not ready
1822:[ 19.620521] IPv6: ADDRCONF(NETDEV_UP): ens33: link is not ready
1823-[ 19.626516] Ebtables v2.0 registered
1824-[ 19.820016] Netfilter messages via NETLINK v0.30.
--
1826-[ 20.609962] nf_conntrack version 0.5.0 (7778 buckets, 31112 max)
1827-[ 21.259096] bridge: filtering via arp/ip/ip6tables is no longer available by default. Update your scripts to load br_netfilter if you need this.
1828:[ 21.601791] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
1829:[ 21.608352] IPv6: ADDRCONF(NETDEV_CHANGE): ens33: link becomes ready
1830:[ 21.708661] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
1831-[ 26.904983] tun: Universal TUN/TAP device driver, 1.6
1832-[ 26.904986] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
本来匹配到的前两行是1821
和1822
行,但通过使用-A
(After)和-B
(Before)参数,在结果中出现了1829
、1820
以及1823
、1824
行,这样有助于我们在某些情况下结合匹配结果的上下文分析问题。
基础正则表达式练习
这里使用《鸟哥的私房菜》提供的文本进行练习:
[icexmoon@xyz ~]$ cd /tmp
[icexmoon@xyz tmp]$ wget http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt
--2021-08-16 15:38:50-- http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt
正在解析主机 linux.vbird.org (linux.vbird.org)... 140.116.44.180
正在连接 linux.vbird.org (linux.vbird.org)|140.116.44.180|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:650 [text/plain]
正在保存至: “regular_express.txt”
100%[===========================================================================================>] 650 --.-K/s 用时 0s
2021-08-16 15:38:56 (21.5 MB/s) - 已保存 “regular_express.txt” [650/650])
简单查找
[icexmoon@xyz tmp]$ grep -n the regular_express.txt
8:I can't finish the test.
12:the symbol '*' is represented as start.
15:You are the best is mean you are the no. 1.
16:The world <Happy> is the same with "glad".
18:google is the best tools for search keyword.
反选
[icexmoon@xyz tmp]$ grep -nv the regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
4:this dress doesn