1

如果表达式很复杂,请解释为什么表达式有意义。

4

3 回答 3

1

让我们探索如何建立一个排除特定短语的模式。

我们将从一个简单的 开始.*,它匹配任何字符(使用),零次或多次(号)。此模式将匹配任何字符串,包括空字符串1

但是,由于存在我们不想匹配的特定短语我们可以尝试使用否定环视来阻止它匹配我们不想要的内容。环顾是一个零宽度的断言,这意味着正则表达式引擎需要满足断言才能有匹配,但断言不消耗任何字符(或者换句话说,它不会在细绳)。在这种特定情况下,我们将使用前瞻,它告诉正则表达式引擎向前看当前位置以匹配断言(也有后向搜索,它自然会看在当前位置的后面)。所以我们会尝试(?!cat|dog|fish).*

但是,当我们尝试这种模式时catdogfish,它匹配atdogfish! 这里发生了什么?让我们看看当引擎尝试使用我们的模式时会发生什么catdogfish

引擎从左到右工作,从我们字符串中的第一个字符之前开始。在第一次尝试时,前瞻断言从该点开始的下一个字符不是cat,dogfish,但由于它们实际上是cat,因此引擎无法从该点匹配,并前进到第二个字符之前。这里断言成功,因为后面的下一个字符不满足断言(atf不匹配catdogatfi匹配fish)。现在断言成功,引擎可以 match .*,并且由于默认情况下正则表达式是贪婪的(这意味着它们将尽可能多地捕获您的字符串),点星将消耗字符串的其余部分。

您可能想知道为什么在第一个断言成功后没有再次检查环视。那是因为点星被视为一个单一的标记,环视作为一个整体工作。让我们改变一下,让环视每次重复断言一次:(?:(?!cat|dog|fish).)*

(?:…)称为 捕获组。一般来说,正则表达式中的东西是用括号分组的,但是这些括号是capture,这意味着内容被保存到一个反向引用(或子匹配)中。由于我们在这里不需要子匹配,我们可以使用非捕获组,它的工作方式与普通组相同,但没有跟踪反向引用的开销。

当我们针对 运行新模式时,catdogfish我们现在得到三个匹配项2 :at和! 让我们看看这次正则表达式引擎内部发生了什么。ogish

引擎再次在第一个字符之前启动。它进入将被重复的组 ( (?!cat|dog|fish).) 并看到断言失败,因此移动到下一个位置 ( a)。断言成功,引擎前进到t. 断言再次成功,引擎再次向前移动。此时,断言失败(因为接下来的三个字符是dog),并且引擎at作为匹配项返回,因为这是匹配模式的最大字符串(到目前为止,引擎从左到右工作)。

接下来,即使我们已经找到了匹配项,引擎也会继续运行。它将向前移动到下一个字符 ( o),并再次选择与模式 ( ) 匹配的两个字符ogish最后,字符串末尾的 the也会发生同样的事情。一旦引擎到达字符串的末尾,它就没有更多的事情要做,它会返回它拾取的三个匹配项。

所以这个模式仍然不是完美的,因为它会匹配包含我们不允许的短语的字符串部分。为了防止这种情况,我们需要在我们的模式中引入锚点:^(?:(?!cat|dog|fish).)*$

锚也是零宽度断言,它断言引擎所在的位置必须是字符串中的特定位置。在我们的例子中,^匹配字符串的开头,并$匹配字符串的结尾。现在,当我们将我们的模式与 匹配时catdogfish,这些小匹配项都不能再被拾取,因为它们都与锚位置不匹配。

所以最终的表达式是^(?:(?!cat|dog|fish).)*$.


1但是,点默认情况下不匹配换行符,除非在正则表达式上启用/s(或“单行”)修饰符。
2我在这里假设模式在“全局”模式下工作,这使得模式匹配尽可能多。如果没有全局模式,该模式将只返回第一个匹配项at.

于 2010-09-26T16:08:05.147 回答
1

如果您实际使用grep,则可以使用该-v选项仅选择不匹配的行:

grep -v \(cat\|dog\|fish\|^$\)

该模式将选择空行和包含“cat”、“dog”和“fish”的行。

好吧,你没有使用grep. 根据http://www.regular-expressions.info/refadv.html,如果您的正则表达式引擎支持它,您需要?!

`(?!regex)` 零宽度负前瞻。与正向前瞻相同,但只有在前瞻内的正则表达式无法匹配时,整体匹配才会成功。`t(?!s)` 匹配 `streets` 中的第一个 `t`。
于 2010-09-26T01:19:26.113 回答
0

通常最好将否定保留在“围绕”正则表达式的代码中 - 例如 grep 中的 -v 开关或 perl 中的 !~ 。您是否正在尝试解决某个特定问题,或者它只是一个练习?

于 2010-09-27T12:51:06.417 回答