如何使用preg_match_all()获取所有子组匹配的捕获?

20

更新/注释:

我想我可能正在寻找在PHP中获取组的捕获

参考:使用命名模式子例程的PCRE正则表达式。

(仔细阅读:)


我有一个包含可变数量段落的字符串(简化版):

$subject = 'AA BB DD '; // could be 'AA BB DD CC EE ' as well

我现在希望匹配这些片段并通过匹配数组返回它们:
$pattern = '/^(([a-z]+) )+$/i';
$result = preg_match_all($pattern, $subject, $matches);

这将仅返回捕获组2的最后一个匹配项:DD
有没有办法使用一次正则表达式执行检索所有子模式捕获(AABBDD)?preg_match_all不适用吗?
这个问题是一个概括。 $subject$pattern都被简化了。自然地,使用其他函数(例如explode)或$pattern的变体从这样的AABB等的一般列表中提取更容易。
但是我特别询问如何使用preg_...系列函数返回所有子组匹配项。
对于现实情况,请想象您有多个(嵌套的)级别的变量数量的子模式匹配项。
示例
这是一个伪代码示例,用于描述背景。 想象一下以下内容:
标记的常规定义:
   CHARS := [a-z]+
   PUNCT := [.,!?]
   WS := [ ]

$subject 根据这些进行分词。分词存储在一个令牌数组中(类型,偏移量,...)。

然后将该数组转换为字符串,每个令牌包含一个字符:

   CHARS -> "c"
   PUNCT -> "p"
   WS -> "s"

现在可以基于标记(而不是字符类等)在标记流字符串索引上运行正则表达式。例如:

   regex: (cs)?cp

要表达一个或多个字符组,后面跟着标点符号。

现在我可以将自定义的令牌表示为正则表达式,下一步是构建语法。这只是一个示例,类似于ABNF风格:

   words = word | (word space)+ word
   word = CHARS+
   space = WS
   punctuation = PUNCT

如果我现在将单词的语法编译为(标记)正则表达式,我希望自然地拥有每个单词的所有子组匹配。

  words = (CHARS+) | ( (CHARS+) WS )+ (CHARS+)    # words resolved to tokens
  words = (c+)|((c+)s)+c+                         # words resolved to regex

我能编写代码到这个程度。然后我遇到了一个问题,子组匹配只包含它们的最后一个匹配。

所以我有两个选择:要么自己为语法创建一个自动机(但我希望保持语法表达式的通用性),要么以某种方式让 preg_match 对我起作用,这样我就可以节省时间。

基本上就是这样。现在可能明白我为什么简化了问题。


相关:


如果你把问题概括得太泛了,以至于可能会有其他正确的答案,那么你的问题就不是那么有价值。如果你不想得到简化的答案,就不要简化问题。-1。 - Berry Langerak
1
我正在寻找关于特定主题的答案。 我不明白为什么简化会使这个主题变得可见。但我认为,过高的抽象程度可能是一个负担。 - hakre
1
显然,因为你想要一个子组的答案,而你的例子并没有包括需要子组的情况。这个例子是有缺陷的。 - Berry Langerak
@Berry Langerak:在简化过程中总会有一些信息损失。现在已经添加了一个更详细的例子。 - hakre
preg_split 能被外推吗?[按分隔符拆分字符串,但如果转义则不拆分] (https://dev59.com/nG025IYBdhLWcg3wEhZb)。 - hakre
显示剩余3条评论
8个回答

4

3

试试这个:

preg_match_all("'[^ ]+'i",$text,$n);

$n[0]将包含文本中所有非空格字符组成的数组。

编辑:带有子组:

preg_match_all("'([^ ]+)'i",$text,$n);

现在$n [1]将包含子组匹配,这些匹配与$n [0]完全相同。实际上这是无意义的。

Edit2: 嵌套子组示例:

$test = "Hello I'm Joe! Hi I'm Jane!";
preg_match_all("/(H(ello|i)) I'm (.*?)!/i",$test,$n);

结果如下:

Array
(
    [0] => Array
        (
            [0] => Hello I'm Joe!
            [1] => Hi I'm Jane!
        )

    [1] => Array
        (
            [0] => Hello
            [1] => Hi
        )

    [2] => Array
        (
            [0] => ello
            [1] => i
        )

    [3] => Array
        (
            [0] => Joe
            [1] => Jane
        )

)

我对变量数量的子组匹配感兴趣。您的正则表达式没有任何子组。 - hakre
那么我不明白你的问题。你所要求的匹配并不需要子组。 - aorcsik
不仅是你不理解这个问题,而是因为Hakre无法清楚地表达自己,所以这个问题本身就是完全错误的。对于这个问题给出负一分。 - dynamic
我已经添加了一些信息,以使其更具有抽象性/概括性。 - hakre

2
有没有一种方法可以在一个正则表达式执行中检索所有匹配项(AA,BB,DD)?preg_match_all不适用吗?
您当前的正则表达式似乎是针对preg_match()调用的。请改为尝试以下内容:
$pattern = '/[a-z]+/i';
$result = preg_match_all($pattern, $subject, $matches);

根据评论,我提到的Ruby正则表达式是:
sentence = %r{
(?<subject>   cat   | dog        ){0}
(?<verb>      eats  | drinks     ){0}
(?<object>    water | bones      ){0}
(?<adjective> big   | smelly     ){0}
(?<obj_adj>   (\g<adjective>\s)? ){0}
The\s\g<obj_adj>\g<subject>\s\g<verb>\s\g<opt_adj>\g<object>
}x

md = sentence.match("The cat drinks water");
md = sentence.match("The big dog eats smelly bones");

但我认为您需要一个PHP的词法分析器/解析器/标记解析器来完成类似的事情。 :-|


请阅读结尾处的长例子。我真的很想研究子组模式匹配,而不是全匹配,这样就省去了编写用于组和BNF语法重复的解析器。因此,在消耗整个主题的同时,我需要所有(子)匹配。当这些子模式具有重复时,preg_match_all将始终返回最后一个匹配项。 - hakre
我认为你尝试做的可以用命名分组和递归正则表达式来实现,但我不确定PHP是否支持后者。不过您可能能够在Ruby中处理它。 - Denis de Bernardy
今晚我会仔细考虑一下。 - Denis de Bernardy
不是因为语法的原因,而是因为每个单词至少有一个组,并且这些单词的语义一起形成语法的下一个单词。所以它是堆叠的。并且在这些堆栈内部有可选的重复。所以如果我只能获取匹配数据,那就太完美了。然而,它只返回最后一个反向引用。即使在正则表达式执行之后,拥有一个反向引用的堆栈也很酷。 - hakre
最后一个问题……你有研究过基于PHP的词法分析器和标记解析器吗?我问这个问题是因为,使用正则表达式可能无法实现你尝试解析的内容。Chomsky层次结构 解释了这一点。 - Denis de Bernardy
显示剩余11条评论

1

你无法提取子模式,因为你编写的正则表达式只返回一个匹配项(同时使用^$,以及在主模式上使用+)。

如果你按照以下方式编写它,你会发现你的子组是正确的:

$pattern = '/(([a-z]+) )/i';

(这里仍然有一组不必要的括号,我只是为了说明而保留它)


这个表达式是否可以始终消耗整个主题? - hakre
当我在主题末尾添加#时,它确实返回匹配项,但它并没有消耗整个$subject。我已经在我的模式中添加了起始和结束标记,因为我想将其延伸到$subject的全部内容。 - hakre
@hakre 当字符串末尾添加一个 # 时,您希望发生什么?您的模式会消耗整个字符串,# 只是不匹配而已。如果您需要它匹配,您需要使用不同的正则表达式。请解释您确切的需求。 - kapa
哦,所以你认为无法在模式中使用 ^$ 吗?我正在构建一个将 ABNF 转换为正则表达式的解析器,并且我想保留子组的匹配,但语法需要始终匹配句子和组中的所有单词 - 作为一个整体。 - hakre
1
@hakre 可能你可以使用 preg_match() 函数匹配整个字符串,如果匹配成功,再使用 preg_match_all() 函数提取值。 - kapa
显示剩余4条评论

0

编辑

我没有意识到你最初的要求。这是新的解决方案:

$result = preg_match_all('/[a-z]+/i', $subject, $matches);
$resultArr = ($result) ? $matches[0] : array();

该正则表达式没有任何子组。我特别是在寻找子组的匹配项。 - hakre

0

我可能误解了您所描述的内容。您是不是只是在寻找带有空格的字母组模式?

// any subject containing words:
$subject = 'AfdfdfdA BdfdfdB DdD'; 
$subject = 'AA BB CC';
$subject = 'Af df dfdA Bdf dfdB DdD';

$pattern = '/(([a-z]+)\s)+[a-z]+/i';

$result = preg_match_all($pattern, $subject, $matches);
print_r($matches);
echo "<br/>";
print_r($matches[0]);  // this matches $subject
echo "<br/>".$result;

0

是的,你说得对,你的解决方案是使用preg_match_all。preg_match_all是递归的,所以不要使用以^开头和以$结尾的模式,这样preg_match_all会将所有找到的模式放入一个数组中。

每个新的括号对都会添加一个新的数组,表示不同的匹配项。

使用?进行可选匹配。

您可以使用括号()分隔不同的模式组,以请求在新数组中找到并添加一个组(可以允许您计算匹配项或将每个匹配项从返回的数组中分类)。

需要澄清

让我试着理解你的问题,这样我的答案才能符合你的要求。

  1. 你的 $subject 不是你要找的好例子?

  2. 你想用 pregmatch 搜索,将你提供的 $subject 分成4个类别单词字符标点符号空格?那数字呢?

  3. 同时,你希望返回的匹配结果指定匹配的偏移量吗?

$subject = 'aa.bb cc.dd EE FFF,GG'; 更适合实际例子吗?

我会采用你提供的基本例子在 $subject 上进行操作,确保给你想要的结果。

所以,你能否编辑一下你的 $subject,使其更符合你想要匹配的所有情况呢?

原始代码:'/^(([a-z]+) )+$/i';

保持联系,您可以在这里测试您的正则表达式 http://www.spaweditor.com/scripts/regex/index.php

部分答案

/([a-z])([a-z]+)/i

AA BB DD CD

Array
(
    [0] => Array
        (
            [0] => AA
            [1] => BB
            [2] => DD
            [3] => CD
        )

    [1] => Array
        (
            [0] => A
            [1] => B
            [2] => D
            [3] => C
        )

    [2] => Array
        (
            [0] => A
            [1] => B
            [2] => D
            [3] => D
        )

)

1
不,那不是解决方案。你的例子甚至不能验证整个字符串是否匹配正则表达式,你只是把问题转移到了字符串的一个子集上,而不是整个字符串。此外,子组及其所有匹配/捕获在哪里? - hakre
我想运行 preg_match_all 并获取所有子组捕获,而不仅仅是最后一个。 - hakre
@hakre,有2 1/2种子组,因为你的正则表达式有缺陷。所有正确的答案都会是错误的,我们不知道你想要什么样的结果,请给我们一个你想要的结果数组的例子。 - GuruJR
1
((a)(b)){2}) => 返回两个外部组匹配,返回两个内部组匹配,然后存在两次。例如,此示例也可以是子组,而不仅仅是整个模式。据我所知,PHP的正则表达式引擎无法一次完成此操作。 - hakre
我应该将我在问题中提供的示例转换为代码,以便其抽象特征得到更多“亲身体验”的表现。这可能会有所帮助。 - hakre
preg_match_all是递归的,因此不要使用以“^”开头和以“$”结尾的匹配符号,因为如果你的正则表达式匹配所有内容,它只会给你最后一个DD_的子匹配。 - GuruJR

0

怎么样:

$str = 'AA BB CC';
$arr = preg_split('/\s+/', $str);
print_r($arr);

输出:

(
    [0] => AA
    [1] => BB
    [2] => CC
)

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