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

217

我试图使用正则表达式来匹配空格分隔的数字。 我找不到\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

你能否提供一个带有输入和期望输出的小例子? - Brent Writes Code
示例: 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 - peter.murray.rust
13个回答

160

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

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


54
没问题。\b是一种零宽断言,如果一侧有\w,而另一侧有\W或者位置在字符串的开头或结尾,则匹配成功。\w被任意定义为“标识符”字符(字母数字和下划线),并不是什么特别有用的英语字符。 - hobbs
1
抱歉没有只在您的回答上做评论。我在提交之前看不到你的答案。 - Brent Writes Code
6
为了更好地理解,是否可以重写正则表达式\bhello\b而不使用\b(使用\w\W和其他)? - David Portabella
7
类似于正则表达式 (^|\W)hello($|\W),但它不会在前后捕获任何非单词字符,因此更像是使用先行/后行断言的 (^|(?<=\W))hello($|(?=\W)) - brianary
11
稍微简化一下:(?<!\w)hello(?!\w)。该表达式的意思是匹配单词 "hello",但不包括其他单词中包含 "hello" 的情况。 - David Knipe
显示剩余3条评论

77

在学习正则表达式的过程中,我真的被元字符\b卡住了。当我一遍又一遍地问自己“它是什么,它是什么”时,我确实没有理解它的含义。经过使用这个网站进行尝试后,我注意到了每个单词开头和结尾处的粉色垂直线条。那时候我完全明白了它的意思。现在它确切的含义是单词(\w)边界

我的看法仅仅是极度注重理解。其背后的逻辑应从其他答案中进行检验。

enter image description here


7
了解单词边界及匹配方式的好网站。 - vsingh
16
这篇帖子值得称赞,因为它以图像代替文字来表现,一张图片胜过千言万语。 - M_M

38

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

  1. 如果第一个字符是一个单词字符,则可以出现在字符串中的第一个字符之前。
  2. 如果最后一个字符是一个单词字符,则可以出现在字符串中的最后一个字符之后。
  3. 在字符串中的两个字符之间,其中一个是单词字符,而另一个不是单词字符。

单词字符是字母数字字符;减号不是。 摘自正则表达式教程


快速示例:考虑文本this is a bad c+a+t,如果模式为\ba,则它将匹配此处的 a bad c+a+t。 - maq

28
我想解释一下Alan Moore的答案
一个单词边界是一个位置,它要么是前面有一个单词字符并且后面没有一个,要么是后面有一个单词字符并且前面没有一个。
假设我有一个字符串"This is a cat, and she's awesome",我想替换所有仅在单词边界上存在字母'a'的出现次数,
换句话说:单词'cat'内的字母a不应该被替换。 因此,我将执行正则表达式(在Python中): re.sub(r"\ba","e", myString.strip()) //用e替换a 因此,

输入; 输出

This is a cat and she's awesome

This is e cat end she's ewesome


17

一个单词边界是指在单词字符前面且紧随其后没有其他单词字符的位置,或在单词字符后面且前面没有其他单词字符的位置。


9
即使过了多年,我读答案时仍感觉自己像是在解谜题的人,这种感觉只有我有吗? - Soner from The Ottoman Empire
@snr 请参考此链接:https://dev59.com/33M_5IYBdhLWcg3wjj-r#54629773 :) - Daksh Gargas
@DakshGargas 他不应该引发一个新的帖子来澄清复杂的问题。 - Soner from The Ottoman Empire
3
我当时写这个的时候正在经历极简主义阶段。 - Alan Moore

11

我在这里讲述了 \b 类型的正则表达式边界是什么

简而言之,它们是有条件的。 它们的行为取决于它们旁边的内容。

# 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的Unicode(我相信当时有一个很好的理由)。 \w代表"word character",它总是匹配ASCII字符[A-Za-z0-9_],注意下划线和数字(但不包括破折号!)。在大多数支持Unicode的正则表达式中,\w包括许多其他脚本中的字符。有很多关于哪些字符实际上被包括在内的不一致性。通常都包括字母脚本和表意文字中的字母和数字。连接符标点符号,除了下划线和不是数字的数字符号,可能被包括或不被包括。XML模式和XPath甚至包括\w中的所有符号。但是Java、JavaScript和PCRE只匹配带ASCII字符的\w
这就是为什么在基于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#”,但现在已经更加清晰了。 - Mugoma J. Okomba

4

请查看边界条件的文档:

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")));
    }

当你将其打印出来时,请注意输出结果如下:
[I found the value -, in my string.]
这意味着"-"字符未被视为单词字符,因此未被视为单词边界。看起来@brianary已经提前完成了我的工作,所以他得到了一个赞。

4

参考资料:《精通正则表达式》(Jeffrey E.F. Friedl)- O'Reilly

\b相当于(?<!\w)(?=\w)|(?<=\w)(?!\w)


这是一个很好的解释,让人明白如何仅获取它的“单词开头”或“单词结尾”部分(但不是两者都包括)。 - jlh

2

单词边界 \b 用于将一个单词字符与另一个非单词字符分隔开来。 负数的正则表达式应该是

--?\b\d+\b

检验工作 演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接