【问题标题】:Perl: Matching string not containing PATTERNPerl:匹配字符串不包含 PATTERN
【发布时间】:2014-05-01 07:04:24
【问题描述】:

在使用 Perl 正则表达式将字符串切割成可用片段时,我需要匹配除特定模式之外的所有内容。在Perl Monks找到这个提示后我解决了:

/^(?:(?!PATTERN).)*$/;    # Matches strings not containing PATTERN

虽然我解决了我最初的问题,但我对它的实际工作原理知之甚少。我查了perlre,但是有点太正式了,没把握。

Regular expression to match a line that doesn't contain a word? 对理解有很大帮助,但为什么在我的示例中是 .?: 以及外括号是如何工作的?

有人可以分解正则表达式并用简单的语言解释它是如何工作的吗?

【问题讨论】:

标签: regex perl


【解决方案1】:

逐段构建(并且在整个过程中假设字符串或 PATTERN 中没有换行符):

这匹配任何字符串:

/^.*$/

但是我们不希望.匹配以PATTERN开头的字符,所以替换

.

(?!PATTERN).

这使用了一个否定的前瞻来测试一个给定的模式而不实际消耗任何字符串,并且只有当模式在字符串中的给定点不匹配时才会成功。所以就像说:

if PATTERN doesn't match at this point,
    match the next character

这需要对字符串中的每个字符都进行,因此* 用于匹配零次或多次,从字符串的开头到结尾。

要使* 应用于负前瞻和. 的组合,而不仅仅是.,它需要用括号括起来,因为没有理由捕获,它们应该是非捕获括号(?: ):

(?:(?!PATTERN).)*

并放回锚点以确保我们在字符串中的每个位置进行测试:

/^(?:(?!PATTERN).)*$/

请注意,此解决方案作为较大匹配的一部分特别有用;例如将任何字符串与foo 和更高版本的baz 匹配,但两者之间没有bar

/foo(?:(?!bar).)*baz/

如果没有这些考虑,你可以简单地这样做:

/^(?!.*PATTERN)/

检查 PATTERN 是否与字符串中的任何位置不匹配。

关于换行符:您的正则表达式和换行符存在两个问题。首先,. 不匹配换行符,所以 "foo\nbar" =~ /^(?:(?!baz).)*$/ 不匹配,即使字符串不包含 baz。您需要添加 /s 标志以使 . 匹配任何字符; "foo\nbar" =~ /^(?:(?!baz).)*$/s 正确匹配。其次,$ 不仅仅匹配字符串的末尾,它还可以匹配字符串末尾的换行符之前。所以"foo\n" =~ /^(?:(?!\s).)*$/s 确实匹配,即使字符串包含空格并且您试图只匹配没有空格的字符串; \z 总是只匹配最后,所以"foo\n" =~ /^(?:(?!\s).)*\z/s 正确地无法匹配实际上包含\s 的字符串。所以正确的通用正则表达式是:

/^(?:(?!PATTERN).)*\z/s

【讨论】:

  • 已经有点清楚了,今天晚些时候必须重新阅读。
  • "negative look-ahead" => 你的意思不是“反向”,而是“与前瞻不匹配”?这可以解释为什么我没有掌握这个概念。
  • "non-capturing (?:" 是您解决括号部分不用作替换正则表达式中的变量的方法。所以这是可选的,对吧?或者在尝试捕获零件时甚至可能是不好的做法。 (我想这是 Google-and-Paste 编程的缺点)。
  • 因为 (...)* 只会捕获 0 个或多个 * 匹配的实例中的最后一个,所以它很少有用; (?:...)* 非常普遍。使用(?: 始终是一个好习惯,除非您知道需要捕获。
  • 是的,反向称为look-behind,也可以是正向(?<=或负向(?<!
【解决方案2】:

jippie,首先,这里有一个提示。如果您看到一个对您来说不是很明显的正则表达式,您可以将其转储到解释每个标记的工具中。

例如,这里是 RegexBuddy 的输出:

"
^                # Assert position at the beginning of a line (at beginning of the string or after a line break character) (line feed)
(?:              # Match the regular expression below
   (?!              # Assert that it is impossible to match the regex below starting at this position (negative lookahead)
      PATTERN          # Match the character string “PATTERN” literally (case insensitive)
   )
   .                # Match any single character that is NOT a line break character (line feed)
)
   *                # Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
\$                # Assert position at the end of a line (at the end of the string or before a line break character) (line feed)
                    # Perl 5.18 allows a zero-length match at the position where the previous match ends.
                    # Perl 5.18 attempts the next match at the same position as the previous match if it was zero-length and may find a non-zero-length match at the same position.
"

有些人也使用 regex101。

人类解释

现在,如果我必须解释正则表达式,我就不会那么线性了。我首先要说它完全由^$ 锚定,这意味着唯一可能的匹配是整个字符串,而不是该字符串的子字符串。

然后我们来吃肉:(?: 介绍并由* 重复任意次数的非捕获组

这个小组是做什么的?它包含

  1. 否定前瞻(您可能想要read up on lookarounds here)断言在字符串中的这个确切位置,我们无法匹配单词PATTERN,
  2. 然后是一个点来匹配下一个字符

这意味着在字符串的每个位置,我们断言我们不能匹配 PATTERN,然后我们匹配下一个字符。

如果 PATTERN 可以在任何地方匹配,则否定前瞻失败,整个正则表达式也是如此。

【讨论】:

  • 非常好,谢谢!顺便说一句,regex101 是免费且在线的,regexbuddy 商业必须下载并且只有 windows 对吗?对于 linux,kodo 需要 qt3 可能是通过 ubuntu 12 在 vi​​rtualbox 上我想象的唯一方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-24
  • 2012-12-30
  • 2021-06-02
  • 1970-01-01
  • 1970-01-01
  • 2014-11-17
  • 2010-11-22
相关资源
最近更新 更多