php preg_match_all返回数组数组

时间:2015-09-26 13:09:56

标签: php regex

我想替换一些模板标签:

$tags = '{name} text {first}';
preg_match_all('~\{(\w+)\}~', $tags, $matches);
var_dump($matches);

输出是:

array(2) { 
          [0]=> array(2) { 
                         [0]=> string(6) "{name}" 
                         [1]=> string(7) "{first}" 
                         } 
          [1]=> array(2) { 
                         [0]=> string(4) "name" 
                         [1]=> string(5) "first" 
                         }
         }

为什么有2个阵列?如何只实现第二个?

2 个答案:

答案 0 :(得分:5)

排序答案:

还有其他选择吗?当然有:lookaround assertions允许您轻松使用零宽度(非捕获)单个字符匹配:

preg_match_all('/(?<=\{)\w+(?=})/', $tags, $matches);
var_dump($matches);

将转储此内容:

array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(4) "name"
    [1]=>
    string(5) "first"
  }
}

模式:

  • (?<=\{):积极向后看 - 如果前面有一个{字符(但不要捕获它),则只匹配模式的其余部分
  • \w+:字符匹配
  • (?=}):仅匹配前一个模式,如果后跟}个字符(但不要捕获}个字符)

这很简单:模式使用{}分隔符字符作为匹配的条件,但不会捕获它们

稍微解释这个$matches数组结构:

$matches看起来很简单的原因很简单:当使用preg_match(_all)时,匹配数组中的第一个条目将始终是给定正则表达式匹配的整个字符串。这就是我使用零宽度外观断言而不是组的原因。您的表达式完整地匹配"{name}",并通过分组提取"name" 匹配数组将在索引0上保持完全匹配,并在每个后续索引处添加组,在您的情况下,这意味着:

  • $matches[0]将包含与/\{\w+\}/匹配的所有子字符串作为模式。
  • $matches[1]将包含已捕获的所有子字符串(/\{(\w+)\}/捕获(\w+))。

如果您要使用这样的正则表达式:/\{((\w)([^}]+))}/匹配数组将如下所示:

[
    0 => [
        '{name}',//as if you'd written /\{\w[^}]+}/
    ],
    1 => [
        'name',//matches group  (\w)([^}]+), as if you wrote (\w[^}]+)
    ],
    2 => [
        'n',//matches (\w) group
    ],
    3 => [
        'ame',//and this is the ([^}]+) group obviously
    ]
]

为什么呢?很简单,因为模式包含3个匹配的组。就像我说的:匹配数组中的第一个索引将始终是完全匹配,无论捕获组如何。然后按照表达式中出现的顺序将这些组附加到数组中。所以,如果我们分析表达式:

  • \{:不匹配,但模式的一部分,只会在$matches[0]
  • ((\w)([^}]+)):第一个匹配组开始,\w[^}]+匹配在此处分组,$matches[1]将包含这些值
  • (\w):第二组,一个\w字符(即{之后的第一个字符。$matches[2]因此将包含{之后的所有第一个字符< / LI>
  • ([^}]+):第三组,在{\w之后匹配其余字符串,直到遇到},这将显示$matches[3]

为了更好地理解,并且能够预测$matches将被填充的方式,我强烈建议您使用this site: regex101。在那里写下你的表达,然后在右侧为你打破这一切,列出这些组。例如:

/\{((\w)([^}]+))}/

这样分解:

/\{((\w)([^}]+))}/
  \{ matches the character { literally
  1st Capturing group ((\w)([^}]+))
    2nd Capturing group (\w)
      \w match any word character [a-zA-Z0-9_]
    3rd Capturing group ([^}]+)
      [^}]+ match a single character not present in the list below
      Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
      } the literal character }
  } matches the character } literally

查看捕获组,您现在可以自信地说出$matches的样子,并且您可以放心地说$matches[2]将是一个单个字符数组。

当然,这可能会让您想知道$matches为什么是2D数组。那么,这又是非常简单的:您可以预测的是$matches数组将包含多少匹配索引:1表示完整模式,然后+1表示每个捕获组。但是,您无法预测的是您可以找到多少匹配。
那么preg_match_all的作用非常简单:用所有匹配整个模式的子串填充$matches[0],然后从这些匹配中提取每个子子串并将该值附加到相应的$matches数组上。换句话说,您可以在$matches中找到的数组数量是给定的:它取决于模式。您在$matches的子数组中可以找到的键数是未知的,它取决于您正在处理的字符串。如果preg_match_all要返回一维数组,那么处理匹配将会困难得多,现在你可以简单地写一下:

$total = count($matches);
foreach ($matches[0] as $k => $full) {
    echo $full . ' contains: ' . PHP_EOL;
    for ($i=1;$i<$total;++$i) {
        printf(
            'Group %d: %s' . PHP_EOL,
            $i, $matches[$i][$k]
        );
    }
}

如果preg_match_all创建了一个平面数组,则必须跟踪模式中的组数量。每当模式发生变化时,您还必须确保更新其余代码以反映对模式所做的更改,使您的代码更难维护,同时使其更容易出错

答案 1 :(得分:1)

这是因为你的正则表达式可能有多个匹配组 - 如果你有更多(..),你的数组中会有更多的条目。第一个[0]总是整场比赛。

如果您想要数组的其他顺序,可以使用PREG_SET_ORDER作为preg_match_all的4.参数。这样做会产生以下结果

array(2) { 
          [0]=> array(2) { 
                         [0]=> string(6) "{name}" 
                         [1]=> string(7) "name" 
                         } 
          [1]=> array(2) { 
                         [0]=> string(4) "{first}" 
                         [1]=> string(5) "first" 
                         }
         }

如果你在foreach循环中循环结果,这可能会更容易。

如果您只是在第一场比赛中进行了分析 - 您应该使用默认PREG_PATTERN_ORDER并使用$matches[1]