正则表达式之字符组

  • 1 字符组
本节讨论的都是ASCII匹配规则,Unicode在第7节讨论。
  • 1.1 普通字符组
Character Class就是一组字符,在正则表达式中,它表示“在同一个位置可能出现的各种字符”。
注意:本文用Python语言验证各种正则表达式的匹配。

例子1 用正则表达式判断数字字符在各种语言中的应用
.NET(C#)
//能匹配则返回true,否则返回false
Regex.IsMatch(charStr,"[0123456789]");

Java
//能匹配则返回true,否则返回false
charStr.matchs("[0123456789]");

JavaScript
//能匹配则返回true,否则返回false
/[0123456789]/.test(charStr);

PHP
//能匹配则返回1,否则返回0
preg_match("/[0123456789]/",charStr);

Python
//能匹配则返回RegexObject,否则返回None
import re
re.search("[0123456789]",charStr);

Ruby
//能匹配则返回0,否则返回nil
charStr =~/[0123456789]/


默认情况下,re.search(pattern,string)只判断string的某个子串能否由pattern匹配,即使pattern只能匹配string的一部分,也不会返回None。如果需要完全匹配,在pattern两端加上^和$

# 只要字符串中包含数字字符,就可以匹配
>>> import re
>>> re.search("[0123456789]","2") != None
True
>>> re.search("[0123456789]","12") != None
True
>>> re.search("[0123456789]","a2") != None
True

# 整个字符串就是一个数字字符,才可以匹配
>>> re.search("^[0123456789]$","a2") != None
False
>>> re.search("^[0123456789]$","12") != None
False

字符组中的字符排列顺序并不影响字符组的功能,出现重复字符也不会影响。不过,代码总是要容易编写,方便阅读,所以一般不推荐在字符组中出现重复字符。而且,还应该让字符组中的字符排列更符合认知习惯,比如[0123456789]就好过[0192837465]。为此,正则表达式提供了-范围表示法(range)。

>>> import re
>>> re.search("^[0-9]$","2")!=None
True

>>> re.search("^[9-0]$","2")!=None

Traceback (most recent call last):
File "<pyshell#6>",line 1,in <module>
re.search("^[9-0]$","2")!=None
File "C:\Python27\lib\re.py",line 142,in search
return _compile(pattern,flags).search(string)
File "C:\Python27\lib\re.py",line 242,in _compile
raise error,v # invalid expression
error: bad character range

从上面的例子可以看出,[0-9]等价于[0123456789],而[9-0]则是错误的范围,因为9的ASCII码值是57,大于0的码值48.

如果想表示数字和字母,那么推荐使用[0-9a-z]而不是简单地使用[0-z],因为0-z之间还有标点符号和大写的A-Z。

例子 [0-z]的奇怪匹配

>>> re.search("^[0-z]$","A")!=None
True
>>> re.search("^[0-z]$",":")!=None
True

例子[0-9a-fA-F]准确判断十六进制字符
>>> re.search("^[0-9a-fA-F]$","0") != None
True
>>> re.search("^[0-9a-fA-F]$","c") != None
True
>>> re.search("^[0-9a-fA-F]$","i") != None
False
>>> re.search("^[0-9a-fA-F]$","C") != None
True
>>> re.search("^[0-9a-fA-F]$","G") != None
False

使用\xhex表示一个字符,其中\x是固定前缀,表示转义序列的开头,num是字符对应的码值,是一个两位的十六进制数值。依靠这种表示法可以很方便地匹配所有的中文字符。
例子 [\x00-\x7F]准确判断ASCII 字符

>>> re.search("^[\x00-\x7F]$","c")!= None
True
>>> re.search("^[\x00-\x7F]$","0")!= None
True
>>> re.search("^[\x00-\x7F]$","<")!= None
True

  • 1.2 元字符与转义
像范围表示法中的-,字符组的开方括号[、闭方括号]和之前出现的^、$都是元字符,在匹配中,它们有特殊的意义。但是,有时并不需要表示这些特殊意义,只需要表示普通字符,此时需要使用转义字符\来做特殊处理。
例子 -出现在不同位置,含义不同
#作为普通字符
>>> re.search("^[-09]$","3")!=None
False
>>> re.search("^[-09]$","-")!=None
True

#作为元字符
>>> re.search("^[0-9]$","3")!= None
True
>>> re.search("^[0-9]$","-")!= None
False

#转义之后作为普通字符
>>> re.search("^[0\\-9]$","3")!= None
False
>>> re.search("^[0\\-9]$","-")!= None
True

我们注意到上面的转义字符中使用了两个\,这是由于\n,\t之类的会被认为是转义序列(Escape Sequence),但\-不是,所以在字符串中,需要使用\\表示转义。 (在Java、PHP、Python、.NET等语言中,正则表达式都是以字符串的形式给出的,在Ruby和JavaScript中则不是。)
在Python中,提供了原生字符串(Raw String),它非常适合于正则表达式,此时就不需要考虑正则表达式之外的转义。原生字符串的形式是r"string"。.NET和Ruby也提供了原生字符串。
例子 原生字符串的使用
#原生字符串和字符串的等价
>>> r"^[0\-9]$" == "^[0\\-9]$"
True

#原生字符串的转义
>>> re.search(r"^[0\-9]$","3") != None
False
>>> re.search(r"^[0\-9]$","-") != None
True

例子 ]出现在不同位置,含义不同
#未转义的]
>>> re.search(r"^[012]345]$","2345") != None
False
>>> re.search(r"^[012]345]$","5") != None
False
>>> re.search(r"^[012]345]$","]") != None
False

#转义的]
>>> re.search(r"^[012\]345]$","2345") != None
False
>>> re.search(r"^[012\]345]$","5") != None
True
>>> re.search(r"^[012\]345]$","]") != None
True

例子 取消其他元字符的特殊含义
>>> re.search(r"^[012]345]$","3")!= None
False
>>> re.search(r"^[012\]345]$","3")!= None
True
>>> re.search(r"^[012]$","[012]")!= None
False
#只需要转义开方括号[,闭方括号]不需要转义。
>>> re.search(r"^\[012]$","[012]")!= None
True
  • 1.3 排除型字符组
在开方括号[之后紧跟一个脱字符^,写作[^...]。

例子 使用排除型字符组
>>> re.search(r"^[^0-9][0-9]$","A8") != None
True
>>> re.search(r"^[^0-9][0-9]$","x6") != None
True

例子 排除型字符组必须匹配一个字符
>>> re.search(r"^[^0-9][0-9]$","8") != None
False
>>> re.search(r"^[^0-9][0-9]$","A8") != None
True

例子 在排除型字符组中,紧跟在^之后的-不是元字符
>>> re.search(r"^[^-09]$","-") != None
False
>>> re.search(r"^[^-09]$","8") != None
True

>>> re.search(r"^[^-09]$","-") != None
False
>>> re.search(r"^[^-09]$","8") != None
True

例子 排除型字符组的转义
>>> re.search(r"^[^012]$","^")!=None
True
>>> re.search(r"^[0^12]$","^")!=None
True
>>> re.search(r"^[\^012]$","^")!=None
True

  • 1.4 字符组简记法
正则表达式对[0-9],[a-z]之类的字符组提供了简单的记法,称为字符组简记法(shorthands).常用的字符组简记法有\d表示[0-9],\w表示[0-9a-zA-Z_],\s表示[ \t\n\r\v\f]。
例子 字符组简记法\d \w \s
>>> re.search(r"^\d$","9")!=None
True
>>> re.search(r"^\d$","a")!=None
False
>>> re.search(r"^\w$","8")!=None
True
>>> re.search(r"^\w$","a")!=None
True
>>> re.search(r"^\w$","_")!=None
True
>>> re.search(r"^\s$"," ")!=None
True
>>> re.search(r"^\s$","\t")!=None
True
>>> re.search(r"^\s$","\n")!=None
True

字符组简记法既可以单独使用,也可以使用在字符组中,比如[0-9a-zA-Z]也可以写作[\da-zA-Z]。也可以使用在排除型字符组中,比如[^0-9]可以写作[^\d]。

例子 字符组简记法与普通字符组混用
>>> re.search(r"^[\da-zA-Z]$","8") != None
True
>>> re.search(r"^[\da-zA-Z]$","a") != None
True
>>> re.search(r"^[\da-zA-Z]$","C") != None
True
>>> re.search(r"^[^\w]$","8")!=None
False
>>> re.search(r"^[^\w]$","_")!=None
False
>>> re.search(r"^[^\w]$",",")!=None
True

\d,\w,\s对应的排除型字符组记为\D,\W,\S。
例子 \D \W \S的使用
#\d和\D
>>> re.search(r"^\d$","8")!=None
True
>>> re.search(r"^\d$","a")!=None
False
>>> re.search(r"^\D$","8")!=None
False
>>> re.search(r"^\D$","a")!=None
True
#\w和\W
>>> re.search(r"^\w$","c")!=None
True
>>> re.search(r"^\w$","!")!=None
False
>>> re.search(r"^\W$","c")!=None
False
>>> re.search(r"^\W$","!")!=None
True

#\s和\S
>>> re.search(r"^\s$","0")!=None
False
>>> re.search(r"^\S$","\t")!=None
False
>>> re.search(r"^\S$","0")!=None
True

[\s\S],[\w\W],[\d\D]都可以表示任意字符。在许多有关正则表达式的文档中,点号.能匹配“任意字符”。但在默认情况下,点号其实不能匹配换行符。当然可以在某些语言中开启单行模式(Single line),在这种模式下,所有文本似乎都在一行里,换行符是这一行中的“普通字符”,所以可以由.点号匹配。

  • 1.5 字符组运算
Java:
“a".matches("^[[a-z]&&[^aeIoU]]$"); //False
"b".mattches("^[[a-z]&&[^aeIoU]]$"); //True

  • 1.6 POSIX字符组
常用在Linux/UNIX下的各种工具(sed、AWK、grep等)中。之前介绍的都属于Perl衍生出来的正则表达式刘鹏,叫做 PCRE(Perl Compatible Regular Expression).而POSIX(Portable Operating System Interface for uniX),它是一系列规范,定义了UNIX操作系统应当指出的功能,其中也包括了关于正则表达式的规范,[:digit:]之类的字符组就是遵循POSIX规范的字符组。
常见的[a-z]形式的字符组,在POSIX规范中仍然获得支持,它的准确名称是POSIX方括号表达式(POSIX bracket expression),主要用在UNIX/Linux系统中。POSIX方括号表达式与PCRE的字符组最主要的差别在于:在POSIX字符组中,\不是用来转义的。所以POSIX方括号表达式[\d]只能匹配\和d两个字符,而不是[0-9]对应的数字字符。
为了解决字符组中特殊意义字符的转义问题, POSIX方括号表达式规定:如果要在字符组中表达字符](而不是作为字符组的结束标记],应当让它紧跟在字符组的开方括号之后,所以[]a]能匹配的字符就是]或者a;如果要在字符组中标记字符-(而不是做为范围表示法),就必须将它放在字符组的闭方括号]之前,所以[a-]能匹配的字符就是a或-。


表 POSIX字符组(对应ASCII字符,下表不支持Unicode字符集)
POSIX字符组 说明 ASCII字符组 等级的PCRE简记法
[:alnum:] * 字母字符和数字字符 [a-zA-Z0-9]
[:alpha:] 字母 [a-zA-Z]
[:ASCII:] ASCII字符 [\x00-\x7F]
[:blank:] 空格字符和制表符 [ \t]
[:cntrl:] 控制字符 [\x00-\x1F\x7F]
[:digit:] 数字字符 [0-9] \d
[:graph:] 空白字符之外的字符 [\x21-\x7E]
[:lower:] 小写字母字符 [a-z]
[:print:] 类似[:graph:],但包括空白字符 [\x20-\x7E]
[:punct:] 标点符号 [][!"#$%@&'*(){}-+,./?:;<>=|~`]
[:space:] 空白字符 [ \t\r\n\v\f] \s
[:upper:] 大写字母字符 [A-Z]
[:word:]* 字母字符 [a-zA-Z0-9_] \w
[:xdigit:] 十六进制字符 [A-Fa-f0-9]
注:标记*的字符组简记法并不是POSIX规范中的,但使用很多,一般语言都提供。

JAVA、PHP、Ruby支持使用POSIX字符组。
PHP中可以直接使用POSIX字符组,但是PHP的POSIX字符组只识别ASCII字符,任何非ASCII字符都不能有任何一个POSIX字符组匹配。
Ruby1.8中的POSIX字符组只匹配ASCII字符,而且不支持[:word:]和[:ASCII:];Ruby1.9中的POSIX字符组可以匹配Unicode字符,而且支持[:word:]和[:ASCII:]。
JAVA中,POSIX字符组必须使用\p{Name}的形式,其中name为POSIX字符组对应的名称,比如[:digit:]就应该写作\p{Digit},第一个字母要大写,其他POSIX字符组都是这样,只有[:xdigit:]就应该写作\p{XDigit}.并且JAVA中的POSIX字符组只匹配ASCII字符。

相关文章

一、校验数字的表达式 1 数字:^[0-9]*$ 2 n位的数字:^d{n}$ 3 至少n位的数字:^d{n,}$ 4 m-n位的数字...
正则表达式非常有用,查找、匹配、处理字符串、替换和转换字符串,输入输出等。下面整理一些常用的正则...
0. 注: 不同语言中的正则表达式实现都会有一些不同。下文中的代码示例除特别说明的外,都是使用JS中的...
 正则表达式是从信息中搜索特定的模式的一把瑞士军刀。它们是一个巨大的工具库,其中的一些功能经常...
一、校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数...
\ 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,“n”匹配字符“n”。“\n...