正则表达式匹配只有部分例外的大写“单词”

59
我有以下技术字符串:
"The thing P1 must connect to the J236 thing in the Foo position."

我希望使用正则表达式匹配只有大写字母的单词(即这里的P1J236)。问题是当首字母是一个字母单词时,我不想匹配句子的第一个字母。

例如,在:

"A thing P1 must connect ..." 

我只想要P1,而不是AP1同时存在。通过这样做,我知道有可能会错过一些实际上要匹配的单词(例如在"X must connect to Y"中),但我可以接受这种情况。

此外,如果整个句子都是大写字母,我不希望匹配大写单词。

示例:

"THING P1 MUST CONNECT TO X2."

当然,理想情况下,我希望能够在这里匹配技术词汇P1X2,但由于它们隐藏在全大写的句子中,并且这些技术词汇没有特定的模式,因此这是不可能的。不过我可以接受这种情况,因为全大写的句子在我的文件中并不常见。

谢谢!


1
所有的技术术语都包含数字吗? - Jay
6
无论做什么,都不要使用像[A-Z]这样的7位文字。那很像RADIX-50,并且在过去几十年写的代码中没有用武之地。使用适用于任何文本的内容。至少这意味着在您的正则表达式语言和环境中使用与\w[[:alpha:]]\pL\p{Alphabetic}相关的东西。实际上,实现差异如此之大,以至于其中一些在某些平台上可能是合法和正确的,但在其他平台上则是合法而错误的。 - tchrist
6个回答

94
在一定程度上,这将取决于您使用的正则表达式“风格”。以下基于.NET RegEx,它使用\b表示单词边界。在最后一个示例中,它还使用负向先行断言(?<!)(?!)以及非捕获括号(?:)
基本上,如果术语始终包含至少一个大写字母后跟至少一个数字,您可以使用:
\b[A-Z]+[0-9]+\b

对于所有大写字母和数字(总数必须为2或更多):

\b[A-Z0-9]{2,}\b

针对全大写字母和数字,但是起始至少要有一个字母:

\b[A-Z][A-Z0-9]+\b

这个正则表达式的作用是匹配包含任意大写字母和数字组合的项,但不匹配行首的单个字母,也不匹配全部为大写字母的行中的项:

(?:(?<!^)[A-Z]\b|(?<!^[A-Z0-9 ]*)\b[A-Z0-9]+\b(?![A-Z0-9 ]$))

细节解析:

这个正则表达式以(?:开始。 ?:表示虽然后面内容被放在了括号里,但我不想捕获结果。这称为“非捕获性括号”。 在这里,我使用括号是因为我使用了"或"(见下文)。

在非捕获性括号内,我有两个由管道符号|分隔的单独子句。 这就是“交替”--类似于“或者”的意思。 正则表达式可以匹配第一个表达式或第二个表达式。这两种情况是“这是行的第一个单词”或“其他所有情况”,因为我们有一个特殊要求,即在行开头排除一个字母的单词。

现在,让我们看看交替中的每个表达式。

第一个表达式是:(?<!^)[A-Z]\b。 这里的主要子句是[A-Z]\b,它是任何一个大写字母后跟一个单词边界,可以是标点符号、空格、换行符等。在那之前的部分是(?<!^),这是一个"负向后查找"。这是一个零宽断言,这意味着它不会“消耗”字符作为匹配的一部分--在这里并不重要。.NET中负向后查找的语法是(?<!x),其中x是必须不存在于主子句之前的表达式。 这里的表达式只是^,即行首,因此交替边上的内容翻译为“任何由单个大写字母组成的单词,而且该单词不再行开头。”

好的,所以我们正在匹配不在行首的大小写字母单词。 我们仍然需要匹配由所有数字和大写字母组成的单词。

这由交替中第二个表达式的相对较小部分处理:\b[A-Z0-9]+\b\b代表单词边界,而[A-Z0-9]+匹配一个或多个数字和大写字母放在一起。

表达式的其余部分由其他查找构成。 (?<!^[A-Z0-9 ]*)是另一个负向后查找,其中表达式为^[A-Z0-9 ]*。 这表示前面的内容不能都是大写字母和数字。

第二个查找是(?![A-Z0-9 ]$),它是一个负向前瞻。这表示后面的内容不能全部是大写字母和数字。

所以总体来说,我们捕获由所有大写字母和数字组成的单词,并从行开头排除一个字母的单词和全部大写字母的行中的所有内容。

这里至少有一个弱点,在交替表达式的第二部分中的查找是独立的,因此像“A P1 should connect to the J9”这样的语句将匹配J9,但不会匹配P1,因为P1之前的所有内容都是大写的。

可以解决这个问题,但它几乎会使正则


@Patrick 就像我说的那样,它会因 RegEx 风格而异,而我不知道你在使用什么。并非每种类型的 RegEx 使用相同的符号,也不是每种类型都支持相同的功能。所给出的示例基于 .NET Regex;很抱歉它对你不起作用。 - Jay
Jay,能请你解释一下你的“granddaddy”的不同部分吗?我正在尝试理解它以适应我的PCRE风格。再次感谢! - Patrick
我的天啊,如果我能投票,你就是第一名!非常感谢Jay,真的非常感激。 - Patrick
+1 对你回答这个问题所付出的努力表示赞赏,正则表达式本来就很难读懂,而你的第四个例子简直令人惊叹。 - Rand Random
如果这不起作用,你可能正在使用一种REGEX方言,在这种情况下,你应该_1._使用´(´和\)代替(:?) _2._使用\<表示单词的开头和\>表示单词边界,而不是使用\b表示单词边界。 - Dirk Horsten
显示剩余9条评论

7
也许你可以先运行这个正则表达式,看看这一行是否全部大写:
^[A-Z \d\W]+$

只有当一行像THING P1 MUST CONNECT TO X2.这样时才会匹配。

否则,您可以使用以下方法提取单个大写短语:

[A-Z][A-Z\d]+

这应该匹配在The thing P1 must connect to the J236 thing in the Foo position.中的"P1"和"J236"。


在全大写检查中,我认为空格属于\W,然后添加_并假设在空字符串上不需要进一步检查,它可以推广为/^[A-Z\d\W_]*$/ - user557597

6
不要使用类似 [A-Z] 或 [0-9] 的表示法,应该使用 \p{Lu} 和 \d。当然,这仅适用于基于 Perl 的正则表达式语法。这也包括 Java。
建议不要编写过于复杂的正则表达式。先将文本分成句子,然后对其进行标记化(拆分为单词)。使用正则表达式检查每个标记/单词。跳过句子中的第一个标记。如果所有标记都是大写字母,则跳过整个句子,或在这种情况下修改正则表达式。

5

为什么需要在一个巨大的正则表达式中完成这些操作?实际上,您可以使用实际的代码来实现其中一些规则,这样做将更容易修改,以便在后期更改这些要求。

例如:

if(/^[A-Z0-9\s]*$/)
    # sentence is all uppercase, so just fail out
    return 0;

# Carry on with matching uppercase terms

实际上,我有一组正则表达式存储在MySQL表中,我的PHP代码按顺序执行所有这些preg_replace()。这就是为什么我不想通过添加if语句来增加复杂性。当然,如果没有其他办法,我可能会改变主意... - Patrick
1
好问题。逻辑存储在数据库中,因为最终是用户的责任通过网络表单输入正则表达式来应用到特定的文本上。我的程序循环遍历这些正则表达式并返回匹配结果。 - Patrick

3

我并不是一个正则表达式方面的专家。但是可以尝试以下代码:

<[A-Z0-9][A-Z0-9]+>

<           start of word
[A-Z0-9]    one character
[A-Z0-9]+   and one or more of them
>           end of word

我不会试图获得整个大写句子的奖励分数。 呵呵


2
对于您提出的第一个案例,您可以使用:'[[:blank:]]+[A-Z0-9]+[[:blank:]]+',例如:
echo "The thing P1 must connect to the J236 thing in the Foo position" | grep -oE '[[:blank:]]+[A-Z0-9]+[[:blank:]]+'
在第二种情况下,也许您需要使用其他东西而不是正则表达式,也许是具有技术词汇字典的脚本...
祝好,Fernando

我点赞这个想法是因为使用技术术语字典的想法。由于原帖已经在其他评论中确认了数据库的可用性,因此使用这种信息来查找有趣的术语似乎比基于不完美约定的识别更有意义。 - Zac Thompson
虽然数据库是可用的,但我也提到技术词汇没有特定的模式。 - Patrick

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