preg_match_all

(PHP 4, PHP 5, PHP 7, PHP 8)

preg_match_all执行全局正则表达式匹配

描述

preg_match_all(
    字符串 $pattern,
    字符串 $subject,
    数组 &$matches = null,
    整数 $flags = 0,
    整数 $offset = 0
): 整数|false

subject 中搜索与 pattern 中给出的正则表达式匹配的所有内容,并将它们按 flags 指定的顺序放入 matches 中。

找到第一个匹配项后,后续搜索将从最后一个匹配项的末尾继续。

参数

pattern

要搜索的模式,以字符串形式。

subject

输入字符串。

matches

根据 flags 对所有匹配项进行排序的多维数组。

flags

可以是以下标志的组合(请注意,将 PREG_PATTERN_ORDERPREG_SET_ORDER 结合使用没有意义)

PREG_PATTERN_ORDER

对结果进行排序,以便 $matches[0] 是一个包含完整模式匹配的数组,$matches[1] 是一个包含第一个带括号的子模式匹配的字符串的数组,依此类推。

<?php
preg_match_all
("|<[^>]+>(.*)</[^>]+>|U",
"<b>example: </b><div align=left>this is a test</div>",
$out, PREG_PATTERN_ORDER);
echo
$out[0][0] . ", " . $out[0][1] . "\n";
echo
$out[1][0] . ", " . $out[1][1] . "\n";
?>

上面的例子将输出

<b>example: </b>, <div align=left>this is a test</div>
example: , this is a test

所以,$out[0] 包含一个包含与完整模式匹配的字符串的数组,而 $out[1] 包含一个包含由标签包围的字符串的数组。

如果模式包含命名子模式,则 $matches 还会包含具有子模式名称的键的条目。

如果模式包含重复的命名子模式,则只有最右边的子模式存储在 $matches[NAME] 中。

<?php
preg_match_all
(
'/(?J)(?<match>foo)|(?<match>bar)/',
'foo bar',
$matches,
PREG_PATTERN_ORDER
);
print_r($matches['match']);
?>

上面的例子将输出

Array
(
    [0] => 
    [1] => bar
)

PREG_SET_ORDER

对结果进行排序,以便 $matches[0] 是一个包含第一组匹配项的数组,$matches[1] 是一个包含第二组匹配项的数组,依此类推。

<?php
preg_match_all
("|<[^>]+>(.*)</[^>]+>|U",
"<b>example: </b><div align=\"left\">this is a test</div>",
$out, PREG_SET_ORDER);
echo
$out[0][0] . ", " . $out[0][1] . "\n";
echo
$out[1][0] . ", " . $out[1][1] . "\n";
?>

上面的例子将输出

<b>example: </b>, example:
<div align="left">this is a test</div>, this is a test

PREG_OFFSET_CAPTURE

如果传递了此标志,则对于每个出现的匹配项,还将返回附加的字符串偏移量(以字节为单位)。请注意,这会将 matches 的值更改为一个数组的数组,其中每个元素都是一个数组,包含在偏移量 0 处的匹配字符串,以及它在 subject 中的字符串偏移量,在偏移量 1 处。

<?php
preg_match_all
('/(foo)(bar)(baz)/', 'foobarbaz', $matches, PREG_OFFSET_CAPTURE);
print_r($matches);
?>

上面的例子将输出

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => foobarbaz
                    [1] => 0
                )

        )

    [1] => Array
        (
            [0] => Array
                (
                    [0] => foo
                    [1] => 0
                )

        )

    [2] => Array
        (
            [0] => Array
                (
                    [0] => bar
                    [1] => 3
                )

        )

    [3] => Array
        (
            [0] => Array
                (
                    [0] => baz
                    [1] => 6
                )

        )

)

PREG_UNMATCHED_AS_NULL

如果传递了此标志,则未匹配的子模式将被报告为 null;否则,它们将被报告为空 字符串

如果没有给出顺序标志,则假定为 PREG_PATTERN_ORDER

offset

通常,搜索从主题字符串的开头开始。可选参数 offset 可用于指定要开始搜索的替代位置(以字节为单位)。

注意:

使用 offset 不等同于将 substr($subject, $offset) 传递给 preg_match_all() 以代替主题字符串,因为 pattern 可以包含断言,例如 ^$(?<=x)。有关示例,请参阅 preg_match()

返回值

返回完整模式匹配的数量(可能为零),或在失败时返回 false

错误/异常

如果传递的正则表达式模式无法编译为有效的正则表达式,则会发出 E_WARNING

变更日志

版本 描述
7.2.0 现在为 $flags 参数支持 PREG_UNMATCHED_AS_NULL

示例

示例 #1 从一些文本中获取所有电话号码。

<?php
preg_match_all
("/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x",
"Call 555-1212 or 1-800-555-1212", $phones);
?>

示例 #2 查找匹配的 HTML 标签(贪婪)

<?php
// \\2 是反向引用的一个例子。它告诉 pcre 必须匹配正则表达式中的第二个括号
// 本身,在本例中将是 ([\w]+)。额外的反斜杠是必需的,因为字符串在双引号中。
$html = "<b>bold text</b><a href=howdy.html>click me</a>";

preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER);

foreach (
$matches as $val) {
echo
"matched: " . $val[0] . "\n";
echo
"part 1: " . $val[1] . "\n";
echo
"part 2: " . $val[2] . "\n";
echo
"part 3: " . $val[3] . "\n";
echo
"part 4: " . $val[4] . "\n\n";
}
?>

上面的例子将输出

matched: <b>bold text</b>
part 1: <b>
part 2: b
part 3: bold text
part 4: </b>

matched: <a href=howdy.html>click me</a>
part 1: <a href=howdy.html>
part 2: a
part 3: click me
part 4: </a>

示例 #3 使用命名子模式

<?php

$str
= <<<FOO
a: 1
b: 2
c: 3
FOO;

preg_match_all('/(?P<name>\w+): (?P<digit>\d+)/', $str, $matches);

/* 备选方法 */
// preg_match_all('/(?<name>\w+): (?<digit>\d+)/', $str, $matches);

print_r($matches);

?>

上面的例子将输出

Array
(
    [0] => Array
        (
            [0] => a: 1
            [1] => b: 2
            [2] => c: 3
        )

    [name] => Array
        (
            [0] => a
            [1] => b
            [2] => c
        )

    [1] => Array
        (
            [0] => a
            [1] => b
            [2] => c
        )

    [digit] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

    [2] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

)

参见

添加备注

用户贡献的备注 38 备注

buuh
13 年前
如果你想从字符串中提取所有 {token}s

<?php
$pattern
= "/{[^}]*}/";
$subject = "{token1} foo {token2} bar";
preg_match_all($pattern, $subject, $matches);
print_r($matches);
?>

输出

数组
(
[0] => 数组
(
[0] => {token1}
[1] => {token2}
)

)
harrybarrow at mail dot ru
2 年前
preg_match_all() 和其他 preg_*() 函数在处理非常长的字符串(至少超过 1Mb)时效果不好。
在这种情况下,函数返回 FALSE 并且 $matchers 的值不可预测,可能包含一些值,也可能为空。
在这种情况下,解决方法是将长字符串预先分割成多个部分,例如用 explode() 函数根据某些标准分割长字符串,然后对每个部分应用 preg_match_all() 函数。
此情况的典型场景是通过正则表达式进行日志分析。
已在 PHP 7.2.0 上测试。
mnc at u dot nu
18 年前
PREG_OFFSET_CAPTURE 似乎总是提供字节偏移量,而不是字符位置偏移量,即使您使用的是 unicode /u 修饰符。
stas kuryan aka stafox
8 年前
这里有一个很棒的在线正则表达式编辑器 https://regex101.com/
它可以帮助您测试您的正则表达式(prce、js、python),并实时突出显示数据输入上的正则表达式匹配。
Daniel Klein
9 年前
john at mccarthy dot net 发布的代码是不必要的。如果您希望将结果按单个匹配项进行分组,只需使用

<?
preg_match_all($pattern, $string, $matches, PREG_SET_ORDER);
?>

例如:

<?
preg_match_all('/([GH])([12])([!?])/', 'G1? H2!', $matches); // 默认 PREG_PATTERN_ORDER
// $matches = array(0 => array(0 => 'G1?', 1 => 'H2!'),
// 1 => array(0 => 'G', 1 => 'H'),
// 2 => array(0 => '1', 1 => '2'),
// 3 => array(0 => '?', 1 => '!'))

preg_match_all('/([GH])([12])([!?])/', 'G1? H2!', $matches, PREG_SET_ORDER);
// $matches = array(0 => array(0 => 'G1?', 1 => 'G', 2 => '1', 3 => '?'),
// 1 => array(0 => 'H2!', 1 => 'H', 2 => '2', 3 => '!'))
?>
fab
11 年前
以下是一个函数,它用数字替换字符串中所有出现的数字。

<?php
function decremente_chaine($chaine)
{
// 获取所有出现的数字及其索引
preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
// 遍历出现次数
for($i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// 将字符串分成 3 个部分
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
// 如果是 10、100、1000 等,则将所有数字向后移一位,因为结果少了一个数字
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for(
$j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return
$chaine;
}
?>
bruha
16 年前
要计算 UTF-8 字符串中的 str_length,我使用

$count = preg_match_all("/[[:print:]\pL]/u", $str, $pockets);

其中
[:print:] - 可打印字符,包括空格
\pL - UTF-8 字母
/u - UTF-8 字符串
其他 Unicode 字符属性请参阅 http://www.pcre.org/pcre.txt
phpnet at sinful-music dot com
18 年前
这里有一些柔顺的代码,用于 1. 验证地址列表的 RCF2822 符合性,以及 2. 提取地址规范(通常称为“电子邮件”)。我建议不要在输入表单的电子邮件检查中使用它,但它可能是您在其他电子邮件应用程序中想要的。我知道它可以进一步优化,但我将把这部分留给你们这些“敲代码者”。生成的正则表达式的总长度约为 30000 字节。这是因为它接受注释。您可以通过将 $cfws 设置为 $fws 来删除它,它会缩小到大约 6000 字节。符合性检查绝对严格地参照 RFC2822。玩得开心,如果您有任何改进,请给我发电子邮件!

<?php
function mime_extract_rfc2822_address($string)
{
// RFC2822 令牌设置
$crlf = "(?:\r\n)";
$wsp = "[\t ]";
$text = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
$quoted_pair = "(?:\\\\$text)";
$fws = "(?:(?:$wsp*$crlf)?$wsp+)";
$ctext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F" .
"!-'*-[\\]-\\x7F]";
$comment = "(\\((?:$fws?(?:$ctext|$quoted_pair|(?1)))*" .
"$fws?\\))";
$cfws = "(?:(?:$fws?$comment)*(?:(?:$fws?$comment)|$fws))";
//$cfws = $fws; // 评论的替代方案
$atext = "[!#-'*+\\-\\/0-9=?A-Z\\^-~]";
$atom = "(?:$cfws?$atext+$cfws?)";
$dot_atom_text = "(?:$atext+(?:\\.$atext+)*)";
$dot_atom = "(?:$cfws?$dot_atom_text$cfws?)";
$qtext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!#-[\\]-\\x7F]";
$qcontent = "(?:$qtext|$quoted_pair)";
$quoted_string = "(?:$cfws?\"(?:$fws?$qcontent)*$fws?\"$cfws?)";
$dtext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!-Z\\^-\\x7F]";
$dcontent = "(?:$dtext|$quoted_pair)";
$domain_literal = "(?:$cfws?\\[(?:$fws?$dcontent)*$fws?]$cfws?)";
$domain = "(?:$dot_atom|$domain_literal)";
$local_part = "(?:$dot_atom|$quoted_string)";
$addr_spec = "($local_part@$domain)";
$display_name = "(?:(?:$atom|$quoted_string)+)";
$angle_addr = "(?:$cfws?<$addr_spec>$cfws?)";
$name_addr = "(?:$display_name?$angle_addr)";
$mailbox = "(?:$name_addr|$addr_spec)";
$mailbox_list = "(?:(?:(?:(?<=:)|,)$mailbox)+)";
$group = "(?:$display_name:(?:$mailbox_list|$cfws)?;$cfws?)";
$address = "(?:$mailbox|$group)";
$address_list = "(?:(?:^|,)$address)+";

// 输出字符串长度(让你知道它有多长)
echo(strlen($address_list) . " ");

// 应用表达式
preg_match_all("/^$address_list$/", $string, $array, PREG_SET_ORDER);

return
$array;
};
?>
chuckie
17 年前
这是一个将字节偏移量转换为(UTF-8)字符偏移量的函数(无论你是否使用/u 修饰符)

<?php

function mb_preg_match_all($ps_pattern, $ps_subject, &$pa_matches, $pn_flags = PREG_PATTERN_ORDER, $pn_offset = 0, $ps_encoding = NULL) {
// 注意! - 此函数只修正偏移量,其他什么都不做:
//
if (is_null($ps_encoding))
$ps_encoding = mb_internal_encoding();

$pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding));
$ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);

if (
$ret && ($pn_flags & PREG_OFFSET_CAPTURE))
foreach(
$pa_matches as &$ha_match)
foreach(
$ha_match as &$ha_match)
$ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
//
// (代码独立于 PREG_PATTER_ORDER / PREG_SET_ORDER)

return $ret;
}

?>
spambegone at cratemedia dot com
16 年前
我发现 simpleXML 只有在 XML 非常小的情况下才有用,否则服务器会因内存不足而崩溃(我怀疑是存在内存泄漏或其他问题?)。因此,在寻找替代解析器时,我决定尝试一种更简单的方法。我不知道这与 CPU 使用率相比如何,但我确定它对大型 XML 结构有效。这更像是一种手动方法,但对我来说很有效,因为我一直都知道我会接收什么数据结构。

本质上,我只是使用 preg_match() 匹配唯一节点以找到我想要的值,或者使用 preg_match_all() 找到多个节点。这将结果放在一个数组中,然后我就可以根据需要处理这些数据。

不过,我并不满意 preg_match_all() 会将数据存储两次(需要两倍的内存),一个数组用于所有完整模式匹配,另一个数组用于所有子模式匹配。你可能可以编写自己的函数来克服这个问题。但目前这对我很有效,我希望它也能帮助其他人节省一些时间。

// XML 示例
<RETS ReplyCode="0" ReplyText="Operation Successful">
<COUNT Records="14" />
<DELIMITER value="09" />
<COLUMNS>PropertyID</COLUMNS>
<DATA>521897</DATA>
<DATA>677208</DATA>
<DATA>686037</DATA>
</RETS>

<?PHP

// 示例函数
function parse_xml($xml) {


// 获取分隔符(单例)
$match_res = preg_match('/<DELIMITER value ?= ?"(.*)" ?\/>/', $xml, $matches);
if(!empty(
$matches[1])) {
$results["delimiter"] = chr($matches[1]);
} else {
// 默认分隔符
$results["delimiter"] = "\t";
}
unset(
$match_res, $matches);


// 获取多个数据节点(多个实例)
$results["data_count"] = preg_match_all("/<DATA>(.*)<\/DATA>/", $xml, $matches);
// 获取子模式的匹配项,丢弃其余项
$results["data"]=$matches[1];
unset(
$match_res, $matches);

// 取消设置 XML 以节省内存(也应该在函数外部取消设置)
unset($xml);

// 返回结果数组
return $results;


}

?>
meaneye at mail dot com
15 年前
最近我不得不写一个希伯来语搜索引擎,遇到了很多问题。我的数据存储在带有 utf8_bin 编码的 MySQL 表中。

所以,为了能够在 utf8 表中写入希伯来语,你需要这样做
<?php
$prepared_text
= addslashes(urf8_encode($text));
?>

但我必须找到某个单词是否存在于存储的文本中。这就是我卡住的地方。简单的 preg_match 无法找到文本,因为希伯来语没有那么简单。我尝试了 /u 和其他一些东西。

解决方案有点合乎逻辑且简单...
<?php
$db_text
= bin2hex(stripslashes(utf8_decode($db_text)));
$word = bin2hex($word);

$found = preg_match_all("/($word)+/i", $db_text, $matches);
?>

我使用了 preg_match_all,因为它返回了出现次数。因此我可以根据此对搜索结果进行排序。

希望有人发现它有用!
john at mccarthy dot net
13 年前
我需要一个函数来旋转 preg_match_all 查询的结果,并创建了这个函数。不确定它是否存在。

<?php
function turn_array($m)
{
for (
$z = 0;$z < count($m);$z++)
{
for (
$x = 0;$x < count($m[$z]);$x++)
{
$rt[$x][$z] = $m[$z][$x];
}
}

return
$rt;
}
?>

示例 - 获取某些 preg_match_all 查询的结果

数组
(
[0] => 数组
(
[1] => Banff
[2] => Canmore
[3] => Invermere
)

[1] => 数组
(
[1] => AB
[2] => AB
[3] => BC
)

[2] => 数组
(
[1] => 51.1746254
[2] => 51.0938416
[3] => 50.5065193
)

[3] => 数组
(
[1] => -115.5719757
[2] => -115.3517761
[3] => -116.0321884
)

[4] => 数组
(
[1] => T1L 1B3
[2] => T1W 1N2
[3] => V0B 2G0
)

)

将它旋转 90 度以将结果分组为记录

数组
(
[0] => 数组
(
[1] => Banff
[2] => AB
[3] => 51.1746254
[4] => -115.5719757
[5] => T1L 1B3
)

[1] => 数组
(
[1] => Canmore
[2] => AB
[3] => 51.0938416
[4] => -115.3517761
[5] => T1W 1N2
)

[2] => 数组
(
[1] => Invermere
[2] => BC
[3] => 50.5065193
[4] => -116.0321884
[5] => V0B 2G0
)
)
stamster at gmail dot com
8 年前
小心使用此模式匹配和 preg_match_* 函数上的大型输入缓冲区。

<?php
$pattern
= '/\{(?:[^{}]|(?R))*\}/';

preg_match_all($pattern, $buffer, $matches);
?>

如果 $buffer 的大小为 80+ KB,你最终会得到段错误!

[89396.588854] php[4384]: segfault at 7ffd6e2bdeb0 ip 00007fa20c8d67ed sp 00007ffd6e2bde70 error 6 in libpcre.so.3.13.1[7fa20c8c3000+3c000]

这是由于 PCRE 递归造成的。这是 PHP 自 2008 年以来已知的一个错误,但它的来源不是 PHP 本身,而是 PCRE 库。

Rasmus Lerdorf 给出了答案:https://bugs.php.net/bug.php?id=45735#1365812629

"这里的问题是,没有办法检测到失控的正则表达式
这里没有巨大的性能和内存开销。是的,我们可以以一种方式构建 PCRE
它不会导致段错误,我们可以将默认回溯限制提高到
一个巨大的数字,但这会大大降低每次正则表达式调用的速度。如果 PCRE
提供了一种更优雅的方式来处理这个问题,而不会影响性能
点击,当然我们会使用它。"
ad
15 年前
我编写了一个简单的函数来从字符串中提取数字。

我不确定它有多好,但它确实有效。

它只获取数字 0-9、"-"、" "、"("、")"、"."

字符。据我所知,这是电话号码最常用的字符。

<?php
function clean_phone_number($phone) {
if (!empty(
$phone)) {
//var_dump($phone);
preg_match_all('/[0-9\(\)+.\- ]/s', $phone, $cleaned);
foreach(
$cleaned[0] as $k=>$v) {
$ready .= $v;
}
var_dump($ready);
die;
if (
mb_strlen($cleaned) > 4 && mb_strlen($cleaned) <=25) {
return
$cleaned;
}
else {
return
false;
}
}
return
false;
}
?>
marc
12 年前
最好使用 preg_replace 将文本转换为带有 <a> 标签的可点击链接

$html = preg_replace('"\b(http://\S+)"', '<a href="$1">$1</a>', $text);
no at bo dot dy
13 年前
用于解析带有实体的查询

<?php
preg_match_all
("/(?:^|(?<=\&(?![a-z]+\;)))([^\=]+)=(.*?)(?:$|\&(?![a-z]+\;))/i",
$s, $m, PREG_SET_ORDER );
?>
sledge NOSPAM
16 年前
也许你想找到所有锚标签的位置。这将返回一个二维数组,其中将返回起始和结束位置。

<?php
function getTagPositions($strBody)
{
define(DEBUG, false);
define(DEBUG_FILE_PREFIX, "/tmp/findlinks_");

preg_match_all("/<[^>]+>(.*)<\/[^>]+>/U", $strBody, $strTag, PREG_PATTERN_ORDER);
$intOffset = 0;
$intIndex = 0;
$intTagPositions = array();

foreach(
$strTag[0] as $strFullTag) {
if(
DEBUG == true) {
$fhDebug = fopen(DEBUG_FILE_PREFIX.time(), "a");
fwrite($fhDebug, $fulltag."\n");
fwrite($fhDebug, "Starting position: ".strpos($strBody, $strFullTag, $intOffset)."\n");
fwrite($fhDebug, "Ending position: ".(strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag))."\n");
fwrite($fhDebug, "Length: ".strlen($strFullTag)."\n\n");
fclose($fhDebug);
}
$intTagPositions[$intIndex] = array('start' => (strpos($strBody, $strFullTag, $intOffset)), 'end' => (strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag)));
$intOffset += strlen($strFullTag);
$intIndex++;
}
return
$intTagPositions;
}

$strBody = 'I have lots of <a href="http://my.site.com">links</a> on this <a href="http://my.site.com">page</a> that I want to <a href="http://my.site.com">find</a> the positions.';

$strBody = strip_tags(html_entity_decode($strBody), '<a>');
$intTagPositions = getTagPositions($strBody);
print_r($intTagPositions);

/*****
Output:

Array (
[0] => Array (
[start] => 15
[end] => 53 )
[1] => Array (
[start] => 62
[end] => 99 )
[2] => Array (
[start] => 115
[end] => 152 )
)
*****/
?>
loretoparisi at gmail dot com
1 年前
一个多字节安全的 preg_match_all,它在使用 PREG_OFFSET_CAPTURE 处理 utf-8 字符串时修复了捕获偏移量

<?php
function mb_preg_match_all($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) {
$out=preg_match_all($pattern, $subject, $matches, $flags, $offset);
if(
$flags & PREG_OFFSET_CAPTURE && is_array($matches) && count($matches)>0) {
foreach (
$matches[0] as &$match) {
$match[1] = mb_strlen(substr($subject, 0, $match[1]));
}
}
return
$out;
}
?>
biziclop at vipmail dot hu
2 年前
有时你不仅仅想挑选匹配项,还需要整个主体由匹配的子字符串构成,因此主体的每个字符都是匹配项的一部分。现有的 preg_* 函数都不容易适用于此任务,因此我创建了 preg_match_entire() 函数。
它使用 (*MARK) 语法,该语法在以下文档中有所介绍:https://pcre.org/original/doc/html/pcrepattern.html#SEC27

<?php

// 返回:匹配到的数组
// 如果字符串不是模式的重复则为 null
// 错误时为 false
function preg_match_entire( string $pattern, string $subject, int $flags = 0 ){
// 重建并包装模式
$delimiter = $pattern[0];
$ldp = strrpos( $pattern, $delimiter );
$pattern = substr( $pattern, 1, $ldp - 1 );
$modifiers = substr( $pattern, $ldp + 1 );
$pattern = "{$delimiter} \G\z (*MARK:END) | \G (?:{$pattern}) {$delimiter}x{$modifiers}";
$r = preg_match_all( $pattern, $subject, $m, PREG_SET_ORDER | $flags );
if(
$r === false ) return false; // 错误
$end = array_pop( $m );
if(
$end === null || ! isset( $end['MARK']) || $end['MARK'] !== 'END')
return
null; // 未到达字符串末尾
return $m; // 返回实际匹配项,可能为空数组
}

// 相同的结果:
test('#{\d+}#', ''); // []
test('#{\d+}#', '{11}{22}{33}'); // {11},{22},{33}

// 不同的结果:preg_match_entire 不会匹配此项:
test('#{\d+}#', '{11}{}{aa}{22},{{33}}');
// preg_match_entire: null
// preg_match_all: {11},{22},{33}

function test( $pattern, $subject ){
echo
"pattern: $pattern\n";
echo
"subject: $subject\n";
print_matches('preg_match_entire: ', preg_match_entire( $pattern, $subject ));
preg_match_all( $pattern, $subject, $matches, PREG_SET_ORDER );
print_matches('preg_match_all: ', $matches );
echo
"\n";
}
function
print_matches( $t, $m ){
echo
$t, is_array( $m ) && $m ? implode(',', array_column( $m, 0 )) : json_encode( $m ), "\n";
}
?>
rajudec at gmail dot com
2 年前
<?php
// 允许在 html 文本中有限的跨度格式

$str='<span style="text-decoration-line: underline; font-weight: bold; font-style: italic;">White</span>
<span style="text-decoration-line: underline;">RED</span><span style="color:blue">blue</span>'
;

function
next_format($str)
{
$array=array("text-decoration-line"=>"underline","font-weight"=>"bold","font-style"=>"italic");
foreach (
$array as $key=>$val)
{
if(
$str[1]==$key && $str[2]==$val)
{
return
$str[1].': '.$str[2].";";
}
}
return
'';

}
function
next_span($matches)
{
$needFormat=preg_replace_callback('/([a-z\-]+):\s*([^;]+)(;|)/ism',"next_format",$matches[2]);
return
$matches[1].$needFormat.$matches[3];

}
echo
preg_replace_callback(
"/(\<span\s+style\=\")([^\"]+)(\">)/ism",
"next_span",
$str);
?>
mojo
3 年前
为什么 <?php preg_match_all('/(?:^|\s)(ABC|XYZ)(?:\s|$)/i', 'ABC XYZ', $match) ?> 仅找到 'ABC'?

因为第一个完全匹配是 'ABC ' - 包含尾部空格。而这个空格不可用于进一步处理。

使用后视和前视来解决这个问题: <?php preg_match_all('/(?<=^|\s)(ABC|XYZ)(?=\s|$)/i', 'ABC XYZ', $match) ?>
chris at ocproducts dot com
3 年前
如果设置了 PREG_OFFSET_CAPTURE,则不匹配的捕获(即带 '?' 的捕获)将不会出现在结果数组中。这可能是因为没有偏移量,因此原始的 PHP 开发人员决定最好将其省略。
qdinar at gmail dot com
6 年前
当正则表达式用于字符串的较长和较短版本时,
仅捕获了长短版本之一。
当正则表达式匹配发生在字符串的某个位置时,
对于该位置,仅将一个匹配保存到 matches[0] 中。
如果使用 '?',正则表达式是贪婪的,并且会捕获更长的版本,
如果使用 '|',则会捕获最先匹配的变体。
<?php
preg_match_all
('/ab|abc/','abc',$m);
var_dump($m);
preg_match_all('/abc?/','abc',$m);
var_dump($m);
?>
在 $m[0] 中预期 'ab' 和 'abc',但事实并非如此,
实际上它们输出 [['ab']] 和 [['abc']]
array(1) {
[0]=>
array(1) {
[0]=>
string(2) "ab"
}
}
array(1) {
[0]=>
array(1) {
[0]=>
string(3) "abc"
}
}
b3forgames at gmail dot com
8 个月前
示例
$file = file_get_contents('file');
if(preg_match_all('#Task To Run(.*)#s', $file, $m)) {
var_dump($m);
}

没有输出...

如果文件存在 BOM 字节 (FF FE),则 preg_match_all 无法工作。

╰─$ head -n1 file | hexdump -C
00000000 ff fe 48 00 6f 00 73 00 74 00 4e 00 61 00 6d 00 |..H.o.s.t.N.a.m.|

使用 dos2unix 清除 BOM

╰─$ dos2unix file
dos2unix: 正在将 UTF-16LE 文件 file 转换为 UTF-8 Unix 格式...

再次检查

╰─$ head -n1 file | hexdump -C
00000000 48 6f 73 74 4e 61 6d 65 3a 20 20 20 20 20 20 20 |HostName: |

太好了!现在 preg_match_all 工作正常了。
elyknosrac at gmail dot com
15 年前
使用 preg_match_all,我创建了一个非常方便的函数。

<?php

function reg_smart_replace($pattern, $replacement, $subject, $replacementChar = "$$$", $limit = -1)
{
if (!
$pattern || ! $subject || ! $replacement ) { return false; }

$replacementChar = preg_quote($replacementChar);

preg_match_all ( $pattern, $subject, $matches);

if (
$limit > -1) {
foreach (
$matches as $count => $value )
{
if (
$count + 1 > $limit ) { unset($matches[$count]); }
}
}
foreach (
$matches[0] as $match) {
$rep = ereg_replace($replacementChar, $match, $replacement);
$subject = ereg_replace($match, $rep, $subject);
}

return
$subject;
}
?>

这个函数可以将文本块转换为可点击的链接或其他内容。示例

<?php
reg_smart_replace
(EMAIL_REGEX, '<a href="mailto:$$$">$$$</a>', $description)
?>
将把所有电子邮件地址转换为实际链接。

只需用正则表达式将找到的文本替换为 $$$。如果你不能使用 $$$,则使用第四个参数 $replacementChar
MonkeyMan
15 年前
以下是如何匹配页面上的所有内容,并在处理每个匹配项时执行一个操作。我曾在其他语言中使用过这种用法,它很常见,但在 PHP 中似乎并不那么常见。

<?php
function custom_preg_match_all($pattern, $subject)
{
$offset = 0;
$match_count = 0;
while(
preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, $offset))
{
// 递增计数器
$match_count++;

// 获取字节偏移量和字节长度(假设单字节编码)
$match_start = $matches[0][1];
$match_length = strlen(matches[0][0]);

// (可选)将 $matches 转换为通常设置的格式(不设置 PREG_OFFSET_CAPTURE)
foreach($matches as $k => $match) $newmatches[$k] = $match[0];
$matches = $new_matches;

// 你的代码在这里
echo "匹配编号 $match_count,字节偏移量为 $match_start,长度为 $match_length 字节:".$matches[0]."\r\n";

// 将偏移量更新到匹配的末尾
$offset = $match_start + $match_length;
}

return
$match_count;
}
?>

请注意,返回的偏移量是字节值(不一定是字符数),因此您需要确保数据是单字节编码的。(或者在 strlen 手册页上查看 paolo mosna 的 strByte 函数)。
我很想知道这种方法在速度上与使用 preg_match_all 然后递归遍历结果的比较。
matt at lvl99 dot com
8 年前
我一直在使用 Regex101 和 `preg_match_all()` 测试器在线编写和测试一些正则表达式模式,发现我编写的正则表达式模式在它们上可以正常工作,但在我自己的代码中却不行。

我的问题是双重转义反斜杠字符

<?php
// 输入测试
$input = "\"something\",\"something here\",\"some\nnew\nlines\",\"this is the end\"";

// 在在线正则表达式测试器中工作,在 PHP 中不工作
preg_match_all( "/(?:,|^)(?<!\\)\".*?(?<!\\)\"(?:(?=,)|$)/s", $input, $matches );

/*
输出:NULL
*/

// 在在线正则表达式测试器中工作,在 PHP 中工作
preg_match_all( "/(?:,|^)(?<!\\\\)\".*?(?<!\\\\)\"(?:(?=,)|$)/s", $input, $matches );

/*
输出:
array(2) {
[0]=>
array(4) {
[0]=>
string(11) ""something""
[1]=>
string(17) ","something here""
[2]=>
string(17) ","some
new
lines""
[3]=>
string(18) ","this is the end""
}
[1]=>
array(4) {
[0]=>
string(9) "something"
[1]=>
string(14) "something here"
[2]=>
string(14) "some
new
lines"
[3]=>
string(15) "this is the end"
}
}
*/
?>
phektus at gmail dot com
17 年前
如果你想在正则表达式中包含双引号以用于 preg_match_all,尝试三次转义,例如:\\\"

例如,模式
'/<table>[\s\w\/<>=\\\"]*<\/table>/'

应该能够匹配
<table>
<row>
<col align="left" valign="top">a</col>
<col align="right" valign="bottom">b</col>
</row>
</table>
..以及这些 table 标签下的所有内容。

我不确定为什么会出现这种情况,但我尝试过只使用双引号和一个或两个转义字符,但它不起作用。在沮丧之下,我添加了另一个,然后它就正常工作了。
royaltm75 at gmail dot com
14 年前
我收到了投诉,说我的 html2a() 代码(见下文)在某些情况下不起作用。
然而,这不是算法或过程的问题,而是 PCRE 递归堆栈限制的问题。

如果你使用递归 PCRE (?R),你应该记住要增加这两个 ini 设置

ini_set('pcre.backtrack_limit', 10000000);
ini_set('pcre.recursion_limit', 10000000);

但请注意:(来自 php.ini)

; 请注意,如果你将此值设置为一个高数字,你可能会消耗所有
; 可用的进程堆栈,最终导致 PHP 崩溃(由于达到
; 操作系统施加的堆栈大小限制)。

我写这个例子主要是为了展示 PCRE 语言的力量,而不是它实现的力量 :)

但如果你喜欢它,请使用它,当然要自担风险。
fseverin at free dot fr
11 年前
因为我想为自己的目的创建一个干净的 PHP 类来处理 XML 文件,它结合了 DOM 和 simplexml 函数的使用,我遇到了一个很小但非常烦人的问题,那就是路径中的偏移量在两者中编号不同。

也就是说,例如,如果我获得一个 DOM xpath 对象,它看起来像
/ANODE/ANOTHERNODE/SOMENODE[9]/NODE[2]
而作为 simplexml 对象将等效于
ANODE->ANOTHERNODE->SOMENODE[8]->NODE[1]

所以你明白我的意思吗?我使用 preg_match_all 来解决这个问题,最终我经过几个小时的头痛后得到了这个(因为我是法国人,变量的名称是法语,抱歉),希望它对你们中的有些人有用

<?php
function decrease_string($string)
{
/* 从原始字符串中检索所有数字及其偏移量: */

preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
for(
$i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// 将字符串分割成三部分
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
/* 如果是10、100、1000,问题是字符串会变短,所有的偏移量都会发生变化,所以我们必须将它们减少 1 */
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for(
$j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return
$chaine;
}
?>
dolbegraeb
16 年前
请注意,“mail at SPAMBUSTER at milianw dot de” 的函数在某些情况下可能会导致无效的 xhtml。我认为我使用的方式是正确的,但我的结果是这样的

<img src="./img.jpg" alt="nice picture" />foo foo foo foo </img>

如果我错了,请纠正我。
我会在有时间的时候看看如何修复它。 -.-
ajeet dot nigam at icfaitechweb dot com
10 年前
这里 http://tryphpregex.com/ 是一个基于 PHP 的在线正则表达式编辑器,可以帮助您测试正则表达式,并在数据输入时实时突出显示正则表达式匹配。
DarkSide
10 年前
这对于组合匹配非常有用
$a = array_combine($matches[1], $matches[2]);
satyavvd at ymail dot com
12 年前
从 csv 字符串中提取字段:(因为在 php5.3 之前,你不能使用 str_getcsv 函数)
这是正则表达式

<?php

$csvData
= <<<EOF
10,'20',"30","'40","'50'","\"60","70,80","09\\/18,/\"2011",'a,sdfcd'
EOF

$reg = <<<EOF
/
(
(
([\'\"])
(
(
[^\'\"]
|
(\\\\.)
)*
)
(\\3)
|
(
[^,]
|
(\\\\.)
)*
),)
/x
EOF;

preg_match_all($reg,$csvData,$matches);

// 提取 csv 字段
print_r($matches[2]);
?>
mr davin
17 年前
<?php
// 返回一个字符串数组,其中包含找到的开始和结束位置
function findinside($start, $end, $string) {
preg_match_all('/' . preg_quote($start, '/') . '([^\.)]+)'. preg_quote($end, '/').'/i', $string, $m);
return
$m[1];
}

$start = "mary has";
$end = "lambs.";
$string = "mary has 6 lambs. phil has 13 lambs. mary stole phil's lambs. now mary has all the lambs.";

$out = findinside($start, $end, $string);

print_r ($out);

/* 结果是
(
[0] => 6
[1] => all the
)
*/
?>
royaltm75 at NOSPAM dot gmail dot com
15 年前
pregs 的力量只受限于你的 *想象力* :)
我使用递归匹配 (?R) 写了这个 html2a() 函数,它提供了相当安全和防弹的 html/xml 提取
<?php
function html2a ( $html ) {
if ( !
preg_match_all( '
@
\<\s*?(\w+)((?:\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?)\>
((?:(?>[^\<]*)|(?R))*)
\<\/\s*?\\1(?:\b[^\>]*)?\>
|\<\s*(\w+)(\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?\/?\>
@uxis'
, $html = trim($html), $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) )
return
$html;
$i = 0;
$ret = array();
foreach (
$m as $set) {
if (
strlen( $val = trim( substr($html, $i, $set[0][1] - $i) ) ) )
$ret[] = $val;
$val = $set[1][1] < 0
? array( 'tag' => strtolower($set[4][0]) )
: array(
'tag' => strtolower($set[1][0]), 'val' => html2a($set[3][0]) );
if (
preg_match_all( '
/(\w+)\s*(?:=\s*(?:"([^"]*)"|\'([^\']*)\'|(\w+)))?/usix
'
, isset($set[5]) && $set[2][1] < 0
? $set[5][0]
:
$set[2][0]
,
$attrs, PREG_SET_ORDER ) ) {
foreach (
$attrs as $a) {
$val['attr'][$a[1]]=$a[count($a)-1];
}
}
$ret[] = $val;
$i = $set[0][1]+strlen( $set[0][0] );
}
$l = strlen($html);
if (
$i < $l )
if (
strlen( $val = trim( substr( $html, $i, $l - $i ) ) ) )
$ret[] = $val;
return
$ret;
}
?>

现在让我们用这个例子来试试: (有一些非常糟糕的符合 XHTML 的错误,但是...我们不应该担心)

<?php
$html
= <<<EOT
some leftover text...
< DIV class=noCompliant style = "text-align:left;" >
... and some other ...
< dIv > < empty> </ empty>
<p> This is yet another text <br >
that wasn't <b>compliant</b> too... <br />
</p>
<div class="noClass" > this one is better but we don't care anyway </div ><P>
<input type= "text" name ='my "name' value = "nothin really." readonly>
end of paragraph </p> </Div> </div> some trailing text
EOT;

$a = html2a($html);
//现在我们将用它创建一些漂亮的 HTML
echo a2html($a);

function
a2html ( $a, $in = "" ) {
if (
is_array($a) ) {
$s = "";
foreach (
$a as $t)
if (
is_array($t) ) {
$attrs="";
if ( isset(
$t['attr']) )
foreach(
$t['attr'] as $k => $v )
$attrs.=" ${k}=".( strpos( $v, '"' )!==false ? "'$v'" : "\"$v\"" );
$s.= $in."<".$t['tag'].$attrs.( isset( $t['val'] ) ? ">\n".a2html( $t['val'], $in." " ).$in."</".$t['tag'] : "/" ).">\n";
} else
$s.= $in.$t."\n";
} else {
$s = empty($a) ? "" : $in.$a."\n";
}
return
$s;
}
?>
这将产生
some leftover text...
<div class="noCompliant" style="text-align:left;">
... and some other ...
<div>
<empty>
</empty>
<p>
This is yet another text
<br/>
that wasn't
<b>
compliant
</b>
too...
<br/>
</p>
<div class="noClass">
this one is better but we don't care anyway
</div>
<p>
<input type="text" name='my "name' value="nothin really." readonly="readonly"/>
end of paragraph
</p>
</div>
</div>
some trailing text
avengis at gmail dot com
14 年前
下一个函数几乎适用于任何复杂的 XML/XHTML 字符串

<?php
/**
* 查找并关闭未关闭的 XML 标签
**/
function close_tags($text) {
$patt_open = "%((?<!</)(?<=<)[\s]*[^/!>\s]+(?=>|[\s]+[^>]*[^/]>)(?!/>))%";
$patt_close = "%((?<=</)([^>]+)(?=>))%";
if (
preg_match_all($patt_open,$text,$matches))
{
$m_open = $matches[1];
if(!empty(
$m_open))
{
preg_match_all($patt_close,$text,$matches2);
$m_close = $matches2[1];
if (
count($m_open) > count($m_close))
{
$m_open = array_reverse($m_open);
foreach (
$m_close as $tag) $c_tags[$tag]++;
foreach (
$m_open as $k => $tag) if ($c_tags[$tag]--<=0) $text.='</'.$tag.'>';
}
}
}
return
$text;
}
?>
vojjov dot artem at ya dot ru
9 年前
// 这是一个允许你使用 preg_match_all 数组的函数

function getMatches($pattern, $subject) {
$matches = array();

if (is_array($pattern)) {
foreach ($pattern as $p) {
$m = getMatches($p, $subject);

foreach ($m as $key => $match) {
if (isset($matches[$key])) {
$matches[$key] = array_merge($matches[$key], $m[$key]);
} else {
$matches[$key] = $m[$key];
}
}
}
} else {
preg_match_all($pattern, $subject, $matches);
}

return $matches;
}

$patterns = array(
'/<span>(.*?)<\/span>/',
'/<a href=".*?">(.*?)<\/a>/'
);

$html = '<span>some text</span>';
$html .= '<span>some text in another span</span>';
$html .= '<a href="path/">here is the link</a>';
$html .= '<address>address is here</address>';
$html .= '<span>here is one more span</span>';

$matches = getMatches($patterns, $html);

print_r($matches); // 结果如下

/*
数组
(
[0] => 数组
(
[0] => <span>some text</span>
[1] => <span>some text in another span</span>
[2] => <span>here is one more span</span>
[3] => <a href="path/">here is the link</a>
)

[1] => 数组
(
[0] => some text
[1] => some text in another span
[2] => here is one more span
[3] => here is the link
)

)
*/
To Top