尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

正则指引——字符组

正则指引——字符组 字符组1、普通字符组2、关于Python的基础知识3、普通字符组4、元字符与转义5、排除型字符组6、字符组简记法7、字符组运算8、POSIX字符组1、普通字符组字符组Character Class​是正则表达式最基本的结构之一要理解正则表达式的“灵活”​认识它是第一步。顾名思义字符组就是“一组”字符在正则表达式中它表示“在同一个位置可能出现的各种字符”​其写法是在一对方括号[和]之间列出所有可能出现的字符简单的字符组包括[ab]、[314]、[#.?]等。在解决一些常见问题时使用字符组可以大大简化操作下面举“匹配数字字符”的例子来说明。字符可以分为很多类比如数字、字母、标点等。有时候要求“只出现一个数字字符”​换句话说这个位置上的字符只能是0、1、2、…、8、9这10个字符之一。要进行这种判断通常的思路是用10个条件分别判断字符是否等于这10个字符对10个结果取“或”​只要其中一个条件成立就返回True表示这是一个数字字符其伪代码如例所示。判断数字字符的伪代码因为正则表达式处理的都是“字符串”​String而不是“字符”​所以这里假设变量charStr虽然它只包含一个字符也是字符串类型使用了双引号但是在有些语言中字符串也用单引号表示。这种解法的问题在于太烦琐—如果要判断一个小写英文字母就要用||连接26个判断如果还要兼容大写字母则要连接52个判断代码长到几乎无法阅读。相反用字符组解决起来却异常简单具体思路是列出可能出现的所有字符在这个例子里就是10个数字字符​只要出现了其中任何一个就返回True。下例给出了使用字符组判断的例子程序语言使用Python。用正则表达式判断数字字符:importre charStrabc123re.search([0123456789],charStr)!None# truere.search()是Python提供的正则表达式操作函数表示“进行正则表达式匹配”​charStr仍然是需要判断的字符串而[0123456789]则是以字符串形式给出的正则表达式它是一个字符组表示“这里可以是0、1、2、…、8、9中的任意一个字符。只要charStr与其中任何一个字符相同或者说“charStr可以由[0123456789]匹配”​​就会得到一个MatchObject对象​否则返回None。所以判断结果是否为None就可以判断charStr是否是数字字符。用正则表达式判断数字字符在各种语言中的应用可以看到不同语言使用正则表达式的方法也不相同。如果仔细观察会发现Java、.NET、Python、PHP中的正则表达式都要以字符串形式给出两端都有双引号而Ruby和JavaScript中的正则表达式则不必如此只在首尾有两个斜线字符/这也是不同语言中使用正则表达式的不同之处。不过这个问题现在不需要太关心因为大部分例子以Python程序来讲解下面讲解关于Python的基础知识。2、关于Python的基础知识选择使用Python语言来演示实际的匹配结果因为它能在多种操作系统中运行安装也很方便另一方面Python是解释型语言输入代码就能看到结果方便动手实践。考虑到不是所有人都熟悉Python这里专门用一节的篇幅来介绍。如果你的机器上没有安装Python可以从http://python.org/download/下载目前Python有2和3两个版本例子以2版本为准。​[3]请选择自己平台对应的程序下载并安装目前Mac OS、Linux的各种发行版一般带有Python具体可以在命令行下输入python看是否启动对应的程序​。然后可以启动Python在Mac OS和Linux下输入python会显示出Python提示符进入交互模式如图所示Linux下的提示符与Mac OS下的差不多所以此处不列出​而在Windows下需要在“开始”菜单的“程序”中选择Python 目录下的Python(commandline)如图所示。Python中常用的关于正则表达式的函数是re.search() 使用它必须首先导入正则表达式对应的包package​也就是输入下面的代码。通常的用法是提供两个参数re.search(pattern,string)其中pattern是字符串形式提供的正则表达式string是需要匹配的字符串如果能匹配则返回一个MatchObject​这时提示符会显示类似_sre.SRE_Match object at 0x0000000001D8E578之类的结果如果不能匹配结果是None这是Python中的一个特殊值类似其他某些语言中的Null​不会有任何显示。下图演示了运行Python语句的结果。为讲解清楚、形象、方便本书中的程序部分需要做两点修改。第一因为暂时还不需要关心匹配结果的细节只关心有没有结果所以在re.search()之后添加判断返回值是否为None如果为True则表示匹配成功否则返回False表示匹配失败。为节省版面尽可能用注释表示这类匹配结果如# True或者 # False附在语句之后。第二目前我们关心的是整个字符串是否能由正则表达式匹配。但是在默认情况下re.search(pattern,string)只判断string的某个子串能否由pattern匹配即便pattern只能匹配string的一部分也不会返回None。为了测试整个string能否由pattern匹配在pattern两端加上^和$。^和$是正则表达式中的特殊字符它们并不匹配任何字符只是表示“定位到字符串的起始位置”和“定位到字符串的结束位置”​原理如图示​这样就保证只有在整个string都可以由pattern匹配时才算匹配成功不返回None如例所示。使用^和$测试string由pattern完整匹配3、普通字符组字符组中的字符排列顺序并不影响字符组的功能出现重复字符也不会影响所以[0123456789]完全等价于[9876543210]、[1029384756]、[9988876543210]。不过代码总是要容易编写、方便阅读正则表达式也是一样的所以一般并不推荐在字符组中出现重复字符。而且还应该让字符组中的字符排列更符合认知习惯比如[0123456789]就好过[0192837465]。为此正则表达式提供了-范围表示法range​它更直观能进一步简化字符组。所谓“-范围表示法”​就是用[x-y]的形式表示x到y整个范围内的字符省去一一列出的麻烦这样[0123456789]就可以表示为[0-9]。如果你觉得这么做看起来意义不大那么[a-z]确实比[abcdefghijklmnopqrstuvwxyz]简单太多了。你可能会问​“-范围表示法”的范围是如何确定的为什么要写作[0-9]而不写作[9-0]要回答这个问题必须了解范围表示法的实质。在字符组中-表示的范围一般是根据字符对应的码值CodePoint也就是字符在对应编码表中的编码的数值来确定的码值小的字符在前码值大的字符在后。在ASCII编码中包括各种兼容ASCII的编码中​字符0的码值是48十进制​字符9的码值是57十进制​所以[0-9]等价于[0123456789]而[9-0]则是错误的范围因为9的码值大于0所以会报错。程序代码见例。** [0-9]是合法的​[9-0]会报错**如果知道09的码值是4857az的码值是97122AZ的码值是6590能不能用[0-z]统一表示数字字符、小写字母、大写字母呢答案是勉强可以但不推荐这么做。根据惯例字符组的范围表示法用来表示一类字符数字字符是一类字母字符也是一类​所以虽然[0-9]、[a-z]都是很好理解的但[0-z]却很难理解不熟悉ASCII编码表的人甚至不知道这个字符组还能匹配大写字母更何况在码值48到122之间除去数字字符码值4857​、小写字母码值97122​、大写字母码值6590​还有不少标点符号参见下表​从字符组[0-z]中却很难看出来使用时就容易引起误会例所示的程序就很可能让人感觉莫名其妙。[0-z]的奇怪匹配在字符组中可以同时并列多个“-范围表示法”​字符组[0-9a-zA-Z]可以匹配数字、大写字母或小写字母字符组[0-9a-fA-F]可以匹配数字大、小写形式的af它可以用来验证十六进制字符代码见下例。[0-9a-fA-F]准确判断十六进制字符在不少语言中还可以用转义序列\xhex来表示一个字符其中\x是固定前缀表示转义序列的开头num是字符对应的码值Code Point​是一个两位的十六进制数值。比如字符A的码值是41十进制则为65​所以也可以用\x41表示。字符组中有时会出现这种表示法它可以表现一些难以输入或者难以显示的字符比如\x7F也可以用来方便地表示某个范围比如所有ASCII字符对应的字符组就是[\x00-\x7F]代码见下例。这种表示法很重要在第126页还会讲到它依靠这种表示法可以很方便地匹配所有的中文字符。[\x00-\x7F]准确判断ASCII字符4、元字符与转义在上面的例子里字符组中的横线-并不能匹配横线字符而是用来表示范围这类字符叫作元字符meta-character​。字符组的开方括号[​、闭方括号]和之前出现的^、$都算元字符。在匹配中它们有着特殊的意义。但是有时候并不需要表示这些特殊意义只需要表示普通字符比如“我就想表示横线字符-”​​此时就必须做特殊处理。先来看字符组中的-如果它紧邻着字符组中的开方括号[那么它就是普通字符其他情况下都是元字符而对于其他元字符取消特殊含义的做法都是转义也就是在正则表达式中的元字符前加上反斜线字符\。如果要在字符组内部使用横线-最好的办法是将它排列在字符组的开头。[-09]就是包含三个字符-、0、9的字符组[0-9]是包含09这10个字符的字符组[-0-9]则是由“-范围表示法”0-9和横线-共同组成的字符组它可以匹配11个字符下例说明了使用横线-的各种情况。–出现在不同位置含义不同仔细观察会发现在正文里说“在正则表达式中的元字符之前加上反斜线字符\”​而在代码里写的却不是[0-9]​而是[0\-9]​。这并不是输入错误。因为在这段程序里正则表达式是以字符串String的方式[4]提供的而字符串本身也有关于转义的规定你或许记得在字符串中有\n、\t之类的转义序列​。上面说的“正则表达式”​其实是经过“字符串转义处理”之后的字符串的值正则表达式[0\-9]包含6个字符​[​、0、\、-、9、​]​在表达式中只需要使用这6个字符即可但是在源代码里必须使用7个字符\需要转义成\\因为处理字符串时反斜线和它之后的字符会被认为是转义序列Escape Sequence​比如\n、\t都是合法的转义序列然而\-不是。这个问题确实有点麻烦。正则表达式是用来处理字符串的但它又不完全等于字符串正则表达式中的每个反斜线字符\在字符串中也就是正则表达式之外还必须转义为\\。所以之前所说的是“正则表达式[0\-9]”​程序里写的却是[0\\-9]​这确实有点麻烦但需要注意。不过Python提供了原生字符串Raw String​它非常适合于正则表达式正则表达式是怎样的原生字符串就是怎样的完全不需要考虑正则表达式之外的转义只有双引号字符是例外原生字符串内的双引号字符必须转义写成​。原生字符串的形式是rstring也就是在普通字符串之前添加r示例代码如下例。原生字符串的使用如果希望在字符组中列出闭方括号]比如[012]345]就必须在它之前使用反斜线转义写成[012\]345]否则结果就如下例所示正则表达式将]与最近的[匹配这个表达式就成了“字符组[012]加上4个字符345]”​它能匹配的是字符串0345]或1345]或2345]​却不能匹配]​。]出现在不同位置含义不同除去字符组内部的-其他元字符的转义都必须在字符之前添加反斜线[的转义也是如此。如果只希望匹配字符串[012]​直接使用正则表达式[012]是不行的因为这会被识别为一整个字符组它只能匹配0、1、2这三个字符中的任意一个所以必须转义把正则表达式写作\[012]请注意只有开方括号[需要转义闭方括号]不需要转义如下例所示。取消其他元字符的特殊含义要转义如下例所示。取消其他元字符的特殊含义5、排除型字符组在方括号[…]中列出希望匹配的所有字符这种字符组可以叫作“普通字符组”​它的确非常方便。不过也有些问题是普通字符组不能解决的。给定一个由两个字符构成的字符串str要判断这两个字符是否都是数字字符可以用[0-9]​[0-9]来匹配。但是如果要求判断的是这样的字符串—第一个字符不是数字字符第二个字符才是数字字符比如A8、x6​[5]—应当如何处理数字字符的匹配很好处理用[0-9]即可​“不是数字”则很难办—不是数字的字符太多了全部列出几乎不可能这时就应当使用排除型字符组。排除型字符组Negated Character Class非常类似普通字符组[…]只是在开方括号[之后紧跟一个脱字符^写作[^…]表示“在当前位置匹配一个没有列出的字符”​。所以[^0-9]就表示“09之外的字符”​也就是“非数字字符”​。那么[^0-9]​[0-9]就可以解决问题了如下例所示。使用排除型字符组排除型字符组看起来很简单不过新手常常会犯一个错误就是把“在当前位置匹配一个没有列出的字符”理解成“在当前位置不要匹配列出的字符”​两者其实是不同的后者暗示“这里不出现任何字符也可以”​。下例很清楚地说明排除型字符组必须匹配一个字符这点一定要记住。排除型字符组必须匹配一个字符除了开方括号[之后的^排除型字符组的用法与普通字符组几乎完全相同唯一需要改动的是在排除型字符组中如果需要表示横线字符-而不是用于“-范围表示法”​​那么-应该紧跟在^之后而在普通字符组中作为普通字符的横线-应该紧跟在开方括号之后如下例所示。在排除型字符组中紧跟在^之后的-不是元字符在排除型字符组中^是一个元字符但只有它紧跟在[之后时才是元字符如果想表示“这个字符组中可以出现^字符”​不要让它紧挨着[即可否则就要转义。下例给出了三个正则表达式后两个表达式实质是一样的但第三种写法很麻烦理解起来也麻烦不推荐使用。排除型字符组的转义6、字符组简记法用[0-9]、[a-z]等字符组可以很方便地表示数字字符和小写字母字符。对于这类常用的字符组正则表达式提供了更简单的记法这就是字符组简记法shorthands​。常见的字符组简记法有\d、\w、\s。从表面上看它们与[…]完全没联系但效果其实是等价的。其中\d等价于[0-9]其中的d代表“数字digit​”​\w等价于[0-9a-zA-Z_]其中的w代表“单词字符word​”​\s等价于[\t\r\n\v\f]第一个字符是空格​s表示“空白字符space​”​。下例说明了这几个字符组简记法的典型匹配。一般印象中单词字符似乎只包含大小写字母但是字符组简记法中的“单词字符”不只有大小写单词还包括数字字符和下画线_其中的下画线_尤其值得注意在进行数据验证时有可能只允许输入“数字和字母”​有人会偷懒用\w验证而忽略了\w能匹配下画线所以这种匹配并不严格[0-9a-zA-Z]才是准确的选择。“空白字符”并不难定义它可以是空格字符、制表符\t、回车符\r、换行符\n等各种“空白”字符只是不方便展现因为显示和印刷出来都是空白​。不过这也提醒我们注意匹配时看到的“空白”可能不是空格字符因此\s才是准确的选择。字符组简记法可以单独出现也可以使用在字符组中比如[0-9a-zA-Z]也可以写作[\da-zA-Z]所以匹配十六进制字符的字符组可以写成[\da-fA-F]。字符组简记法也可以用在排除型字符组中比如[^0-9]就可以写成[^\d][^0-9a-zA-Z_]就可以写成[^\w]代码如下例。字符组简记法与普通字符组混用相对于\d、\w和\s这三个普通字符组简记法正则表达式也提供了对应排除型字符组的简记法\D、\W和\S—字母完全一样只是改为大写。这些简记法能匹配的字符是互补的\s能匹配的字符\S一定不能匹配\w能匹配的字符\W一定不能匹配\d能匹配的字符\D一定不能匹配。下例示范了这几个字符组简记法的应用。\D、\W、\S的使用妥善利用这种互补的属性可以得到一些非常巧妙的效果最简单的应用就是字符组[\s\S]。初看起来在同一个字符组中并列两个互补的简记法这种做法有点奇怪不过仔细想想就会明白\s和\S组合在一起匹配的就是“所有的字符”​或者叫“任意字符”​​。许多语言中的正则表达式并没有直接提供“任意字符”的表示法所以[\s\S]、[\w\W]、[\d\D]虽然看起来有点古怪但确实可以匹配任意字符。​关于字符组简记法最后需要补充三点第一如果字符组中出现了字符组简记法最好不要出现单独的-否则可能引起错误比如[\d-a]就很让人迷惑在有些语言中-会被作为普通字符而在有些语言中这样写会报错第二以上说的\d、\w、\s的匹配规则都是针对ASCII编码而言的也叫ASCII匹配规则。但是目前一些语言中的正则表达式已经支持了Unicode字符那么数字字符、单词字符、空白字符的范围已经不仅限于ASCII编码中的字符。第三不同的语言可能有一些专属的独特的字符组简记法比如Java 8就提供了\h、\v两种前者匹配“任何水平方向的空白字符”​后者匹配“任何垂直方向的空白字符”​。在某些具体场景下它们确实很方便只是不熟悉的读者阅读起来会有点困难。7、字符组运算以上介绍了字符组的基本功能它们在常用的语言中都有提供还有些语言为字符组提供了更强大的功能比如Java、.NET、Objective-C就提供了字符组运算的功能可以在字符组内进行集合运算在某些情况下这种功能非常实用。如果要匹配所有的元音字母暂时只考虑小写字母的情况​可以用[aeiou]但是要匹配所有的辅音字母却没有什么方便的办法最直接的写法是[b-df-hj-np-tv-z]不但烦琐而且难理解。换个角度看从26个字母中“减去”元音字母剩下的就是辅音字母如果有办法做这个“减法”​就方便多了。Java语言中提供了这样的字符组​[​[a-z][^aeiou]​]虽然初看有点古怪但仔细看看也不难理解。[a-z]表示26个英文字母[^aeiou]表示除元音字母之外的所有字符还包括大写字母、数字和各种符号​两者取交集就得到“26个英文字母中除去5个元音字母剩下的21个辅音字母”​。.NET中也有这样的功能只是写法不一样。同样是匹配辅音字母的字符组.NET中写作[a-z-[aeiou]​]其逻辑是从[a-z]能匹配的26个字符中​“减去”[aeiou]能匹配的元音字母。相对于Java这种逻辑更符合直觉但写法却有点古怪—不是[​[a-z]-[aeiou]​]而是[a-z-[aeiou]​]。下例集中演示了Java和.NET中的字符组运算。字符组运算8、POSIX字符组前面介绍了常用的字符组但是在某些文档中你可能会发现类似[:digit:]​、​[:lower:]之类的字符组看起来不难理解digit就是“数字”​lower就是“小写”​​但又很奇怪它们就是POSIX 字符组POSIX CharacterClass​。因为某些语言的文档中出现了这些字符组为避免困惑这里有必要做一个简要介绍。如果只使用常用的编程语言可以忽略文档中的POSIX字符组也可以忽略本节如果想了解POSIX字符组或者需要在Linux/UNIX下的各种工具sed、awk、grep等中使用正则表达式最好阅读本节。之前介绍的字符组都属于Perl衍生出来的正则表达式流派Flavor​这个流派叫作PCREPer CompatibleRegular Expression​。除此之外正则表达式还有其他流派比如POSIXPortable Operating SystemInterface for uniX​它是一系列规范定义了UNIX操 作系统应当支持的功能其中也包括关于正则表达式的规范[:digit:]之类的字符组就是遵循POSIX规范的字符组。常见的[a-z]形式的字符组在POSIX规范中仍然获得支持它的准确名称是POSIX方括号表达式POSIXbracket expression​主要用在UNIX/Linux系统中。POSIX方括号表达式与之前所说的字符组最主要的差别在于在POSIX字符组中反斜线\不是用来转义的。所以POSIX方括号表达式[\d]只能匹配\和d两个字符而不是[0-9]对应的数字字符。为了解决字符组中特殊意义字符的转义问题POSIX方括号表达式规定如果要在字符组中表达字符]​而不是作为字符组的结束标记​应当让它紧跟在字符组的开方括号之后所以[​]a]能匹配的字符就是]或a如果要在字符组中标识字符-而不是“-范围表示法”​​就必须将它放在字符组的闭方括号]之前所以[a-]能匹配的字符就是a或-。另一方面POSIX规范还定义了POSIX字符组POSIXcharacter class​它大致等于之前介绍的字符组简记法都是使用类似[:digit:]、[:lower:]之类有明确意义的记号表示某类字符。下表简要介绍了POSIX字符组注意表格中与其对应的是ASCII字符组也就是能匹配的ASCII字符ASCII编码表中码值在0127之间的字符​。因为POSIX规范中有一个重要概念locale通常翻译为“语言环境”​​它是一组与语言和文化相关的设定包括日期格式、货币币值、字符编码等。POSIX字符组的意义会根据locale的变化而变化下表介绍的只是这些POSIX字符组在ASCII编码中的意义如果换用其他的locale比如使用Unicode字符集​它们的意义可能会发生变化。POSIX字符组的使用也与PCRE字符组简记法的使用有所不同主要区别在于PCRE字符组简记法可以脱离方括号直接出现而POSIX字符组必须出现在方括号内。所以同样是匹配数字字符PCRE中可以直接写\d而POSIX字符组必须写成[​[:digit:]​]。在各种语言中Java、PHP、Ruby、Golang支持使用POSIX字符组。在PHP中可以直接使用POSIX字符组但是PHP中的POSIX字符组只识别ASCII字符也就是说任何非ASCII字符比如中文字符都不能由任何一个POSIX字符组匹配。Ruby的情况稍微复杂一点。Ruby 1.8中的POSIX字符组只能匹配ASCII字符而且不支持[:word:]和[:ASCII:]Ruby 1.9中的POSIX字符组可以匹配Unicode字符而且支持[:word:]和[:ASCII:]。Java中的情况更加复杂。POSIX字符组[​[:name:]​]必须使用\p{name}的形式其中name为POSIX字符组对应的名字比如[:space:]就应当写作\p{Space}请注意第一个字母要大写其他POSIX字符组都是这样只有[:xdigit:]要写作\p{XDigit}。还需要指出的是Java中的POSIX字符组只能匹配ASCII字符。Golang的情况与PHP类似POSIX字符组可以直接使用但是都只能匹配ASCII字符。
返回列表