什么是正则表达式中的单词边界?

Posted

技术标签:

【中文标题】什么是正则表达式中的单词边界?【英文标题】:What is a word boundary in regex? 【发布时间】:2020-02-07 17:37:15 【问题描述】:

我正在尝试使用正则表达式来匹配以空格分隔的数字。 我找不到\b(“单词边界”)的精确定义。 我曾假设-12 将是一个“整数字”(与\b\-?\d+\b 匹配),但似乎这不起作用。我会很感激知道的方式。

[我在 Java 1.6 中使用 Java 正则表达式]

例子:

Pattern pattern = Pattern.compile("\\s*\\b\\-?\\d+\\s*");
String plus = " 12 ";
System.out.println(""+pattern.matcher(plus).matches());

String minus = " -12 ";
System.out.println(""+pattern.matcher(minus).matches());

pattern = Pattern.compile("\\s*\\-?\\d+\\s*");
System.out.println(""+pattern.matcher(minus).matches());

这会返回:

true
false
true

【问题讨论】:

你能发布一个带有输入和预期输出的小例子吗? 示例模式 pattern = Pattern.compile("\\s*\\b\\-?\\d+\\s*");字符串加=“12”; System.out.println(""+pattern.matcher(plus).matches());字符串减号 = " -12 "; System.out.println(""+pattern.matcher(减号).matches()); pattern = Pattern.compile("\\s*\\-?\\d+\\s*"); System.out.println(""+pattern.matcher(减号).matches());给出:真假真 【参考方案1】:

在大多数正则表达式方言中,单词边界是\w\W(非单词字符)之间的位置,或者如果字符串以单词开头或结尾(分别)位于字符串的开头或结尾字符 ([0-9A-Za-z_])。

因此,在字符串"-12" 中,它将匹配1 之前或2 之后。破折号不是单词字符。

【讨论】:

更正。 \b 是一个零宽度断言,如果一侧有\w,另一侧有\W,或者位置是字符串的开头或结尾,则匹配。 \w 被任意定义为“标识符”字符(alnums 和下划线),而不是对英语特别有用的任何东西。 100% 正确。道歉不只是评论你的。在看到您的答案之前,我点击了提交。 为了便于理解,是否可以不使用\b(使用\w\W等)重写正则表达式\bhello\b 排序为:(^|\W)hello($|\W),只是它不会在前后捕获任何非单词字符,所以它更像(^|(?<=\W))hello($|(?=\W))(使用前瞻/后瞻断言)。跨度> @brianary 稍微简单一点:(?<!\w)hello(?!\w).【参考方案2】:

在学习正则表达式的过程中,我真的被\b这个元字符卡住了。当我反复问自己“它是什么,它是什么”时,我确实没有理解它的含义。在使用the website 进行了一些尝试后,我注意到单词开头和结尾处的粉红色垂直破折号。我当时就很好理解了它的含义。现在正好是word(\w)-boundary

我的观点仅仅是以极大的理解为导向。其背后的逻辑应该从另一个答案中检查。

【讨论】:

一个非常好的网站,可以了解什么是单词边界以及匹配是如何发生的 这篇文章值得称赞的是展示而不是讲述。一张图片胜过一千个字。 太棒了,其他问题我真的没看懂,看了你的回答我一下子就明白了,这个答案值得称赞【参考方案3】:

单词边界可以出现在三个位置之一:

    如果第一个字符是单词字符,则在字符串的第一个字符之前。 在字符串的最后一个字符之后,如果最后一个字符是单词字符。 字符串中两个字符之间,一个是单词字符,另一个不是单词字符。

单词字符是字母数字;减号不是。 取自Regex Tutorial。

【讨论】:

Quicl 示例:考虑文本this is a bad c+a+t,如果模式是\ba,那么它将匹配这是a bad c+a+t【参考方案4】:

我想解释一下Alan Moore's answer

单词边界是指前面有一个单词字符但后面没有一个,或者后面有一个单词字符但前面没有一个的位置。

假设我有一个字符串“This is a cat, and she's awesome”,我想替换所有出现的仅当此字母 ('a') 存在于“单词边界”,

换句话说:'cat' 中的字母 a 应该被替换。

所以我将执行正则表达式(Python)作为

re.sub(r"\ba","e", myString.strip()) //将a替换为e

所以输出将是

这是a cat,她是a好人->

This is e cat end she's ewesome //Result

【讨论】:

【参考方案5】:

单词边界是指前面有一个单词字符但后面没有一个,或者后面有一个单词字符但前面没有一个的位置。

【讨论】:

我是否只是那种在阅读答案时想解谜的人,即使是在多年之后? @snr 请参考这个:***.com/a/54629773/8164116 :) @DakshGargas 他不应该产生一个新职位来理顺复杂的职位。 写这篇文章时,我正处于极简主义阶段。【参考方案6】:

我谈论的是\b 风格的正则表达式边界实际上是here。

简而言之,它们是有条件的。他们的行为取决于他们身边的事物。

# same as using a \b before:
(?(?=\w) (?<!\w)  | (?<!\W) )

# same as using a \b after:
(?(?<=\w) (?!\w)  | (?!\W)  )

有时这不是你想要的。请参阅我的其他答案以进行详细说明。

【讨论】:

【参考方案7】:

在文本中搜索 .NETC++C#C 等词时,我遇到了更严重的问题。你会认为计算机程序员会比给一种难以编写正则表达式的语言命名更好。

无论如何,这就是我发现的(主要来自 http://www.regular-expressions.info 的总结,这是一个很棒的网站):在大多数正则表达式中,与简写字符类 \w 匹配的字符是被单词边界视为单词字符。 Java 是个例外。 Java 支持\b 的Unicode,但不支持\w。 (我确信当时有充分的理由)。

\w 代表“单词字符”。它始终匹配 ASCII 字符 [A-Za-z0-9_]。注意包含下划线和数字(但不是破折号!)。在大多数支持 Unicode 的风格中,\w 包含许多来自其他脚本的字符。关于实际包含哪些字符存在很多不一致之处。通常包括来自字母脚本和表意文字的字母和数字。除了下划线和不是数字的数字符号之外的连接标点符号可能包含也可能不包含。 XML Schema 和 XPath 甚至包括\w 中的所有符号。但是 Java、javascript 和 PCRE 仅匹配带有 \w 的 ASCII 字符。

这就是为什么基于 Java 的正则表达式搜索 C++C#.NET(即使您记得避开句号和加号)被 \b 搞砸了。

注意:我不确定如何处理文本中的错误,例如有人在句末的句号后没有加空格。我允许这样做,但我不确定这样做是否一定是正确的。

无论如何,在 Java 中,如果您正在搜索那些名称怪异的语言的文本,您需要将 \b 替换为前后空格和标点符号。例如:

public static String grep(String regexp, String multiLineStringToSearch) 
    String result = "";
    String[] lines = multiLineStringToSearch.split("\\n");
    Pattern pattern = Pattern.compile(regexp);
    for (String line : lines) 
        Matcher matcher = pattern.matcher(line);
        if (matcher.find()) 
            result = result + "\n" + line;
        
    
    return result.trim();

然后在你的测试或主函数中:

    String beforeWord = "(\\s|\\.|\\,|\\!|\\?|\\(|\\)|\\'|\\\"|^)";   
    String afterWord =  "(\\s|\\.|\\,|\\!|\\?|\\(|\\)|\\'|\\\"|$)";
    text = "Programming in C, (C++) C#, Java, and .NET.";
    System.out.println("text="+text);
    // Here is where Java word boundaries do not work correctly on "cutesy" computer language names.  
    System.out.println("Bad word boundary can't find because of Java: grep with word boundary for .NET="+ grep("\\b\\.NET\\b", text));
    System.out.println("Should find: grep exactly for .NET="+ grep(beforeWord+"\\.NET"+afterWord, text));
    System.out.println("Bad word boundary can't find because of Java: grep with word boundary for C#="+ grep("\\bC#\\b", text));
    System.out.println("Should find: grep exactly for C#="+ grep("C#"+afterWord, text));
    System.out.println("Bad word boundary can't find because of Java:grep with word boundary for C++="+ grep("\\bC\\+\\+\\b", text));
    System.out.println("Should find: grep exactly for C++="+ grep(beforeWord+"C\\+\\+"+afterWord, text));

    System.out.println("Should find: grep with word boundary for Java="+ grep("\\bJava\\b", text));
    System.out.println("Should find: grep for case-insensitive java="+ grep("?i)\\bjava\\b", text));
    System.out.println("Should find: grep with word boundary for C="+ grep("\\bC\\b", text));  // Works Ok for this example, but see below
    // Because of the stupid too-short cutsey name, searches find stuff it shouldn't.
    text = "Worked on C&O (Chesapeake and Ohio) Canal when I was younger; more recently developed in Lisp.";
    System.out.println("text="+text);
    System.out.println("Bad word boundary because of C name: grep with word boundary for C="+ grep("\\bC\\b", text));
    System.out.println("Should be blank: grep exactly for C="+ grep(beforeWord+"C"+afterWord, text));
    // Make sure the first and last cases work OK.

    text = "C is a language that should have been named differently.";
    System.out.println("text="+text);
    System.out.println("grep exactly for C="+ grep(beforeWord+"C"+afterWord, text));

    text = "One language that should have been named differently is C";
    System.out.println("text="+text);
    System.out.println("grep exactly for C="+ grep(beforeWord+"C"+afterWord, text));

    //Make sure we don't get false positives
    text = "The letter 'c' can be hard as in Cat, or soft as in Cindy. Computer languages should not require disambiguation (e.g. Ruby, Python vs. Fortran, Hadoop)";
    System.out.println("text="+text);
    System.out.println("Should be blank: grep exactly for C="+ grep(beforeWord+"C"+afterWord, text));

附:感谢http://regexpal.com/,没有他,正则表达式的世界将会非常悲惨!

【讨论】:

我很难理解为什么我无法匹配 C# 但现在更清楚了【参考方案8】:

查看有关边界条件的文档:

http://java.sun.com/docs/books/tutorial/essential/regex/bounds.html

查看此示例:

public static void main(final String[] args)
    
        String x = "I found the value -12 in my string.";
        System.err.println(Arrays.toString(x.split("\\b-?\\d+\\b")));
    

当你打印出来的时候,注意输出是这样的:

[我在我的字符串中找到了值 -。]

这意味着“-”字符没有被拾取为位于单词的边界,因为它不被视为单词字符。看起来@brianary 有点击败我,所以他得到了支持。

【讨论】:

【参考方案9】:

参考:掌握正则表达式 (Jeffrey E.F. Friedl) - O'Reilly

\b 等价于(?&lt;!\w)(?=\w)|(?&lt;=\w)(?!\w)

【讨论】:

这是一个很好的解释,它清楚地表明了如何只获取其中的“词首”或“词尾”部分(但不能同时获取两者)。【参考方案10】:

单词边界 \b 用于一个单词应该是单词字符而另一个单词应该是非单词字符的地方。 负数的正则表达式应该是

--?\b\d+\b

检查工作DEMO

【讨论】:

【参考方案11】:

我相信您的问题是由于- 不是单词字符。因此,单词边界将在- 之后匹配,因此不会捕获它。单词边界匹配字符串中第一个单词字符之前和最后一个单词字符之后,以及在它之前是单词字符或非单词字符的任何位置,而在它之后则相反。另请注意,单词边界是零宽度匹配。

一种可能的选择是

(?:(?:^|\s)-?)\d+\b

这将匹配以空格字符和可选破折号开头并以单词边界结尾的任何数字。它还将匹配从字符串开头开始的数字。

【讨论】:

【参考方案12】:

当您使用\\b(\\w+)+\\b 时,这意味着与仅包含单词字符([a-zA-Z0-9]) 的单词完全匹配

在您的情况下,例如在正则表达式的开头设置\\b 将接受-12(带空格)但同样不会接受-12(不带空格)

供参考支持我的话:https://docs.oracle.com/javase/tutorial/essential/regex/bounds.html

【讨论】:

【参考方案13】:

我认为它是最后一个匹配的边界(即字符后面)或字符串的开头或结尾。

【讨论】:

您正在考虑\G:在第一次匹配尝试时匹配字符串的开头(如\A);之后它匹配上一场比赛结束的位置。

以上是关于什么是正则表达式中的单词边界?的主要内容,如果未能解决你的问题,请参考以下文章

re 模块中的正则表达式是不是支持单词边界 (\b)?

utf-8中的php正则表达式单词边界匹配

正则表达式——06位置匹配

Java正则表达式-替换表达式直至单词边界

正则表达式的与B总结

正则表达式——7.4 单词边界