匹配标识符
C语言标识符:合法的标识符是由字母、数字和下划线的组成的序列,而且必须由英文字母或下划线开头,不能以数字开头。
identifier.txt:
## 标识符:只包含数字,字母,下划线。但是不能以字母开头
xiaoming1 //是标识符
2xiaoming //标识符不能以数字开头
_xiaoming3 //是标识符
#xiaoming //不是标识符:c语言标识符只能字母数字下划线组成
xiaoming@ //不是标识符,同上
匹配上面的标识符:
命令: egrep "\<[a-zA-Z_][a-zA-Z0-9_]*\>" identifier.txt
,效果如下。
可以看到上面的标识符已经选出来,但是还是有一些不正确地方,就是#xiaoming
,xiaoming@
这两行都选出来了,但是还是选出来了,这是因为单词分界符只认识字母和数字的起始位置有关。也就说#和@这两个符号单词分界符\<
和\>
不认为它是单词的一部分。所以上面的#和@并没有标红。
这里我还没有想到有什么很通用的方法去掉这些字符。
下面仅仅对上面的文本例子做匹配,可以加入行开头元字符^
,让该行必须以字母下划线开头,这样就可以过滤掉#xiaoming
这一行。效果如下:
可以看到已经滤掉#xiaoming
这一行了,但是xiaoming@
这一行还没有过滤掉,我们可以在使用排除型字符组,使得单词分界结束符后面不能出现@
这个字符,命令: egrep "^\<[a-zA-Z_][a-zA-Z0-9_]*\>[^#@].*$" identifier.txt
效果如下。
但是这种修修补补的做法,不具备通用型,如果此时如果又来一个新的符号&
那上面的正则表达式就不好用了。
引号内的字符串
匹配引号内字字符串最简便的办法就是使用这个表达式"[^"]*"
,不过在使用的时候要注意使用转义符进行转义,不然egrep运行出错,命令:egrep "\"[^\"]*\"" str.txt
.
实例:
str.txt:
private String str="小明";
命令:egrep "\"[^\"]*\"" str.txt
,匹配结果如下。
表达式"[^"]*"
中两端的引号用来匹配字符串的开头和结尾。在这两个引号之间的文本可以包括双引号之外的任何字符,所以用[^"]
表示双引号之间的任何字符,用*
来表示引号之间可以存在任意数目的非双引号字符。
不过关于引号字符串,双引号之间可以出现有转义之后的引号,也是就是\"
这种形式的引号。例如String str2="{\"小明\",\"小李\",\"小王\"}";
上面的正则表达式就不适用了,可以改成"([^"]|(\")?)*"
这样的形式即可。
str2.java:
package lan.base;
public class Test
{
public static void main(String[] args)
{
String str1="小明";
String str2="{\"小明\",\"小李\",\"小王\"}";
System.out.println(str2);
}
}
命令:egrep "\"([^\"]|(\\\")?)*\"" str2.java
,匹配效果如下。
可以看到我们实现了在字符串的匹配。即使引号里面有转义引号的情况。对不之前的正则表达式式:"[^"]*"
,我们可以看到区别:
应用3:匹配美元金额(可能是小数)
\$[0-9]+(\.[0-9][0-9])?
是一种匹配美元金额的办法.
从整体上看,这个表达式很简单,分为三部分\$
,[0-9]+
和(\.[0-9][0-9])?
,大致可以理解为,一个美元符号,然后是一组数字,之后可能一组字符(由小数点和两组数字组成)从几个方便来看,这表达式还是很简陋的。比如它只能接受 1000,而无法接受 1000 , 而 无 法 接 受 1,000。它确实能接受可能出现的小数部分
dollar.txt:
$123.12
$11.00
$11
$1000
$1,000
命令:egrep "\$[0-9]+(\.[0-9][0-9])?" dollar.txt
,这个没命令没有效果,可见,转移字符$好像没有用了。结果如下:
看到上面的运行结果是不对的,不过既然前面说到字符组可以让其中的元字符失效。所以这里我改成[$][0-9]+(\.[0-9][0-9])?
,因为字符组使用要匹配一个字符,而[$]中只有一个美元符,所以美元符一定要匹配到。
命令:egrep "[$][0-9]+(\.[0-9][0-9])?" dollar.txt
。
还可以在上面的正则表示式中加入行结束符
$
来匹配只有美元的行
命令:egrep "[$][0-9]+(\.[0-9][0-9])?$" dollar.txt
,匹配效果如下
解决$1,000无法匹配的问题,可以使用正则表达式:[$][0-9]{1,3}(,[0-9]{3})*$
表示。
dollar.txt:
$123.12
$11.00
$11
$1000000
$1,000
$11,000
$100,000.34
$1,000,000.23
命令:egrep "[$][0-9]{1,3}(,[0-9]{3})*$" dollar.txt
,效果如下。
然后加入小数点支持:[$][0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
,命令:egrep "[$][0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$" dollar.txt
从运行结果上来看,还差$1000000
这一行没有匹配到。使用多选结构和上面匹配没有逗号分隔的正则合并一行就行了。合并后的正则表达式为:
[$]([0-9]{1,3}(,[0-9]{3})*|[0-9]*)(.[0-9]{1,2})?$
,命令:egrep "[$]([0-9]{1,3}(,[0-9]{3})*|[0-9]*)(.[0-9]{1,2})?$" dollar.txt
可以惊喜的发现,上面的正则表达式还支持$.23
这种形式的美元金额类型。
在上面的dollar.txt中末尾加入一行$.23
,匹配效果如下。
可以看到,所有类型的美元符都能匹配到了。
这里再来解释一下[$]([0-9]{1,3}(,[0-9]{3})*|[0-9]*)(.[0-9]{1,2})?$
这个正则表达式,
[$]
表示美元符号([0-9]{1,3}(,[0-9]{3})*|[0-9]*)
这是有两个子表达式构成的多选一结果
- 多选结构的子表达式
[0-9]{1,3}(,[0-9]{3})*
支持$11,000,000
形式的金额表示法,这个子表达式又分为两部分。
- 第一部分
[0-9]{1,3}
中,{1,3}前面的字符组[0-9]
,重复1到3次。也就是表示一个1位数,或者2位数,或者3位数的数字。 - 第二部分
(,[0-9]{3})*
表示可以有一个逗号,然后是一个3位数的数字,{3}
表示前面的字符组[0-9]
重复3次,所以第二部分(,[0-9]{3})*
表示可以有多次逗号,然后是3位数,或者没有。
- 第一部分
- 多选结果的第二个子表达式:
[0-9]*
支持$100000000
这种没有逗号的美元金额表示方法。
- 多选结构的子表达式
(.[0-9]{1,2})?
支持小数操作,可以有也可以没有小数,小数的位数可以是1到2位。- 最后的
$
,表示美元金额之后就是行结尾。
匹配HTTP/HTML URL
Web URL
的形式可能有多种,所以构造一个能够匹配所有形式的URL
的正则表达式颇有难度。不过稍微降低一点要求的话,我们可以用一个相当简单的正则表达式来匹配大多数常见的URL
。
常见的HTTP/HTML URL
是下面这样的
http://hostname/path.html
当然以.htm
结尾的也很常见。
hostname
(主机名,例如www.yahoo.com
)的规范比较复杂,但是我们知道,
跟在http://
后面的就可能是主机名,所以这正则表达式就很简单,[-a-z0-9_.]+
,path
部分变化更多,所以我们需要使用[-a-z0-9_:@&?=+,.!/~*$]*
注意,连字符必须放在字符组的开头,保证它是一个普通的字符,而不是用来表示范围的
综合起来,我们第一次尝试的正则表达式是:
egrep -i "\<http://[-a-z0-9_.]+/[-a-z0-9_:@&?=+,.!/~*$]*.html?\>" filename
匹配时间
匹配12小时制时间
匹配表示时刻的文字可能有不同的严格程度
[0-9]?[0-9]:[0-9][0-9]\s*(am|pm)
能够匹配9:17 am
或者12:30 pm
但也能匹配无意义的时刻,如99:99pm
。
匹配12小时制的小时数
首先来看小时,我们知道如果小时是两位数的话,十位数只能是1
,但是1?[0-9]
显然能匹配19
也能匹配0
这样无意义的小时。所以更好的办法应该是把小时分为两种情况来处理,两位数(大于等于10)的时候是一种情况,个位数的时候是一种情况(10点以下)。所以用1[0-2]
来匹配10
点,11
点12
点的情况。用[1-9]
来表示1
,2
,3
,4
,5
,6
,7
,8
,9
点这种情况。结合起来,小时就是(1[0-2]|[1-9])
,
匹配分钟
分钟就比价简单十位数应该是[0-5]
,个位数应该是[0-9]
所以12小时制综合起来就是(1[0-2]|[1-9]):[0-5][0-9]\s*(am|pm)
实例:
time.txt:
1:30 am
10:20 am
1:40 pm
9:12 am
12:30 pm
18:89 pm #错误的表示方法
99:99 pm #错误的时间表示法
01:20 am #都是两位的时间表示法
00:30
23:59
24:00 #错误的时间
88:88 #错误的时间
使用正则表达式:(1[0-2]|[1-9]):[0-5][0-9]\s*(am|pm)
进行匹配,命令:
egrep "\<(1[0-2]|[0-9]):[0-5][0-9]\s*(am|pm)\>" time.txt
,匹配结果如下。
可以看到对于这个正则表达式可以成功匹配1:30 am
这类的事件表示方法。但是无法匹配01:30 am
这种表示方法。解决办法是把1-9点的正则表示式[1-9]
改成0?[1-9]
即可,新的正则表达式为:(1[0-2]|0?[1-9]):[0-5][0-9]\s*(am|pm)
,命令:egrep "\<(1[0-2]|0?[1-9]):[0-5][0-9]\s*(am|pm)\>" time.txt
,匹配结果如下。
可以看到像01:20 am
这种都是两位数表示的事件也能匹配到了。
匹配24小时制的时间
还是先按照上面分而治之的方法,
首先来匹配小时
小时数小于10,也就是0-9的时候,可以用[0-9]
来表示,但是不确定是不是只用一位数如8
,可以能用两位数表示,例如08
,所以小于10的小时数用0?[0-9]
来表示。而大于10,小于20的时间用1[0-9]
来表示,例如10
,19
。大于20的小时,用2[0-3]
来表示,所以,24小时制的小时用(2[0-3]|1[0-9]|0?[0-9])
表示小时,
再来看分钟,分钟还是和上面一样简单,用[0-5][0-9]
即可表示。
所以24小时制的事件表示:\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>
,匹配命令:egrep "\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>" time.txt
,匹配效果如下。
可以看到像1:30 am
这样的行居然也会匹配到,这是因为单词分界符在1:30结尾处就结束了。所以1:30看起来也像24小时制的,所以也会匹配出来。解决方法是一直匹配到行结尾,行结尾之前不允许出现am
或者pm
这样才是24小时制的。
新的正则表示式为\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>[^apm]*$
,命令:egrep "\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>[^apm]*$" time.txt
这样就匹配出来的都是24小时制的时间了。
同时匹配12小时制和24小时制的时间
我们把上面匹配12小时制的正则表达式:\<(1[0-2]|0?[1-9]):[0-5][0-9]\s*(am|pm)\>
和匹配24小时制的正则表达式\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>[^apm]*$
使用多选结构合并一下,这样就能同时支持两个时间了。如下所示。
(\<(1[0-2]|0?[1-9]):[0-5][0-9]\s*(am|pm)\>|\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>[^apm]*$)
命令:egrep "(\<(1[0-2]|0?[1-9]):[0-5][0-9]\s*(am|pm)\>|\<(2[0-3]|1[0-9]|0?[0-9]):[0-5][0-9]\>[^apm]*$)" time.txt
,匹配效果如下。
可以看到12小时制的时间和24小时制的时间都很好的匹配到了。不过这个表达式还是有问题的,后面不能出现任何的字母a,字母p和字母m,显然这就不是很正确。