常用
- [0-9a-zA-Z]
- . 可以匹配任意单个字符、字母、数字甚至是 . 本身
- 取反:^
- 元字符:. [ ] \ ? * + 等。元字符大致可以分为两种:一种用来匹配文本的(比如 . ),另一种是正则表达式语法的组成部分(比如 [ 和 ] )。
- 匹配空行:^$。 ^表示行开始,$表示行结尾。比如下面命令扣除空行内容
grep '^$' test1.txt -v
- \d:等价[0-9]
- \D:等价[^0-9]
- \w:等价[a-zA-Z0-9_]
- \W:等价[^a-zA-Z0-9_]
- \s:匹配任何一个空白字符
- \S:匹配非空白字符
- | : 当前字符只要是
|左右两边的字符之一。就算匹配成功 - +:匹配一个或多个字符。比如使用下面的正则表达式可以匹配:test@test.com这样的邮箱格式字符串
\w+@\w\.\w+
- 不过,上述表达式无法匹配形如:test.test@test.com这样格式的邮箱字符串,可以对字符集合使用+
[\w.]+@[\w.]+\.\w+
- 你可能注意到了,我们没有对 [\w.] 里的字符 . 进行转义,一般来说,当在字符集合里使用的时候,像 + 和 . 这样的元字符将被解释成普通字符,不过转义了也没有坏处
- *:匹配零个或多个字符
- 上面的匹配邮箱格式的正则可能会匹配到如下字符串:.test.test@test.com,显然,第一个 . 不是我们想要的,该如何避免呢?
\w[\w.]*@[\w.]+\.\w+
- ?:匹配零个或一个字符,看似没什么用,但换种思路,最多出现一次,常用来匹配http链接,其能兼容http和https两种形式的链接。比如 www.test.com 和 www.test.com ,下面是正则表达式:
https?:\/\/[\w.]+
注意: / 并不是元字符,但它经常用来包裹正则表达式,称作界定符,因此也需要转义
- 重复匹配和重复的区间范围:使用 { } 放在要重复的字符或字符集前面,比如:\d{2}代表数字要重复两次,\d{2,4}代表数字要重复2~4次
- 至少重复多少次:指定至少要匹配多少次,比如{3,},即重复的最大值不写,那么就代表至少重复3次。逗号别忘了,否则就变成了:只匹配3次。最后,上面的元字符 + 等价于{1,}
防止过度匹配(贪婪与懒惰)
目前我们所有的正则表达式元字符都是贪婪型的,即其匹配行为是多多益善而不是适可而止。他们会尽可能地从一段文本的开头一直匹配到末尾,而不是碰到第一个匹配时就停止,这是有意设计的。
在不需要这种“贪婪行为”的时候该怎么办?答案是使用这些元字符的“懒惰型”版本。之所以称之为“懒惰型”是因为其匹配尽可能少的字符,而非尽可能多地去匹配。懒惰型的写法是在贪婪型元字符后面加上一个?比如下面:
贪婪型元字符 懒惰型元字符
* *?
+ +?
{n,} {n,}+
grep想要使用非贪婪的正则表达式,需要跟上-E参数,代表使用Perl的正则,比如你想要匹配出下面字符串的两个标签:
<b>11</b> aaaaa <b>22</b>
便可以使用如下命令:
echo '<b>11</b> aaaaa <b>22</b>' | grep -P -o '<[bB]>.*?<\/[bB]>'
结果如下:
<b>11</b>
<b>22</b>
你可以试试如何不使用懒惰型元字符匹配的话,结果是什么
位置匹配
这节我们要做什么事呢?如果我们想匹配下面句子中的cat单词,而不想把scattered中的cat匹配出来,如何实现呢?
the cat scattered his food all over the room
翻译:猫把食物撒得满屋子都是
按照现有的知识储备我们是做不到的。
单词边界
\b 用来匹配一个单词的开头或结尾。什么是"\b":首先,b是boundary(边界)的缩写,那么 \b 到底匹配什么东西呢?简单地说, \b 匹配的是字符之间的一个位置:一边是单词(能够被 \w 匹配的字母数字字符和下划线),另一边是其他内容(能够被 \W 匹配的字符)。所以你知道了,\b不代表一个字符,而只是两类字符之间的位置。
我们可以使用下面的正则来实现上面的需求:
\bcat\b
单词cat的前后都有空格,所以匹配模式\bcat\b(空格是用来分割单词的字符之一)。该模式并不匹配单词scattered中的字符序列cat,因为它的前一个字符是s、后一个字符是t,与cat构不成边界。
重要的是认识到,如果你想匹配一个完整的单词,就必须在要匹配的文本的前后都加上\b。比如,你想匹配任何以字符序列cap开头的单词,就可以使用正则表达式\bcap实现。
如果你不想匹配边界(即\w和\W之间的位置),则可以使用\B实现,比如下面的例子中:
please enter the nine-digit id as it appears on your color - coded pass-key
翻译:请输入显示在您采用颜色编码的密码上的九位数字id(好奇怪的句子)
使用如下的正则表达式:
\B-\B
就可以只匹配到下面红色的部分:
please enter the nine-digit id as it appears on your color - coded pass-key
字符串边界
单词边界可以用来对单词位置进行匹配(单词的开头、单词的结尾、整个单词等)。字符串边界有着类似的用途,只不过用于在字符串收尾进行模式匹配。字符串边界元字符有两个:^代表字符串开头,$代表字符串结尾。
注意1: 你应该记得^代表的是排除的意思。这怎么又变成字符串的开头了呢?有些元字符拥有多种用途,^就是其中之一。只有当它出现在字符集合里(位于[和]之间)且紧跟在左方括号的后面时,它才表示排除该字符集合。如果出现在字符集合之外并位于模式的开头,^将匹配字符串的起始位置。
注意2: 如果你使用过grep或是sed等命令,你会发现^和$代表的是每一行的收尾边界,这是因为这些命令都会将文件按行切分,我们使用的grep等都是针对每一行使用的正则匹配。
字符串边界的例子就不举了。捎带提一句,^$代表的其实就是空串,在grep中,经常用来匹配或过滤空行。
使用子表达式
本节学习如何运用子表达式对表达式进行分组。如果你不能理解这句话的意思(即什么是分组),举个例子:如果你熟悉阿里云的SLS或者是其他的分布式日志系统,那么你大概会知道正则表达式的重要之处,首先大体流程是告诉日志系统,要采集的文件是什么,一般为log.info,或log.error等,然后你要提供一份正则表达式,它会对你输出的每一条日志进行切分,比如你的日志是:field1:"xxx",field2:"yyy",field3:"zzz",那么正则表达式将会将这个字符串进行分组匹配成为:group1:field1:"xxx" ,group2:field2:"yyy" ,grou3:field3:"zzz",最后你需要告诉日志系统,每个字段的数据类型(在es中的类型),然后作为es索引的某个字段进行存储。这就是子表达式的强大之处。在jdk中,匹配获得到的Matcher对象中也有获取group的操作。
不过说明什么是子表达式重要之处不必那么麻烦,举个例子即可:首先介绍html的实体引用: (nobreaking space 不换行空格,常用于html排版,使得一段文字即使有空格也不换行),代表空格,目前我想匹配html中的连续两次或多次重复出现的 ,如何实现呢?米可能会想:
{2,}
这是错误的,因为重复匹配只会针对于紧挨着它左边的单个字符或字符集([],也是单个字符)。如何实现就用到子表达式。
子表达式是更长的表达式的一部分。划分子表达式的目的是为了将其视为单一的实体来使用,子表达式必须出现在(和)之间。因此,上面的问题应该用下面的正则来解决:
( ){2,}
再举一个例子,匹配ipv4地址,你知道它是四段数字(最多三位)组成,根据已有的知识,可以写出下面的正则表达式进行匹配:
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
如果使用我们本节的子表达式该如何书写呢:
(\d{1,3}\.){3}\d{1,3}
再比如说一些元字符比如|,比如你想匹配年份,年份一般是4位数字,开头一般是19或20,所以你这样写你的正则表达式:19|20\d{2}。实际的效果其实是匹配19和20\d{2},这并不满足你的需求,你应该将19和 20括起来:(19|20)\d{2}
子表达式的嵌套
所谓子表达式嵌套就是子表达式里面还有个子表达式。还是拿ip地址举例子,你会发现上面的解决方案还是很不严谨,因为ip地址的每一位都不会超过255,如何解决呢?可以对ip地址进行归类,满足下面四条之一的任何一条都都属于正常的ip地址的某一段:
- 任意的1位或2位数字
- 任意的以1开头的3位数字
- 任意的以2开头、第二位数字在0到4之间的3位数字
- 任意的以25开头、第三位数字在0到5之间的3位数字
优化后的正则表达式如下:
(25[0-5]|2[0-4]\d|1\d{2}|\d{1.2}\.){3}25[0-5]|2[0-4]\d|1\d{2}|\d{1.2}
你会发现我的规则是倒着写的,如果正着写会发生什么呢?会发生短路,可能最后一位是200(比如ip地址为:12.159.46.200),但是却匹配到了20。
反向引用
子表达式的另一个重要用途:反向引用。