PHPpreg_match_all麻烦

PHP preg_match_all trouble

我写了一个正则表达式,我在 rubular.com 中测试过,它返回了 4 个匹配项。可以在此处 http://pastebin.com/49ERrzJN 找到测试主题,下面是 PHP 代码。出于某种原因,PHP 代码 returns 仅匹配前 2 个。如何让它匹配所有 4 个?好像跟贪心有关系吧

$file = file_get_contents('x.txt');
preg_match_all('~[0-9]+\s+(((?!\d{7,}).){2,20})\s{2,30}(((?!\d{7,}).){2,30})\s+([0-9]+)-([0-9]+)-([0-9]+)\s+(F|M)\s+(.{3,25})\s+(((?!\d{7,}).){2,50})~', $file, $m, PREG_SET_ORDER);
foreach($m as $v) echo 'S: '. $v[1]. '; N: '. $v[3]. '; D:'. $v[7]. '<br>';

你的正则表达式太慢了。在 regex101.com 上尝试后,我发现它会在 PHP 上超时(但不是 JS,无论出于何种原因)。我很确定超时发生在 50,000 步左右。实际上,现在您不使用在线 PHP 正则表达式测试器的原因是有道理的。

我不确定这是否是您问题的根源,但是 there is a default memory limit in PHP:

memory_limit [default:] "128M"

[history:] "8M" before PHP 5.2.0, "16M" in PHP 5.2.0

如果你使用 multiline 修饰符(我假设 preg_match_all 基本上添加了 global 修饰符),你可以使用这个只需要 1282 步的正则表达式来找到所有4 场比赛:

^ [0-9]+\s+(((?!\d{7,}).){2,20})\s{2,30}(((?!\d{7,}).){2,30})\s+([0-9]+)-([0-9]+)-([0-9]+)\s+(F|M)\s+(.{3,25})\s+(((?!\d{7,}).){2,50})

实际上,我添加的字符只有2个。它们在开头,锚 ^ 和文字 space.

如果你必须写一个长模式,首先要做的是让它可读。为此,请使用允许注释和自由间距的详细模式(x 修饰符),并使用命名捕获。

那么你需要准确描述你要找的东西:

  • your target takes a whole line => 使用带有修饰符 m 的锚点 ^$,并使用 \h class (仅包含水平空格) 而不是 \s class.
  • 与其使用这种低效的子模式 (?:(?!.....).){m,n} 来描述您的字段不能包含的内容,不如描述字段可以包含的内容。
  • 在需要时使用原子组 (?>...) 而不是非捕获组以避免无用的回溯。
  • 一般来说,使用精确的字符classes可以避免很多问题

模式:

~
^ \h*+ # start of the line
# named captures                            # field separators
(?<VOTERNO>     [0-9]+                     )  \h+
(?<SURNAME>     \S+ (?>\h\S+)*?            )  \h{2,}
(?<OTHERNAMES>  \S+ (?>\h\S+)*?            )  \h{2,}
(?<DOB>         [0-9]{2}-[0-9]{2}-[0-9]{4} )  \h+
(?<SEX>         [FM]                       )  \h+
(?<APPID_RECNO> [0-9A-Z/]+                 )  \h+
(?<VILLAGE>     \S+ (?>\h\S+)*             ) 
\h* $  # end of the line
~mx

demo

如果你想知道一个模式出了什么问题,你可以使用函数preg_last_error()