惰性表达式的用法

时间:2019-04-20 19:12:02

标签: regex perl

我正在尝试学习正则表达式,但无法理解许多代码在用法上的差异

/apples??/
/.*?[0-9]*/
/.*?[0-9]+/

关于结果,我了解到,如果我将?与任何东西一起使用,它将使该步骤成为可选步骤(或只是忽略它)

例如: 第一个命令将仅匹配apple而不匹配apples 第二个命令将与26

中的page 26匹配

我希望第3条命令会执行相同的操作,但它与整个page 26字符串匹配。

3 个答案:

答案 0 :(得分:2)

?在正则表达式中的含义取决于上下文。

用作量词时,表示“可选”。例如,[0-9]?匹配0或1位数字(可选数字)。

但是,当应用于另一个量词时,则表示“非贪婪”。普通量词会尝试尽可能多地匹配(仅当正则表达式的其余部分否则失败时才放弃匹配)。非贪婪匹配会反过来:它首先尝试尽可能少地匹配,只有在其余正则表达式否则会失败的情况下,才匹配更多。

例如,将a[a-z]*b应用于abcdebafcbad匹配项abcdebafcb(或(abcdebafcb)ad(使用括号标记匹配项))。 [a-z]*首先会消耗整个字符串,只返回字符,直到后面的b可以匹配为止。

但是,a[a-z]*?b应用于abcdebafcbad匹配项ab(或(ab)cdebafcbad(使用括号标记匹配项))。 [a-z]*?首先根本不匹配任何字符,并且由于b可以立即匹配,因此正则表达式停止了。

关于您的示例:

您需要了解的有关正则表达式的第一件事是外部循环。有一个循环尝试在输入字符串中从左到右的每个位置调用正则表达式引擎。如果找到匹配项,则循环停止并报告成功。如果所有位置都用尽而找不到匹配项,则循环将停止并报告失败。

(严格来说,我们并没有遍历输入字符串中的字符,而是遍历了字符之间的间隔。ab具有3个可能的匹配位置:在a之前,a之间和b,以及b之后。)

您的第一个正则表达式apples??等效于apple(?:|s)。我们匹配apple,后跟一个空字符串或s(即,我们首先尝试不匹配任何内容)。由于空的正则表达式总是匹配的,而这是正则表达式的结尾,因此以后没有任何事情可以迫使我们重新考虑该决定。正则表达式仅等效于apple

您的第二个正则表达式.*?[0-9]*有点奇怪。您应该注意的第一件事是它的所有部分都是可选的,即它可以匹配长度为0的字符串。因为空字符串可以在输入中的任何位置进行匹配,并且上述外部循环从偏移量0开始,所以此regex将始终在输入字符串的开头匹配。

要注意的第二件事是它尝试重新实现正则表达式中的外部循环:.*?首先将消耗0个字符,然后(如果其余正则表达式不匹配)消耗1个字符,然后2,...,直到找到匹配项。这有点没有意义,因为外循环已经做到了。

如果输入字符串为page 26,则.*?将通过匹配尽可能少的字符(即无字符)开始。然后[0-9]*将尝试匹配尽可能多的数字,但是p不是数字,因此“尽可能多”也是一个。因此.*?[0-9]*page 26()page26的开头的空字符串匹配(使用括号标记匹配项)。

您的第三个正则表达式.*?[0-9]+在开头.*?仍包含该冗余显式循环。但是现在数字部分不是可选的:[0-9]+至少需要一位数字才能匹配。

如果输入字符串为page 26,则.*?将通过匹配尽可能少的字符(即无字符)开始。然后[0-9]+将尝试匹配尽可能多的数字,但至少要匹配一位。之所以失败,是因为p不是数字。由于[0-9]+失败,我们回溯到.*?,并尝试再消耗一个字符(p)。然后,对其余的输入字符串[0-9]+尝试age 26。这也失败了;我们回溯并在.*?pa)中消耗了一个字符。然后,我们对[0-9]+尝试ge 26,但仍然失败。 ...

这一直持续到.*?消耗了page 为止。此时,[0-9]+最终找到一个要匹配的数字2。由于+是贪婪的,因此我们消耗26这个位置上的所有可用数字。最终匹配为(page 26)(即整个输入字符串),其中.*?匹配page ,而[0-9]+匹配26

答案 1 :(得分:2)

[这是对现有答案的补充,而不是本身的答案。]

以下显示了您提供的示例的匹配过程。 (正则表达式引擎实际上可能会使用不可见的快捷方式来提高性能。)


#0123456
"apples" =~ /apples??/
  1. 在位置0,apple匹配5个字符⇒位置5。
    1. 在位置5,s??匹配0个字符*⇒位置5。
      1. 成功!模式匹配从位置0("apple")开始的5个字符!

#01234567
"page 26" =~ /.*?[0-9]*/
  1. 在位置0,.*?匹配0个字符*⇒位置0。
    1. 在位置0,[0-9]*匹配0个字符⇒位置0。
      1. 成功!模式从位置0("")开始匹配0个字符!

#01234567
"page 26" =~ /.*?[0-9]+/
  1. 在位置0,.*?匹配0个字符*⇒位置0。
    1. 在位置0,[0-9]+无法匹配⇒回溯!
  2. 在位置0,.*?匹配1个字符⇒位置1。
    1. 在位置1,[0-9]+无法匹配⇒回溯!
  3. 在位置0,.*?匹配2个字符⇒位置2。
    1. 在位置2,[0-9]+无法匹配⇒回溯!
  4. 在位置0,.*?匹配3个字符⇒位置3。
    1. 在位置3,[0-9]+无法匹配⇒回溯!
  5. 在位置0,.*?匹配4个字符⇒位置4。
    1. 在位置4,[0-9]+无法匹配⇒回溯!
  6. 在位置0,.*?匹配5个字符⇒位置5。
    1. 在位置5,[0-9]+匹配2个字符⇒位置7。
      1. 成功!模式从位置0("page 26")开始匹配7个字符!

#01234
"ooba" =~ /o.*?a/
  1. 在位置0,o匹配1个字符⇒位置1。
    1. 在位置1,.*?匹配0个字符*⇒位置1。
      1. 在位置1,a无法匹配⇒回溯!
    2. 在位置1,.*?匹配1个字符⇒位置2。
      1. 在位置2,a无法匹配⇒回溯!
    3. 在位置1,.*?匹配2个字符⇒位置3。
      1. 在位置3,a匹配1个字符⇒位置4。
        1. 成功!模式匹配从位置0("ooba")开始的4个字符!

*-由于?而导致的非贪婪,因此首先尝试在当前位置匹配尽可能小的值。

答案 2 :(得分:1)

Bash正则表达式在*, +, ?之后不支持惰性量词,因此*? +? ??将彻底失败
但是,如果${BASH_REMATCH[1]}命令返回了if()组,例如;

$ [[ "apples " =~ (apples?) ]] && echo ${BASH_REMATCH[1]}

apples

$ [[ "page 26" =~ (.*[0-9]+) ]] && echo ${BASH_REMATCH[1]}

page 26

$ [[ "page 26" =~ .*\ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}

26

有效