正则表达式匹配所有单词,但排除引号之间的内容。

3
在这个例子中,我想选择除了引号内的单词之外的所有单词(即"results"、"items"、"packages"、"settings"和"build_type",但不包括"compiler.version")。
results[0].items[0].packages[0].settings["compiler.version"] 
results[0].items[0].packages[0].settings.build_type

这是我所知道的:我可以针对所有单词使用目标。
[a-z_]+

然后使用以下方法来定位引号之间的内容:

(?<=\")[\w.]+(?=\")

有没有办法匹配第一个和第二个正则表达式结果之间的差异? (即除非它们被双引号包围,否则为单词)。 这里是一个正则表达式游乐场,方便查看示例。
3个回答

4

您可以在双引号之间匹配字符串,然后匹配并捕获单词,可选择跟随用点分隔的单词:

list(filter(None, re.findall(r'"[^"]*"|([a-z_]\w*(?:\.[a-z_]\w*)*)', text, re.ASCII | re.I)))

请查看正则表达式演示细节:

  • "[^"]*" - 一个"字符,零个或多个非"字符,然后再是一个"字符
  • | - 或者
  • ([a-z_]\w*(?:\.[a-z_]\w*)*) - 第一组: 以字母或下划线开头,后跟零个或多个单词字符, 然后是零个或多个由.和字母或下划线开头的零个或多个单词字符的序列.

请查看Python代码演示:

import re
text = 'results[0].items[0].packages[0].settings["compiler.version"] '
print(list(filter(None, re.findall(r'"[^"]*"|([a-z_]\w*(?:\.[a-z_]\w*)*)', text, re.ASCII | re.I))))
# => ['results', 'items', 'packages', 'settings']
re.ASCII选项用于使\w匹配[a-zA-Z0-9_],但不考虑Unicode字符。

读者们:Wiktor使用的技巧已被rexegg.com背后的人(“Rex”)称为有史以来最伟大的正则表达式技巧。他对这种技术的解释非常好,但我警告你,在得出结论之前,他会讲述成千上万个单词,而结论就是“技巧在于我们在交替符(|)的左侧匹配我们不想要的内容,然后在右侧捕获我们想要的内容。” - Cary Swoveland

3

如果一个单词在字符串中的后面跟随偶数个双引号(假设该字符串已正确格式化并因此包含偶数个双引号),则该单词不在双引号子字符串中。您可以使用以下正则表达式匹配不包含在双引号子字符串中的字符串。

[a-z_]+(?=(?:(?:[^\"\n]*\"){2})*[^\"\n]*$)

演示

该正则表达式可拆分为以下部分(或者,将鼠标悬停在链接中的每个表达式部分上以获取其功能的解释)。

[a-z_]+         # match one or more of the indicated characters
(?=             # begin a positive lookahead
  (?:           # begin an outer non-capture group
    (?:         # begin an inner non-capture group
      [^\"\n]*  # match zero or more characters other than " and \n 
      \"        # match "
    ){2}        # end inner non-capture group and execute twice
  )*            # end outer non-capture group and execute zero or more times
  [^\"\n]*      # match zero or more characters other than " and \n 
  $             # match end of string
)               # end positive lookahead

为什么当我遍历一个单行字符串时,它没有选择任何内容?这是我的意思:https://regex101.com/r/TcRpXn/1 尝试通过删除最后的 \n 更改公式,但也没有帮助。谢谢! - jlo
找到了一种方法。对于只需要处理一个没有任何 '\n' 的行/字符串的人,您可以使用此 [a-z_]+(?=(?:(?:[^\"]*\"){2})*[^\"]*$)/gi 公式(基本上与上面相同,只是将最后一个 \n 替换为 $)。演示:https://regex101.com/r/loiAiA/2 - jlo
jlo,干得好。我已经编辑过了。在我的原始答案中,我说:“如果最后一行可能没有行终止符,则\n应该被替换为(?:\n|$)”,但我发现$就足够了。 - Cary Swoveland
@jlo 注意 (?=(?:(?:[^\"]*\"){2})*[^\"]*$) 是第二个最糟糕的正则表达式结构(最糟糕的是 (?:\s|.)*? / (?:\n|.)* 等等),因为它非常低效,会导致减速和堆栈溢出问题。请自行决定是否使用,并仅用于短字符串。 - Wiktor Stribiżew
根据@Wiktor的评论,我将示例中的两行转换为1,000个交替行,并在Ruby中针对它们运行了String#scan正则表达式。对于我的正则表达式,大约需要0.03秒,而对于他的正则表达式,则需要大约0.005秒... - Cary Swoveland
Wiktor的前两行匹配是[["results"], ["items"], ["packages"], ["settings"], [nil], ["results"], ["items"], ["packages"], ["settings.build_type"]]。我没有在基准测试中包括他执行的额外步骤,但我不认为这些步骤的时间会很长。这显然是一个非常简单的基准测试,但它确实表明我的方案是比赛中的乌龟,但最终它将到达终点。 - Cary Swoveland

-1

这里有一个更简单的版本,可以与您提供的示例一起使用。

(?<!\")\b[a-z_]+\b(?!\")

这里有一个演示

编辑:对于您提供的示例,这确实有效。然而,它有一些缺陷,因为它只避免匹配与"接触的单词。因此,如果引号内有多个单词,它将匹配任何不与"接触的内部单词。

正在努力改进这个解决方案,如果有新的更新,将编辑此帖子。


1
这与results[0].settings["compiler.version.two"]中的version匹配。 - Cary Swoveland
1
没错,参见证明 - Ryszard Czech

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