2024年PHP开发者大会日本站

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
"匹配: " . $val[0] . "\n";
echo
"部分 1: " . $val[1] . "\n";
echo
"部分 2: " . $val[2] . "\n";
echo
"部分 3: " . $val[3] . "\n";
echo
"部分 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}。

<?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
3 年前
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
9 年前
这是一个很棒的在线正则表达式编辑器 https://regex101.com/
它可以帮助您测试正则表达式 (pcre、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
12 年前
这是一个将字符串中所有数字都替换为数字减1的函数:

<?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);
// 将字符串分成三部分
$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;


}

?>
[email protected]
16 年前
最近我需要用希伯来语编写一个搜索引擎,遇到了很多问题。我的数据存储在使用 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,因为它会返回出现的次数。所以我可以根据出现的次数对搜索结果进行排序。

希望有人觉得这个有用!
[email protected]
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
)
)
[email protected]
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 年以来已知的一个 bug,但其来源并非 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);
[email protected]
14年前
用于解析包含实体的查询

<?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, "起始位置: ".strpos($strBody, $strFullTag, $intOffset)."\n");
fwrite($fhDebug, "结束位置: ".(strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag))."\n");
fwrite($fhDebug, "长度: ".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 = '我有很多<a href="http://my.site.com">链接</a>在这个<a href="http://my.site.com">页面</a>上,我想找到它们的位置。';

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

/*****
输出:

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,修复了在 utf-8 字符串上使用 PREG_OFFSET_CAPTURE 时的捕获偏移量。

<?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\n";
echo
"目标字符串: $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 文本中使用有限的 span 格式

$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']]
数组(1) {
[0]=>
数组(1) {
[0]=>
字符串(2) "ab"
}
}
数组(1) {
[0]=>
数组(1) {
[0]=>
字符串(3) "abc"
}
}
b3forgames at gmail dot com
1 年前
示例
$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
16 年前
这是一种匹配页面上所有内容的方法,并在处理过程中对每个匹配项执行操作。我在其他语言中使用过这种习惯用法,在这些语言中它的使用是很常见的,但在 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
9 年前
我一直在使用 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
15年前
我收到了一些抱怨,说我的 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
12 年前
由于我打算为自己的目的创建一个干净的 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("$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 年前
请注意,“[email protected]”邮箱的函数在某些情况下可能会导致无效的 XHTML。我认为我的使用方法正确,但结果却类似这样:

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

如果我错了,请纠正我。
如果有时间我会处理这个问题。 -.-
[email protected]
10年前
这里http://tryphpregex.com/是一个基于PHP的在线正则表达式编辑器,它可以帮助你测试正则表达式,并实时高亮显示数据输入中的正则表达式匹配。
DarkSide
10年前
这对于组合匹配非常有用
$a = array_combine($matches[1], $matches[2]);
[email protected]
13 年前
从CSV字符串中提取字段:(因为在PHP 5.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
)
*/
?>
[email protected]
15年前
preg 的强大之处仅受限于你的 *想象力* :)
我使用递归匹配 (?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
一些剩余文本...
< DIV class=noCompliant style = "text-align:left;" >
... 和其他一些 ...
< dIv > < empty> </ empty>
<p> 这又是另一段文本 <br >
它也不 <b>符合规范</b>... <br />
</p>
<div class="noClass" > 这个更好,但我们不在乎 </div ><P>
<input type= "text" name ='my "name' value = "nothin really." readonly>
段落结尾 </p> </Div> </div>一些尾随文本
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;
}
?>
这将生成
一些剩余文本...
<div class="noCompliant" style="text-align:left;">
... 和其他一些 ...
<div>
<empty>
</empty>
<p>
这又是另一段文本
<br/>
它也不
<b>
符合规范
</b>
...
<br/>
</p>
<div class="noClass">
这个更好,但我们不在乎
</div>
<p>
<input type="text" name='my "name' value="nothin really." readonly="readonly"/>
段落结尾
</p>
</div>
</div>
一些尾随文本
avengis at gmail dot com
15年前
下一个函数几乎可以处理任何复杂的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 年前
// 此函数允许你使用正则表达式匹配模式数组

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