正则表达式中的大多数结构匹配的文本会出现在最终的匹配结果中(一般用group(0)可以得到),但是也有些结构并不真正匹配文本,而只负责判断在某个位置左/右侧的文本是否符合要求,这种结构被称为断言(assertion)。常见的断言有三类:单词边界行起始/结束位置环视

单词边界

在文本处理中经常可能进行单词替换,比如把一段文本中的row都替换成line。一般想到的是调用字符串的替换方法,直接替换row。在不同语言中这些方法各不相同,但差别不大。

替换前:The row we are looking for is row 10.

替换后:The line we are looking for is line 10.

不过,这样替换也可能会造成意想不到的后果。

替换前:...tomorrow I will wear in brown standing in row 10 next to the rowdy guy...

替换后:...tomorline I will wear in blinen standing in line 10 next to the linedy guy...

不仅所有单词row都被替换成了line,其他单词内部的row也被替换成了line,这显然不是我们想要的结果。要解决这个问题,必须有办法确定单词row,而不是字符串row。为解决这类问题,正则表达式提供了专用的单词边界(word boundary),记为 \b 。它匹配的是“单词边界”位置,而不是字符。也就是说, \b 能够匹配这样的位置:一边是单词字符,另一边不是单词字符,匹配示例:


\brow\b
\brow
row\b
tomorrow


OK
brown



row
OK
OK
OK
rowdy

OK

表达式说明
只能是单词
\b的右侧是单词字符,
所以左侧不能是单词字符
\b的左侧是单词字符,
所以右侧不能是单词字符

观察表格,可以发现两点:第一,单词边界并不区分左右,在“单词边界”上,可能只有左侧是单词字符,也可能只有右侧是单词字符,总的来说,单词字符只能出现在一侧第二,单词字符要求“另一边不是单词字符”,而不是“另一边的字符不是单词字符”,也就是说,一边必须出现单词字符,另一边可以出现非单词字符,也可能没有任何字符。所以,如果字符串只包含单词word,用 \bword\b应该是可以匹配的,虽然w之前和d之后都没有任何字符。

单词边界要求一侧必须出现单词字符,到底什么是单词字符呢?

一般情况下,“单词字符”的解释是 \w 能匹配的字符。在javascript,PHP,Python2,Ruby中, \w只能匹配 [0-9a-zA-z_]。所以在这些语言中, \bw+\b能准确匹配英文单词了。示例:

//单词边界匹配
Stringtext="tomorrowIwillwearinbrownstandinginrow10nexttotherowdyguy";
Patternp=Pattern.compile("\\b(\\w+)\\b");
Matcherm=p.matcher(text);
while(m.find()){
System.out.println(m.group(1));
}

但是也有些单词,\b\w+\b是无能为力的,比如e-mail和M.I.T.。因为连字符-和点号. 都不能由 \w 匹配,所以 \b\w+\b无法匹配e-mail,也无法匹配M.I.T. 。如果确实希望处理e-mail之类的“单词”,也可以把表达式改为 \b[-\w]+\b 。

与单词边界 \b 对应的还有非单词边界 \B 两者的关系类似 \s和\S,\w和\W,\d和\D;在同一种语言中,不管 \b 是如何规定的,\b能匹配的位置,\B就不能匹配;\B能匹配的位置,\b就不能匹配。但是在实际使用中,\B使用频率远远少于 \b。

行起始/结束位置

单词边界匹配的是某个位置而不是文本,在正则表达式中,这类匹配位置的元素叫做锚点(anchor),它用来“定位”到某个位置。常用的锚点还有^$它们分别匹配字符串的开始位置和结束位置,所以可以用来判断“整个字符串能否由表达式匹配”。依靠^,就可以用正则表达式^Some准确验证字符串“是否以Some开头”,因为^会把整个表达式的匹配“定位”在字符串的开始位置。这样,即便表达式的其他部分可以在字符串中其他位置找到匹配,整个表达式也无法匹配成功

在某些情况下,^也可以匹配字符串内部的“行起始位置”。在讲解这种情况之前,我们先来看看怎么划分行。在编辑文本时,敲回车键就输入了行终止符(Line terminal),结束当前行,新起一行。看起来,这很好理解,然而不同平台上的行终止符其实各不相同,下表列出了各平台下的“行终止符”:

平台
行终止符
UNIX/Linux
\n
Windows
\r\n
Mac OS
\n

也就是说,每一行的“起始位置”,就是“行终止符”之后的那个位置,如果没有专门的符号,就要考虑各种“行终止符”。下面的例子看得更清楚,为了让换行符“可见”,我们用NL表示。

first line

second line

last line

它其实是下面这样,其中的NL可能是 \n,也可能是 \r\n。

first lineNLsecond lineNLlast lineNL

如果把匹配模式设定为多行模式(Multiline Mode,这是一种影响元字符匹配的设定,后面在讲)下,^就即可以匹配整个字符串的起始位置,也可以匹配换行符之后的位置(设定多行模式最简单的办法是在正则表达式之前加上 (?m) ,这里虽然出现了括号,但因为是专用于指定匹配模式,所以不会作为捕获分组)。不过一般来说,^的主要用途是与其他子表达式配合,如下例那样提取每行的第一个单词:

//提取每行的第一个单词
Stringtext="firstline\nsecondline\r\n\rlastline";
Patternp=Pattern.compile("(?m)^(\\w+)");
Matcherm=p.matcher(text);
while(m.find()){
System.out.println(m.group(1));
}

如果不想定位到字符串内部的行起始位置,只关心整个字符串的起始位置,则可以使用 \A ,绝大多数工具中的正则表达式都支持这个锚点,它在任何情况下(包括多行模式下)都只匹配整个字符串的起始位置,如例:

//提取匹配整段文本的第一个单词
Stringtext="firstline\nsecondline\r\n\rlastline";
Patternp=Pattern.compile("(?m)\\A(\\w+)");
Matcherm=p.matcher(text);
while(m.find()){
System.out.println(m.group(1));
}

“行结束位置”的情况更复杂。除去“行终止符”可能由各种字符表示的情况之外,“行结束位置”可能没有任何字符,你猜猜下面文本有几个行终止符?

字符串:Some sample text

可能一:Some sample text

可能二:Some sample textNL

而且其中的NL可能是 \n,也可能是 \r\n。如果要匹配字符串最后一个单词,不但必须考虑NL所对应字符的多种可能,而且要兼顾NL是否出现情况更加复杂。针对这种问题,正则表达式提供了“通吃”行结束符的锚点$,它匹配的同样是位置。通常它匹配的是整个字符串的结尾位置——如果最后是行终止符,则匹配行终止符之前的位置;否则,匹配最后一个字符之后的位置。也就是说,上面两种可能,$它都可以匹配。如例:

//提取匹配每行的最后一个单词
Stringtext="firstline\nsecondline\r\n\rlastline";
Patternp=Pattern.compile("(?m)(\\w+)$");
Matcherm=p.matcher(text);
while(m.find()){
System.out.println(m.group(1));
}

如果指定了多行模式,$会匹配每个行终止符之前的位置。如果最后一行没有行终止符,则匹配字符串的结尾位置,就如上面这个例子。

与$类似的还有两个特殊标记 \Z\z ,它们不受多行模式的影响在任何情况下都匹配整个字符串的结束位置。\Z和\z的主要差别在于:\Z等价于默认模式(非多行模式)下的$,如果字符串的末尾有行终止符,则它匹配换行符之前的位置(也就是说不仅匹配换行符还能匹配什么也没有,与单行模式下的$一样);\z则不管行终止符,只匹配“整个字符串的结束位置”(也就是说不配置换行符)。示例如下:

//\Z与\z的使用
Stringtext1="firstline\nsecondline\r\n\rlastline";
Stringtext2="firstline\nsecondline\r\n\rlastKKK\r\n";
Patternp1=Pattern.compile("(\\w+)\\z");
Patternp2=Pattern.compile("(\\w+)\\Z");
Matcherm1=p1.matcher(text1);
Matcherm2=p2.matcher(text2);
if(m1.find()){
System.out.println(m1.group(1));
}
if(m2.find()){
System.out.println(m2.group(1));
}

接着说^和$的另一个特点:进行正则表达式替换时并不会被替换。也就是说,在起始/结束位置进行替换,只会在起始/结束位置添加一些字符,位置本身仍然存在。^和$的另一个常用功能是删去多余的空白,包括行首尾的空白和空行

环视

前面介绍过单词边界匹配的是这样的位置:一边是单词字符,另一边不是单词字符。从另一个角度来看,它能进行这样的判断:在某个位置向左/向右看,必须出现或不能出现某类字符。有时候,这种功能非常有用。

针对这种要求正则表达式专门提供了环视(look-around)用来“停在原地,四处张望”。环视类似单词边界,在它旁边的文本需要满足某种条件,而且本身不匹配任何字符。比如正则表达式 <(?!/),其中的 (?!/)是一个环视结构,(?!...)是这个结构的标识,/才是真正的表达式,整个结构的意思是“当前位置之后(右侧),不容许出现 / 能匹配的文本”。看起来它和 <[^/]类似,其实大不相同:如果<(?!/)匹配成功,正则表达式真正匹配完成的只有<,而不包含<之后的那个字符,这样,就能准确表示“匹配<,同时这个<之后不能是/”。

再来看表达式 (?<!/)>,其中的(?<!/)也是一个环视结构,(?<!...)是这个结构的标识,/才是真正的表达式,整个结构的意思是“在当前位置之前(左侧),不容许出现 / 能匹配的文本“(它与上面的(?!/)类似,只是多了一个<,更加形象地指向左侧)。这样,就能准确地表示“匹配>,同时>之前不能是/”。

上面已经出现两种环视:(?!...)和(?<!...),它们的名字分别是“否定顺序环视”和"否定逆序环视"。“否定”的意思是“如果正则表达式匹配成功,则在当前位置匹配失败”,而“顺序”和“逆序”则表示正则表达式需要匹配的文本所在的位置。所以总的来说,环视一共分为4种:肯定顺序环视,否定顺序环视,肯定逆序环视,否定逆序环视。见下表:

名字
记法
判断方向
结构内表达式匹配成功的返回值
肯定顺序环视
(?=...)
向右
True
否定顺序环视
(?!...)
向右
False
肯定逆序环视
(?<=...)
向左
True
否定逆序环视
(?<!...)
向左
False

这4个名字容易混淆,不妨这样记忆:在当前位置,如果是朝右判断,则是顺序环视,如果是朝左判断,同是逆序环视;如果要求子表达式能匹配的字符串必须出现,则为肯定环视,如果要求子表达式能匹配的字符串不能出现,则为否定环视。

正则指引之断言的更多相关文章

  1. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. ios – 使用大写符号在字符串swift中获取URL的正则表达式

    我尝试在文本中获取URL.所以,在此之前,我使用了这样一个表达式:但是当用户输入带有大写符号的URL时(例如Http://Google.com,它与它不匹配)我遇到了问题.我试过了:但什么都没发生.解决方法您可以使用正则表达式中的i内联标志关闭区分大小写,有关可用正则表达式功能的详细信息,请参阅FoundationFrameworkReference.(?ismwx-ismwx)Flagsetti

  3. ios – 如何在Swift 3中使用正则表达式?

    解决方法我相信.当没有其他选项适用时,将使用.allZeros.因此,使用Swift3,您可以传递一个空的选项列表或省略options参数,因为它默认为无选项:要么请注意,在Swift3中,您不再使用error参数.它现在抛出.

  4. ios – lldb断点在类目标c中的所有方法

    如何使用lldb在ObjectiveC类中的所有方法上自动设置断点?

  5. swift的正则表达式(NSRegularExpression)

    init(_pattern:String){varerror:NSError?

  6. swift 正则表达式运用实例选自《swifter 100个swift开发必备tip 》

  7. 可以匹配就匹配咯: 详解 Swift 的模式匹配

    怒戳查看最终稿@SwiftGG在众多Swift提供给Objective-C程序员使用的新特性中,有个特性把自己伪装成一个无聊的老头,但是却在如何优雅滴解决“鞭尸金字塔“的问题上有着巨大的潜力。很显然我所说的这个特性就是switch语句,对于很多Objective-C程序员来说,除了用在Duff’sDevice上比较有趣之外,switch语句非常笨拙,与多个if语句相比,它几乎没有任何优势。不过Sw

  8. Swift中的模式匹配

    模式是用于匹配的规则值,如switch语句的case,do语句的catch子句,以及if、while、guard、for-in语句的条件。例如,假设你想判断一个整数是大于、小于还是等于零,你可以用if-elseif-else语句,尽管这并不美观:letx=10ifx>0{print}elseifx原文查看:http://bbs.a-coder.cn/thread-7-1-1.html

  9. 《swift2.0 官方教程中文版》 第3章-05模式

    letpoint=(3,2)switchpoint{caselet(x,y):print}//prints"Thepointisat(3,2).”/*元组模式********************************************///元组模式是逗号分隔的,有零个或多个模式的列表,并被一对圆括号括起来。枚举用例模式出现在switch语句中的case标签中,以及if,while,guard和for-in语句的case条件中。is模式和is操作符有相似表现,它们都进行类型转换,却舍弃返回的类型

  10. 模式匹配第一弹: switch, enums &amp; where 子句

    本文作为模式匹配的第一篇介绍文章,旨在抛砖引玉。Switch基本用法Swift中最简单、最为常见的模式匹配就是switch语句,大家对下面的形式都比较熟悉了:但是switch可以更进一步,允许使用包含变量的匹配模式,并在匹配时绑定这些变量。下一部分的计划这篇文章很简单,带你回顾了swith中的一些基本的模式匹配,下一部分将探讨更高级的用法,包括:在enum之外的其他地方使用switch与其他语句一起配合使用模式匹配,包括ifcase,guardcase,forcase,=~,…

随机推荐

  1. 法国电话号码的正则表达式

    我正在尝试实施一个正则表达式,允许我检查一个号码是否是一个有效的法国电话号码.一定是这样的:要么:这是我实施的但是错了……

  2. 正则表达式 – perl分裂奇怪的行为

    PSperl是5.18.0问题是量词*允许零空间,你必须使用,这意味着1或更多.请注意,F和O之间的空间正好为零.

  3. 正则表达式 – 正则表达式大于和小于

    我想匹配以下任何一个字符:或=或=.这个似乎不起作用:[/]试试这个:它匹配可选地后跟=,或者只是=自身.

  4. 如何使用正则表达式用空格替换字符之间的短划线

    我想用正则表达式替换出现在带空格的字母之间的短划线.例如,用abcd替换ab-cd以下匹配字符–字符序列,但也替换字符[即ab-cd导致d,而不是abcd,因为我希望]我如何适应以上只能取代–部分?

  5. 正则表达式 – /bb | [^ b] {2} /它是如何工作的?

    有人可以解释一下吗?我在t-shirt上看到了这个:它似乎在说:“成为或不成为”怎么样?我好像没找到’e’?

  6. 正则表达式 – 在Scala中验证电子邮件一行

    在我的代码中添加简单的电子邮件验证,我创建了以下函数:这将传递像bob@testmymail.com这样的电子邮件和bobtestmymail.com之类的失败邮件,但是带有空格字符的邮件会漏掉,就像bob@testmymail也会返回true.我可能在这里很傻……当我测试你的正则表达式并且它正在捕捉简单的电子邮件时,我检查了你的代码并看到你正在使用findFirstIn.我相信这是你的问题.findFirstIn将跳转所有空格,直到它匹配字符串中任何位置的某个序列.我相信在你的情况下,最好使用unapp

  7. 正则表达式对小字符串的暴力

    在测试小字符串时,使用正则表达式会带来性能上的好处,还是会强制它们更快?不会通过检查给定字符串的字符是否在指定范围内比使用正则表达式更快来强制它们吗?

  8. 正则表达式 – 为什么`stoutest`不是有效的正则表达式?

    isthedelimiter,thenthematch-only-onceruleof?PATTERN?

  9. 正则表达式 – 替换..与.在R

    我怎样才能替换..我尝试过类似的东西:但它并不像我希望的那样有效.尝试添加fixed=T.

  10. 正则表达式 – 如何在字符串中的特定位置添加字符?

    我正在使用记事本,并希望使用正则表达式替换在字符串中的特定位置插入一个字符.例如,在每行的第6位插入一个逗号是什么意思?如果要在第六个字符后添加字符,请使用搜索和更换从技术上讲,这将用MatchGroup1替换每行的前6个字符,后跟逗号.

返回
顶部