来一波,隐藏至深的正则表达式


最近写了好几个关于正则的例子,第一个是过滤WordPress的数据库文章中的图片链接,另一个是写一个app时候要判断一个表达式是否符合逻辑,是时候把这些积累的东西给堆出来了,毕竟,谁都记不了那么清楚啊!!!

第一篇博文内容:特例解释(请关注其用法,而不是示例用的什么语言,Perl,java?随他呢)

(1) 点号(.),它用来匹配任意一个单字符(\n 排除在外,后面我默认不再提这个例外)。

所以: "twoon" =~ /tw.on/ 为 true
 “twvon" =~ /tw.on/ 也为 true.

        点号在正则表达式中是有特殊含义的,有时我们可能也要匹配点号,这时就需要转义一下。

"twoo.n" =~ /twoo\.n/ 为true.

        正则表达中,所有其它的通配符也都可以用同样的方式进行转义,表示直接匹配通配符,去除它的特殊含义。

        你可以看到\也是一个通配符,如果要匹配它也是同样的道理。

"two\\on" =~ /two\\on/ 为true.

(2) 星号(*) : 星号代表匹配它前面一个字符任意遍(0或任意次),它是一数量词(quantifier),必须跟在其它字符的后面,否则这个表达式不正确。如:

”twoon" =~ /two*n/ 为true
“twn" =~ /two*n/ 也为true.
"twoon" =~ /*twoon/ 表达式不正确,*必须跟在其它符号后面.

       同时星号也可以匹配点号(.),点号代表任意非回车字符,因此, (.*)就代表任意字符任意次。这是一个惯常的用法,如:

/twoon.*walks/ 能匹配任意包含"twoon"在前,"walks“在后的字符串。

 所以(.*)也被称为:any old junk. 匹配任何东西。 

(3) 加号(+): 加号是一个与星号(*)类似的通配符,它也是数量词,表示匹配前面的字符一次或多次(至少一次).

       它与星号的差别就在这里,星号可以匹配0次,加号则必须一次以上。  如:

"twoon" =~ /two+n/ 为true.
"twn" =~ /two+n/ 为false.

(4) 问号(?): 问号也是一个数量词,它代表匹配前一个字符0或1次。如:

"twoon" =~ /twoo?n/ 为true
"twoon” =~ /two?n/ 为false.
"twn" =~ /two?n/ 为true.

(5) 括号(()): 括号用来表示一个组合,前面我说数量词作用在前一个字符上,这个说法事实上不准确,应该说是作用在一个组合上,一个字符是一个组合,但多个字符也可以成为组合。括号就是用来表示一个组合,被括号括起来的就是一个组合。如:

"twoon" =~ /tw(o)*n/ 为true.
"twoon" =~ /tw(oo)*n/ 为true
"twowon" =~ /t(wo)*n/ 为true.
"twoon" =~ /t(wv)*oon/ 为false.
“twoon" =~ /t(wo)+on/ 为true.
“twon" =~ /t(wo)+on/ 为false.

     括号里可以放置任何字符,也可以放置其它通配符,如  "aaabcc" =~ /(aa+b)?cc/

(6) 引用通配符:反斜杠加上数字是所谓引用通配符(back reference): \1 \2 \3 等,它的作用是引用前面的某个括号元组,如:  

"twoonwo" =~ /t(wo)on\1/ 为true

    乍看起来,似乎作用不明显,如上例,我们完全可以不用\1,而写成这样: /t(wo)on(wo)/

    在上面的例子里,这个质疑是可以理解的。但有时,我们的括号元组可能这样写的:  (we...) 因为点号代表任意字符,如果我们后面要作用这个元组,不用引用通配符, 我们根本无法引用,具体看例子:    

”weabceeweabc" =~ /(we...)ee\1/ 为 true。
“weabceeweabc" =~ /(we...)ee(we...)/ 为 true
”weabceewecdf" =~ /(we...)ee(we...)/ 也为 true.

    从第2,3个例子,我们可以看区别。\1 表示的是与前一个元组完全一样的匹配。而 \1,\2,\3等,则分别表示,从左往右数第几个元组。

“abcdef def abc" =~ /(...)(...) \2\1/ 为 true.

    在 perl 中,引用通配符中支持从1~9,写法上很活,你既可直接\1 \2 ...\9这样来写,也可以写成 \g{1]  ,\g{2},.....\g{9}。后面一种写法相对复杂些,但有助于perl来理解你想表达的含义。因为反斜杠在程序语言中有特殊的信念,通常表达转义,perl 在遇到反斜杠时,它会去猜你想表达的什么。所以如果你写一个类似: \123这样的东西,它就不知怎么去解析,你是想表达 \1+23,引用后面跟着数字,还是,\12+3,或 \123,转义符后面跟数字是可以表示转义一个8进制数字的。因此这里产生了歧义。perl 5.10 于是引入了 \g{N}这种表述方式来表示引用通配符。N 甚至可以是负数,当是用负数是,它表示一个相对位置。表示从当前位置开始往左数,第N个元组,如:

"twooavvboonn" =~ /tw(oo)a(vv)b\{-2}(nn)/ 为true

(7) 中括号[]: 中括号用来表示一个字符集合(character set)

     字符集合,顾名思义,就是字符的集合,集合的元素放在中括号里,表示每次匹配中其中的一个,如: 

"twoon” =~ /[tw]woo/ 为 true

     有时如果这个集合有很多元素,如26个字母,数字等,一个个地写在中括号里,未免太麻烦太蠢笨,这时可以用连字符(hyphen)来表示一个范围,

如:[a-z]表示小写字母的集合,[a-zA-Z]表示大小写字母的集合。

     上面的用法用于提供范围来选择,但有时不匹配某个范围也是很常见的匹配需求,这时我们可以在集合的开头放一个脱字符 ^ (caret). 这种写法表示,匹配任何不在该集合中的字符,与上面的用法刚好相反。如:

"twoon" =~ /[^two]woon/ 为false
“ewoon" =~ /[^two]woon/ 为true

      由上面的用法,可知 ^,- 这两种符号在集合中有特殊含义,如果我们们想在集合中表示这两个字符,就也要转义一下。如:

[\^ab\-]

      有些字符集合是很常用的,如字母,数字等,perl提供了一些缩写来表示这些常用的集合,如:\d表示一个数字,等价于[0-9],这些特殊字符包括如下 :

\w -- (小写w) 表示字母或数字,等价于 [a-zA-Z0-9]
\W -- (大写W)非字母且非数字,与\w相反
\s -- (小写s)匹配一个空格字符,包括:空格,换行,回车,tab,等价于[ \n\r\t\f]
\S -- (大写S)匹配非空格字符,\s的相反
\d -- 表示10进制数字,等价于 [0-9]

(8) 大括号:{}  

   大括号的作用是指定重复前面一个字符多少遍:

{N} 重复N遍
{n,m} 重复 n~m 遍
{n,} 至少重复n遍
{,m} 至多重复m遍
示例:"twoon" =~ /two{2}n/ 为true.

(9) ^,& 这两个通配符用来表示在匹配串的头部或尾部匹配。

     一般我们写这种: "twoon" =~ /oo/ 正则表达式的时候,匹配是从"twoon"的开始一路匹配下去,如tw != oo,就继续往下匹配,但有时候我们可能只想匹配一下开头或结尾,这时^,&就派上用场了。^用于匹配字符串的开关,&用于匹配字符串的结尾。如: 

"twoon" =~ "^tw" 为true.
“twoon” =~ “oo" 为true
"twoon" =~ "^oo" 就为false.
”twoon" = "on&" 为true.
"twoon“ = ”oo&" 为false

(10) “或" 通配符:  正则表达式用竖线 | 表示或, (ab | cd) 表示匹配竖线左右的字符组之一,如果左右的字符数超过一个,它必须和括号一起使用。如:  

 "twoon” =~ /t|ewoon/ 结果为true
"twoon" =~ /(tw|ee)oon/ 结果为true
"twoon" =~ /(ee|gs)oon/ 结果为false

第二篇博文:入门的符号说明(并未全部验证,复制粘贴可能有错,自行尝试,仅供参考)

// 反斜杠
/t 间隔 ('/u0009')
/n 换行 ('/u000A')
/r 回车 ('/u000D')
/d 数字 等价于[0-9]
/D 非数字 等价于[^0-9]
/s 空白符号 [/t/n/x0B/f/r]
/S 非空白符号 [^/t/n/x0B/f/r]
/w 单独字符 [a-zA-Z_0-9]
/W 非单独字符 [^a-zA-Z_0-9]
/f 换页符
/e Escape
/b 一个单词的边界
/B 一个非单词的边界
/G 前一个匹配的结束

^为限制开头
^java 条件限制为以Java为开头字符

$为限制结尾
java$ 条件限制为以java为结尾字符
. 条件限制除/n以外任意一个单独字符
java.. 条件限制为java后除换行外任意两个字符

加入特定限制条件「[]」
[a-z] 条件限制在小写a to z范围中一个字符
[A-Z] 条件限制在大写A to Z范围中一个字符
[a-zA-Z] 条件限制在小写a to z或大写A to Z范围中一个字符
[0-9] 条件限制在小写0 to 9范围中一个字符
[0-9a-z] 条件限制在小写0 to 9或a to z范围中一个字符
[0-9[a-z]] 条件限制在小写0 to 9或a to z范围中一个字符(交集)
[]中加入^后加再次限制条件「[^]」
[^a-z] 条件限制在非小写a to z范围中一个字符
[^A-Z] 条件限制在非大写A to Z范围中一个字符
[^a-zA-Z] 条件限制在非小写a to z或大写A to Z范围中一个字符
[^0-9] 条件限制在非小写0 to 9范围中一个字符
[^0-9a-z] 条件限制在非小写0 to 9或a to z范围中一个字符
[^0-9[a-z]] 条件限制在非小写0 to 9或a to z范围中一个字符(交集)

在限制条件为特定字符出现0次以上时,可以使用「*」
J* 0个以上J
.* 0个以上任意字符
J.*D J与D之间0个以上任意字符
在限制条件为特定字符出现1次以上时,可以使用「+」
J+ 1个以上J
.+ 1个以上任意字符
J.+D J与D之间1个以上任意字符
在限制条件为特定字符出现有0或1次以上时,可以使用「?」
JA? J或者JA出现

限制为连续出现指定次数字符「{a}」
J{2} JJ
J{3} JJJ

文字a个以上,并且共存「{a,}」
J{3,} JJJ,JJJJ,JJJJJ,???(3次以上J并存)
文字a个以上,b个以下「{a,b}」
J{3,5} JJJ或JJJJ或JJJJJ
两者取一「|」
J|A J或A
Java|Hello Java或Hello
「()」中规定一个组合类型

第三篇文章:正则的搬运工(现成的,基本拿到就可以用,当然你要解析写法我也没意见)

一、校验数字的表达式

数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[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]*))$
负浮点数:^-([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]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

二、校验字符的表达式

 汉字:^[\u4e00-\u9fa5]{0,}$
 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
 长度为3-20的所有字符:^.{3,20}$
 由26个英文字母组成的字符串:^[A-Za-z]+$
 由26个大写英文字母组成的字符串:^[A-Z]+$
 由26个小写英文字母组成的字符串:^[a-z]+$
 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
 禁止输入含有~的字符:[^~\x22]+

三、特殊需求表达式

 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX)
 :^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$ 
 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
 身份证号(15位、18位数字):^\d{15}|\d{18}$
 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间)
 :^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$ 
 日期格式:^\d{4}-\d{1,2}-\d{1,2}
 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$ 
 
 钱的输入格式:
 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":
 ^[1-9][0-9]*$ 
 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:
 ^(0|[1-9][0-9]*)$ 
 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:
 ^(0|-?[1-9][0-9]*)$ 
 4.这表示一个0开头的小数数字:
 ^[0-9]+(.[0-9]+)?$ 
 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:
 ^[0-9]+(.[0-9]{2})?$ 
 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:
 ^[0-9]+(.[0-9]{1,2})?$ 
 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:
 ^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$ 
 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:
 ^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$ 
 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
 
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> 
 (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) 
 (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

第四篇:怎么用?(Java正则示例)

查找以Java开头,任意结尾的字符串

Pattern pattern = Pattern.compile("^Java.*"); 
Matcher matcher = pattern.matcher("Java不是人"); 
boolean b= matcher.matches(); 
//当条件满足时,将返回true,否则返回false 
System.out.println(b);

以多条件分割字符串时

Pattern pattern = Pattern.compile("[, |]+"); 
String[] strs = pattern.split("Java Hello World Java,Hello,,World|Sun"); 
for (int i=0;i<strs.length;i++) { 
 System.out.println(strs[i]); 
}

文字替换(首次出现字符)

Pattern pattern = Pattern.compile("正则表达式"); 
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World"); 
//替换第一个符合正则的数据 
System.out.println(matcher.replaceFirst("Java"));

文字替换(全部)

Pattern pattern = Pattern.compile("正则表达式"); 
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World"); 
//替换第一个符合正则的数据 
System.out.println(matcher.replaceAll("Java"));

文字替换(置换字符)

Pattern pattern = Pattern.compile("正则表达式"); 
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World "); 
StringBuffer sbr = new StringBuffer(); 
while (matcher.find()) { 
 matcher.appendReplacement(sbr, "Java"); 
} 
matcher.appendTail(sbr); 
System.out.println(sbr.toString());

验证是否为邮箱地址

String str="ceponline@yahoo.com.cn"; 
Pattern pattern = Pattern.compile("[//w//.//-]+@([//w//-]+//.)+[//w//-]+",Pattern.CASE_INSENSITIVE); 
Matcher matcher = pattern.matcher(str); 
System.out.println(matcher.matches());

去除html标记

Pattern pattern = Pattern.compile("<.+?>", Pattern.DOTALL); 
Matcher matcher = pattern.matcher("<a href=/"index.html/">主页</a>"); 
String string = matcher.replaceAll(""); 
System.out.println(string);

查找html中对应条件字符串

Pattern pattern = Pattern.compile("href=/"(.+?)/""); 
Matcher matcher = pattern.matcher("<a href=/"index.html/">主页</a>"); 
if(matcher.find()) 
 System.out.println(matcher.group(1)); 
}

截取http://地址

Pattern pattern = Pattern.compile("(http://|https://){1}[//w//.//-/:]+"); 
Matcher matcher = pattern.matcher("dsdsds<http://dsds//gfgffdfd>fdf"); 
StringBuffer buffer = new StringBuffer(); 
while(matcher.find()){ 
 buffer.append(matcher.group()); 
 buffer.append("/r/n"); 
System.out.println(buffer.toString()); 
}

替换指定{}中文字

String str = "Java目前的发展史是由{0}年-{1}年"; 
String[][] object={new String[]{"//{0//}","1995"},new String[]{"//{1//}","2007"}}; 
System.out.println(replace(str,object)); 
 
public static String replace(final String sourceString,Object[] object) { 
 String temp=sourceString; 
 for(int i=0;i<object.length;i++){ 
 String[] result=(String[])object[i]; 
 Pattern pattern = Pattern.compile(result[0]); 
 Matcher matcher = pattern.matcher(temp); 
 temp=matcher.replaceAll(result[1]); 
 } 
 return temp; 
}

最后附上我自己的两个案例:

匹配类似+59.00-59.0,就是完成一个浮点数的加减表达式

 ((\\-|\\+)?\\d+(\\.\\d+)?)*

匹配HTML的图片标签

/[a-z]{2}-[a-z]{7}/[A-Za-z|-|/|.|0-9]+(.jpg|.png|.gif|.zip|.gz|.7z|.rar|.txt|.bz2

声明:TIL|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA[ZH]协议进行授权

转载:转载请注明原文链接 - 来一波,隐藏至深的正则表达式


Life is very interesting. In the end, some of your greatest pains become your greatest strengths.