正则表达式的负向预查

105

在我的主目录下,有一个名为drupal-6.14的文件夹,其中包含Drupal平台。

我使用以下命令从该目录中操作:

find drupal-6.14 -type f -iname '*' | grep -P 'drupal-6.14/(?!sites(?!/all|/default)).*' | xargs tar -czf drupal-6.14.tar.gz

这个命令的作用是压缩文件夹drupal-6.14,但排除了drupal-6.14/sites/下的所有子文件夹,只包含sites/all和sites/default

我的问题在于正则表达式:

grep -P 'drupal-6.14/(?!sites(?!/all|/default)).*'

这个表达式可以排除我想要排除的所有文件夹,但我不太理解为什么。

使用正则表达式来匹配所有字符串,除了那些不包含子模式x的字符串,或者换句话说,否定一个子模式,是一项常见任务。

匹配所有字符串,除了那些不包含子模式x的字符串,或者换句话说,否定一个子模式。

我认为解决这些问题的一般策略是使用负向先行断言,但我从未真正理解过正向和负向先行/后行的工作原理。

多年来,我阅读了许多关于它们的网站。PHP和Python的正则表达式手册,其他页面,如http://www.regular-expressions.info/lookaround.html等,但我从未真正理解过它们。

有人能解释一下这是如何工作的,并提供一些类似的例子来做类似的事情吗?

-- 更新一:

关于Andomar的回答:双重否定先行断言是否可以更简洁地表示为单个正向先行断言语句:

即:

'drupal-6.14/(?!sites(?!/all|/default)).*'

相当于:

'drupal-6.14/(?=sites(?:/all|/default)).*'

???

-- 更新二:

根据 @andomar 和 @alan moore 的说法 - 你不能将双重否定向前查看替换为正向先行断言。

3个回答

184
一个负向先行断言表示在当前位置,接下来的正则表达式不能匹配。
我们来看一个简化的例子:
a(?!b(?!c))

a      Match: (?!b) succeeds
ac     Match: (?!b) succeeds
ab     No match: (?!b(?!c)) fails
abe    No match: (?!b(?!c)) fails
abc    Match: (?!b(?!c)) succeeds

最后一个例子是 双重否定:它允许匹配 b 后面紧跟着的 c。嵌套的负向先行断言成为了一个正向先行断言:必须存在 c

在每个例子中,只有a被匹配。先行断言只是一个条件,并不会添加到匹配的文本中。


如果嵌套的负向先行断言(“双重负向先行断言”)可以变成正向先行断言,那么是否可能陈述一个等效的正向先行断言形式?即:(a)我的双重负向先行断言Drupal“'drupal-6.14/(?!sites(?!/all|/default))。* '”示例的正向先行断言形式是什么?它会是:'drupal-6.14/(?=sites/all|default).* ???(b)您的双重负向先行断言“(!?b(?!c))”示例的正向先行断言形式是什么? - themesandmodules
@willieseabrook:我认为不行,只有前瞻的一部分是双重否定,所以你不能用肯定的替换整个前瞻。 - Andomar
1
我一直在使用负向先行断言时遇到问题,你的语句“在这个位置”澄清了我的错误。谢谢。 - just mike
1
你有任何想法为什么这在R中不起作用吗?我得到了“grep(“a(?!b(?!c))”,“a”)”无效的正则表达式错误。 - pssguy

15

Lookarounds可以嵌套使用。

因此,这个正则表达式匹配的是"drupal-6.14/",但该字符串后面不是"sites",或者不是"/all"或"/default"。

有点难理解?换句话说,它匹配的是"drupal-6.14/",只要后面不是"sites",除非接下来出现的是"/all"或"/default"。


1
谢谢你。我仍然觉得这很困惑,哈哈。我认为你的引用“不跟随站点,除非跟随所有|默认”非常有帮助。 - themesandmodules

7
如果您将您的正则表达式修改为如下格式:
drupal-6.14/(?=sites(?!/all|/default)).*
             ^^

如果匹配的输入包含drupal-6.14/,后跟sites,再后面是除了/all/default之外的任何内容,则会进行匹配。例如:

drupal-6.14/sites/foo
drupal-6.14/sites/bar
drupal-6.14/sitesfoo42
drupal-6.14/sitesall

?=改为?!以匹配原始正则表达式只是否定了这些匹配:

drupal-6.14/(?!sites(?!/all|/default)).*
             ^^

因此,这意味着drupal-6.14 / 现在不能后跟sites后面的任何其他内容除了/ all / default 。因此,这些输入将满足正则表达式:
drupal-6.14/sites/all
drupal-6.14/sites/default
drupal-6.14/sites/all42

但是,可能并不明显的是,您的正则表达式也将允许其他输入,其中 drupal-6.14/ 后面跟着除 sites 之外的任何内容。例如:

drupal-6.14/foo
drupal-6.14/xsites

结论: 因此,您的正则表达式基本上是告诉它包括drupal-6.14所有子目录,除了那些以除alldefault以外任何其他开头的sites子目录。


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