如何扫描字符串文字,允许转义字符?

5
我想解析一个输入字符串,并确定它是否包含由双引号 (") 包围的字符序列。该字符序列本身不允许包含进一步的双引号,除非它们被反斜杠转义,如下所示: \"
为了使事情更加复杂,反斜杠本身可以被转义,如下所示: \\。因此,由两个(或任何偶数个)反斜杠 (\\") 前缀的双引号不会被转义。更糟糕的是,允许单个非转义反斜杠 (即后面既不跟 " 也不跟 \\ 的反斜杠)。
我正在尝试使用 Python 的 re 模块来解决这个问题。 模块文档 告诉我们有一个管道运算符 A|B:
在扫描目标字符串时,从左到右尝试使用由'|'分隔的正则表达式。当一个模式完全匹配时,该分支将被接受。这意味着一旦A匹配成功,即使它可以产生更长的整体匹配,B也不会进一步测试。换句话说,'|'运算符从不贪婪。
然而,这并不像我预期的那样工作:
>>> import re
>>> re.match(r'"(\\[\\"]|[^"])*"', r'"a\"')
<_sre.SRE_Match object; span=(0, 4), match='"a\\"'>

这个正则表达式的想法是首先检查转义字符(\\\"),只有在找不到它时,才检查任何不是"的字符(但它可以是单个\)。这可能会发生任意次数,并且必须用字面上的"字符括起来。
我希望字符串"a\"根本不匹配,但显然它确实匹配了。我希望\"匹配A部分,而B部分不被测试,但显然它确实被测试了。
我真的不知道回溯在这种情况下如何工作,但是否有一种避免它的方法?
我猜如果我首先在单独的步骤中检查初始的"字符(并从输入中删除它),那么它将起作用。然后,我可以使用以下正则表达式获取字符串的内容:
>>> re.match(r'(\\[\\"]|[^"])*', r'a\"')
<_sre.SRE_Match object; span=(0, 3), match='a\\"'>

这将包括转义引号。由于不会剩下一个闭合引号,我会知道总体上给定的字符串不匹配。

我必须这样做吗?还是可能用单个正则表达式解决这个问题,而不需要额外的手动检查?

在我的实际应用中,“”括起来的字符串只是更大模式的一部分,因此我认为在单个正则表达式中一次完成所有操作会更简单。

我找到了类似的问题,但是它们没有考虑到单个非转义反斜杠可以是字符串的一部分:使用正则表达式解析带有转义字符的字符串, 使用正则表达式解析转义字符


看这个:"(?:[^\\"]|\\.)*" --> "[^"\\]*(?:\\.[^"\\]*)*" - Wiktor Stribiżew
谢谢,非常有效!箭头是什么意思?其中一个选择比另一个更好吗?它们完全等效吗? - Matthias
是的,有所不同。好的,既然这对您有用,那我就把它作为答案。 - Wiktor Stribiżew
1个回答

6
当您使用"(\\[\\"]|[^"])*"时,您匹配的是"后跟0个或多个序列\,后跟一个\",或非"字符,然后再跟一个“结束”"。请注意,当您的输入为"a\"时,\被第二个替代分支[^"]排除(因为反斜杠是有效的非"字符)。
您需要从非"字符中排除\
"(?:[^\\"]|\\.)*"
      ^^

所以,我们匹配 ",然后是非"和非\(使用[^\\"]),或者任何转义序列(使用\\.),0次或多次。
然而,这个正则表达式不够高效,因为有很多回溯操作(由于备选项和量词引起)。展开版本如下:
"[^"\\]*(?:\\.[^"\\]*)*"

请查看正则表达式演示 最后一个模式匹配:
  • " - 双引号
  • [^"\\]* - 除了 \" 外的零个或多个字符
  • (?:\\.[^"\\]*)* - 零个或多个序列
    • \\. - 反斜杠后跟任何非换行符的字符
    • [^"\\]* - 除了 \" 外的零个或多个字符
  • " - 双引号

这绝对是正确的方法,但我仍然想知道为什么我的原始表达式匹配“\”而不是\",后者是A(在|的左侧)。文档说:“一旦A匹配,将不会进一步测试B”。为什么在这种情况下B匹配呢? - Matthias
2
答案是:回溯使得[^"]可以匹配\而不是\"。你的\\["\\]是第一个,而在"a之后通过2次[^"]匹配到\"被第一个替代项捕获。但是,最后的"必须的,这意味着正则表达式引擎将尝试重新分组已经找到的捕获值以适应一些位置来放置"。引擎回溯,并找到了一个在第二个替代分支[^"]中可以与"匹配的\。请参见您的正则表达式演示,在正则表达式调试器页面上的步骤12、13。 - Wiktor Stribiżew
1
谢谢解释,现在我觉得我明白了!我对这个正则表达式的东西很新,我不知道正则表达式调试器,它是一个非常好的工具,可以帮助理解这些正则表达式的工作原理。 - Matthias
1
太好了,我能帮上忙。回溯在正则表达式中既有利又有弊,你很快就会意识到这一点。不幸的是,re Python模块不支持占有量词,也不支持原子组(但你可以使用PyPi regex 模块来使用它们),因此,你可以通过以下方式来避免这种情况,例如"(\[\\"]|[^"])*+"。另一种方法是通过在正向前瞻中捕获/引用:"(?:(?=(\[\\"]|[^"]))\1)*"(即使在Python re 中也可以使用)。 - Wiktor Stribiżew

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