为什么这个正则表达式挂有两个尾部反斜杠,但只有一个正常工作?

时间:2015-10-05 20:40:41

标签: c# .net regex

我有以下代码使用正则表达式,当我运行它时挂在最后一行:

const char PathSeparator = '\\';
const string PathPrefix = "\\\\";

var reg = new Regex(string.Format("^{0}(([^{1}\r\n\v\f\u2028\u2029]+){1}?)+$", 
    Regex.Escape(PathPrefix), Regex.Escape(PathSeparator.ToString())));

var test = @"\\Inbox\Test123\Intermediate\3322FB69-FE3F-407E-9E15-584382A407A2\\";
var matches = reg.Matches(test);
var group = matches[0].Groups[2];

如果我删除test中的一个最终反斜杠,例如

,它可以正常工作
var test = @"\\Inbox\Test123\Intermediate\3322FB69-FE3F-407E-9E15-584382A407A2\";

有人可以帮我弄清楚它为什么会挂?我知道我可以设置超时;那不是我的问题。

1 个答案:

答案 0 :(得分:3)

现在你有两个问题

我必须在强制性地提及众所周知的quote时开始这个答案。虽然正则表达式是一个可以有效解决许多问题(包括这个问题)的强大工具,但如果你不是这个问题的专家并且导致痛苦,那么它也可能是一个很大的麻烦来源。技术解决方案。

在这种情况下,您可以通过在斜杠上分割输入字符串,然后验证自己生成的每个标记(是的,甚至可能使用正则表达式)来完成相同的工作。实际上这可能是明智之举,因为它会极大地降低解决方案的复杂性因素;在需要“小调整”(增加复杂性)时,将来会非常有价值的东西。

那就是说,让我们看看有趣的东西:发生了什么?

为什么它会永远运行?

由于嵌套+量词导致excessive backtracking。使用更加温和的

样本输入更容易说明
\\Foo\Bar\Baz\\

让我们看看正则表达式引擎在输入此输入时会尝试匹配的内容。

1)首次尝试:最里面的组匹配Foo\Bar\以及Baz\。由于尾随\,进一步重复失败,然后输入结束标记无法匹配,因此拒绝尝试并且引擎回溯。

请注意,如果不存在尾部斜杠,则正则表达式引擎将声明成功并在此时返回

2)因为最里面的组中的斜杠是可选的,所以下一次尝试是Foo\Bar\Baz,这显然也会失败。更多的回溯。

3)下一个候选人匹配最里面的组的四次重复:Foo\Bar\Baz\。失败,更多回溯。

从现在开始,我将通过将最里面的组的重复与单个空格分开来列出尝试的匹配:

Foo\ Bar\ Ba z      (i.e. 4 repetitions of length 4, 4, 2, 1, and fails)
Foo\ Bar\ Ba
Foo\ Bar\ B az\
Foo\ Bar\ B az
Foo\ Bar\ B a
Foo\ Bar\

等等。

这里需要注意的是,他们花了不合理的尝试甚至排除尾随的“Baz”段可以成为一场成功的比赛:我们不得不考虑“Baz”,“Ba”+“z “,”Ba“,”B“+”az“,”B“+”a“和”B“;通常,对于长度为N的段,可能性的数量是N! (阶乘)。阶乘函数的增长超出了直觉的人类理解as Wikipedia illustrates

由于您的示例输入包含长度为36的最终段,因此显然尝试将此正则表达式与格式错误的输入匹配将永远不会完成。

如何防止这种情况发生?

在这种情况下,它非常简单。因为你知道,例如“Baz”试图将它分成较小的块是没有意义的,如果将它作为整体匹配失败(因为这些块需要用斜线分隔,我们已经知道它们不是因为斜杠不是允许的部分尝试匹配),使用原子捕获组:

var reg = new Regex(string.Format(
    "^{0}(?>([^{1}\r\n\v\f\u2028\u2029]+{1}?))+$",
    Regex.Escape(PathPrefix),
    Regex.Escape(PathSeparator.ToString())));

这将只回溯一次而不是N!对于不成功匹配的每个路径段,将失败的时间减少到几乎为零。