55、PHP正则表达式函数全解析

PHP正则表达式函数全解析

在PHP编程中,正则表达式函数是处理文本匹配和替换的强大工具。下面我们将详细介绍几个重要的正则表达式函数,包括 preg_match preg_match_all preg_replace ,并通过具体的示例来展示它们的使用方法和特性。

1. 避免使用数字命名捕获组

虽然在PHP中使用数字命名捕获组目前不会报错,但并不推荐这样做。PHP 4和PHP 5对这种情况的处理方式不同,而且都不是我们所期望的结果。为了避免不必要的问题,最好完全避免使用数字命名捕获组。

2. 获取匹配的详细信息:PREG_OFFSET_CAPTURE

preg_match 函数的第四个参数 flags 包含 PREG_OFFSET_CAPTURE 时, $matches 数组中的值将从简单字符串变为包含两个元素的子数组。第一个元素是匹配的文本,第二个元素是匹配文本在字符串中的偏移量(如果括号未参与匹配,则为 -1)。

偏移量是相对于字符串起始位置的基于零的计数,即使提供了第五个参数 $offset 来指定匹配的起始位置,偏移量仍然以字节为单位。

下面是一个从锚标签中提取 HREF 属性的示例:

$tag = '<a name=bloglink href=\'http://regex.info/blog/\' rel="nofollow">';
preg_match('/href\s+=\s+(?:"([^"]+)"|\'([^\']+)\'|([^\s\'>"]+))/ix', $tag, $matches, PREG_OFFSET_CAPTURE);
print_r($matches);

输出结果如下:

Array
(
    [0] => Array
        (
            [0] => href='http://regex.info/blog/'
            [1] => 17
        )
    [1] => Array
        (
            [0] => 
            [1] => -1
        )
    [2] => Array
        (
            [0] => http://regex.info/blog/
            [1] => 23
        )
)

$matches[0][0] 是正则表达式匹配的整体文本, $matches[0][1] 是该文本在字符串中的字节偏移量。另一种获取相同字符串的方法是使用 substr 函数:

substr($tag, $matches[0][1], strlen($matches[0][0]));

$matches[1][1] 为 -1,表示第一个捕获组未参与匹配。第三个捕获组也未参与匹配,但根据规则,尾部未参与匹配的组的数据不会包含在 $matches 中。

3. 偏移量参数

如果为 preg_match 函数提供了偏移量参数,匹配引擎将从指定的字节位置开始尝试匹配。如果偏移量为负数,则从字符串末尾开始检查。默认情况下,偏移量为零,即从字符串的开头开始匹配。

需要注意的是,即使使用了 u 模式修饰符,偏移量也必须以字节为单位。使用不正确的偏移量(例如,使引擎从多字节字符内部开始)会导致匹配无声失败。

非零偏移量并不会使该位置成为正则表达式引擎中 ^ 匹配的“字符串开头”,它只是指定了正则表达式引擎开始匹配的位置。例如,反向引用可以查看起始偏移量左侧的内容。

4. preg_match_all函数

preg_match_all 函数用于在字符串中查找所有匹配项,并将匹配结果存储在 $matches 数组中。

4.1 函数用法
preg_match_all(pattern, subject, matches [, flags [, offset ]])
  • 参数总结
  • pattern :用分隔符括起来的正则表达式,可包含可选的修饰符。
  • subject :要搜索的目标字符串。
  • matches :用于接收匹配数据的变量(必需)。
  • flags :可选标志,影响函数的整体行为,包括 PREG_OFFSET_CAPTURE PREG_PATTERN_ORDER PREG_SET_ORDER
  • offset :可选的基于零的偏移量,指定匹配开始的位置,与 preg_match 的偏移量参数相同。

  • 返回值 preg_match_all 返回找到的匹配项的数量。

4.2 匹配空字符串与不匹配的区别

preg_match 对于未参与匹配的括号组返回空字符串,但由于空字符串也是成功匹配空内容的返回值,因此建议将未参与匹配的组的值设为 NULL

下面是一个自定义的 regRmatch 函数,它使用 PREG_OFFSET_CAPTURE 标志获取详细信息,并将未参与匹配的组的值设为 NULL

function regRmatch($regex, $subject, &$matches, $offset = 0)
{
    $result = preg_match($regex, $subject, $matches, PREG_OFFSET_CAPTURE, $offset);
    if ($result) {
        $f = create_function('&$X', '$X = $X[1] < 0 ? NULL : $X[0];');
        array_walk($matches, $f);
    }
    return $result;
}

regRmatch 的结果与无标志调用的 preg_match 相同,只是未参与匹配的元素在 regRmatch 中为 NULL

4.3 匹配数据的收集

preg_match_all preg_match 的主要区别在于第三个参数 $matches 中存储的数据。 preg_match 最多执行一次匹配,因此将一次匹配的数据存储在 $matches 中;而 preg_match_all 可以多次匹配,因此可能将多次匹配的数据存储在 $matches 中。

可以通过第四个参数的标志 PREG_PATTERN_ORDER PREG_SET_ORDER 来选择 $matches 中数据的排列方式。

  • 默认的PREG_PATTERN_ORDER排列方式
    这种排列方式将每次匹配的可比部分分组在一起,我称之为“整理”。下面是一个示例:
$subject = "
Jack A. Smith
Mary B. Miller";
preg_match_all('/^(\w+) (\w\.) (\w+)$/m', $subject, $all_matches);
print_r($all_matches);

输出结果如下:

Array
(
    [0] => Array
        (
            [0] => Jack A. Smith
            [1] => Mary B. Miller
        )
    [1] => Array
        (
            [0] => Jack
            [1] => Mary
        )
    [2] => Array
        (
            [0] => A.
            [1] => B.
        )
    [3] => Array
        (
            [0] => Smith
            [1] => Miller
        )
)

在这个示例中,有两次匹配,每次匹配都产生一个“整体匹配”字符串和三个通过捕获括号得到的子字符串。所有的整体匹配都分组在一个数组中( $all_matches[0] ),第一个捕获括号捕获的字符串分组在另一个数组中( $all_matches[1] ),依此类推。

  • PREG_SET_ORDER排列方式
    这种排列方式将每次匹配的所有数据放在一起,我称之为“堆叠”。下面是相同示例的 PREG_SET_ORDER 版本:
$subject = "
Jack A. Smith
Mary B. Miller";
preg_match_all('/^(\w+) (\w\.) (\w+)$/m', $subject, $all_matches, PREG_SET_ORDER);
print_r($all_matches);

输出结果如下:

Array
(
    [0] => Array
        (
            [0] => Jack A. Smith
            [1] => Jack
            [2] => A.
            [3] => Smith
        )
    [1] => Array
        (
            [0] => Mary B. Miller
            [1] => Mary
            [2] => B.
            [3] => Miller
        )
)

下面是两种排列方式的简要总结:
| 类型 | 标志 | 描述和示例 |
| ---- | ---- | ---- |
| 整理 | PREG_PATTERN_ORDER | 每次匹配的可比部分分组在一起。 $all_matches[$paren_num][$match_num] |
| 堆叠 | PREG_SET_ORDER | 每次匹配的所有数据放在一起。 $all_matches[$match_num][$paren_num] |

4.4 preg_match_all与PREG_OFFSET_CAPTURE标志

可以像在 preg_match 中一样在 preg_match_all 中使用 PREG_OFFSET_CAPTURE 标志,将 $all_matches 的每个叶子元素转换为包含匹配文本和字节偏移量的二元数组。如果同时使用 PREG_OFFSET_CAPTURE PREG_SET_ORDER ,可以使用二进制“或”运算符将它们组合起来:

preg_match_all($pattern, $subject, $all_matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
4.5 带命名捕获的preg_match_all

如果使用了命名捕获, $all_matches 中会根据名称添加额外的元素。下面是一个示例:

$subject = "
Jack A. Smith
Mary B. Miller";
preg_match_all('/^(?P<Given>\w+) (?P<Middle>\w\.) (?P<Family>\w+)$/m', $subject, $all_matches);
print_r($all_matches);

输出结果如下:

Array
(
    [0] => Array
        (
            [0] => Jack A. Smith
            [1] => Mary B. Miller
        )
    ["Given"] => Array
        (
            [0] => Jack
            [1] => Mary
        )
    [1] => Array
        (
            [0] => Jack
            [1] => Mary
        )
    ["Middle"] => Array
        (
            [0] => A.
            [1] => B.
        )
    [2] => Array
        (
            [0] => A.
            [1] => B.
        )
    ["Family"] => Array
        (
            [0] => Smith
            [1] => Miller
        )
    [3] => Array
        (
            [0] => Smith
            [1] => Miller
        )
)

使用 PREG_SET_ORDER 的相同示例:

$subject = "
Jack A. Smith
Mary B. Miller";
preg_match_all('/^(?P<Given>\w+) (?P<Middle>\w\.) (?P<Family>\w+)$/m', $subject, $all_matches, PREG_SET_ORDER);
print_r($all_matches);

输出结果如下:

Array
(
    [0] => Array
        (
            [0] => Jack A. Smith
            ["Given"] => Jack
            [1] => Jack
            ["Middle"] => A.
            [2] => A.
            ["Family"] => Smith
            [3] => Smith
        )
    [1] => Array
        (
            [0] => Mary B. Miller
            ["Given"] => Mary
            [1] => Mary
            ["Middle"] => B.
            [2] => B.
            ["Family"] => Miller
            [3] => Miller
        )
)

个人建议在使用命名捕获时省略数字键,这样可以使代码更简洁高效,但如果保留了数字键,不需要时可以直接忽略它们。

下面是 preg_match_all 函数使用的流程图:

graph TD;
    A[开始] --> B[设置参数: pattern, subject, matches, flags, offset];
    B --> C[执行preg_match_all函数];
    C --> D{是否找到匹配项};
    D -- 是 --> E[返回匹配项数量并存储匹配数据];
    D -- 否 --> F[返回0];
    E --> G[结束];
    F --> G;

以上就是 preg_match preg_match_all 函数的详细介绍,通过合理使用这些函数,可以在PHP中高效地进行文本匹配和处理。在下半部分,我们将继续介绍 preg_replace 函数的使用方法和特性。

PHP正则表达式函数全解析(下半部分)

5. preg_replace函数
5.1 函数用法
preg_replace(pattern, replacement, subject [, limit [, count ]])
  • 参数总结
  • pattern :用分隔符括起来的正则表达式,可包含可选的修饰符,也可以是字符串数组。
  • replacement :替换字符串,如果 pattern 是数组, replacement 也可以是字符串数组。当使用 e 模式修饰符时,替换字符串会被当作PHP代码执行。
  • subject :要搜索的目标字符串,也可以是字符串数组。
  • limit :可选整数,用于限制替换的次数。
  • count :可选变量,用于接收实际完成的替换次数(仅PHP 5支持)。
  • 返回值 :如果 subject 是单个字符串,返回值也是一个字符串(可能是 subject 的修改副本);如果 subject 是字符串数组,返回值也是一个数组(包含 subject 中可能修改后的元素)。
5.2 基本的单字符串、单模式、单替换的preg_replace

在常见情况下, pattern replacement subject 都是简单字符串。 preg_replace 会复制 subject ,在其中找到第一个匹配的 pattern ,用 replacement 替换匹配的文本,然后继续处理后续匹配,直到字符串结束。

在替换字符串中, $0 指的是当前匹配的完整文本, $1 指的是第一个捕获括号内匹配的文本, $2 指的是第二个捕获括号内匹配的文本,依此类推。这些美元符号和数字的序列不是对变量的引用,而是 preg_replace 识别的特殊处理序列。也可以使用带花括号的形式,如 ${0} ${1} ,当数字后面紧接着其他字符时,这种形式可以消除引用的歧义。

下面是一个将所有大写单词用HTML粗体标签包裹的简单示例:

$html = preg_replace('/\b[A-Z]{2,}\b/', '<b>$0</b>', $html);

如果使用了 e 模式修饰符(仅 preg_replace 允许使用),替换字符串会被当作PHP代码执行,执行结果将作为替换文本。以下是上述示例的扩展,将包裹在粗体标签中的单词转换为小写:

$html = preg_replace('/\b[A-Z]{2,}\b/e', 'strtolower("<b>$0</b>")', $html);

例如,如果正则表达式匹配的文本是 HEY ,该单词会替换替换字符串中的 $0 捕获引用,得到字符串 strtolower("<b>HEY</b>") ,然后作为PHP代码执行,最终得到替换文本 <b>hey</b>

使用 e 模式修饰符时,替换字符串中的捕获引用会以特殊方式进行插值:插值值中的引号(单引号或双引号)会被转义。如果不进行这种特殊处理,插值值中的引号可能会使生成的字符串作为PHP代码无效。

如果使用 e 模式修饰符并在替换字符串中引用外部变量,最好使用单引号来表示替换字符串字面量,这样可以避免变量在错误的时间进行插值。

下面是一个类似于PHP内置的 htmlspecialchars() 函数的示例:

$replacement = array ('&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;');
$new_subject = preg_replace('/[&<">]/eS', '$replacement["$0"]', $subject);

在这个示例中,替换字符串使用单引号很重要,这样可以将 $replacement 变量的插值隐藏起来,直到它被 preg_replace 作为PHP代码处理。如果使用双引号,它会在传递给 preg_replace 之前被PHP处理。 S 模式修饰符用于提高效率。

如果为 preg_replace 传递了第四个参数 limit ,它将指定每个正则表达式、每个字符串的最大替换次数。默认值为 -1,表示“无限制”。

如果传递了第五个参数 count (PHP 4不允许), preg_replace 会将实际完成的替换次数写入该变量。如果只想知道是否进行了替换,可以比较原始 subject 和结果,但检查 count 参数会更高效。

5.3 多个主题、模式和替换

subject 参数通常是一个简单的字符串,但也可以是字符串数组,此时会依次对每个字符串进行搜索和替换,返回值是结果字符串的数组。

无论 subject 是字符串还是字符串数组, pattern replacement 参数也可以是字符串数组。以下是不同组合及其含义:
| 模式 | 替换 | 操作 |
| ---- | ---- | ---- |
| 字符串 | 字符串 | 应用模式,用替换字符串替换每个匹配项 |
| 数组 | 字符串 | 依次应用每个模式,用替换字符串替换每个匹配项 |
| 数组 | 数组 | 依次应用每个模式,用对应模式的替换字符串替换匹配项 |
| 字符串 | 数组 | 不允许 |

如果 subject 参数是数组,操作将对每个 subject 元素依次执行,返回值也是字符串数组。

需要注意的是, limit 参数是针对每个模式、每个字符串的,不是所有模式或主题字符串的总体限制。而返回的 $count 是所有模式和主题字符串的总体替换次数。

下面是一个 pattern replacement 都是数组的 preg_replace 示例,其结果类似于PHP内置的 htmlspecialchars() 函数,用于处理文本以使其在HTML中安全使用:

$cooked = preg_replace(
    array('/&/', '/</', '/>/', '/"/'),
    array('&amp;', '&lt;', '&gt;', '&quot;'),
    $text
);

当给定 $text AT&T --> "baby Bells" 时, $cooked 将被设置为 AT&amp;T --&gt; &quot;baby Bells&quot;

也可以提前构建参数数组,以下版本的功能相同且产生相同的结果:

$patterns = array('/&/', '/</', '/>/', '/"/');
$replacements = array('&amp;', '&lt;', '&gt;', '&quot;');
$cooked = preg_replace($patterns, $replacements, $text);

preg_replace 接受数组参数很方便,避免了手动编写循环来遍历模式和主题字符串,但实际上并没有增加额外的功能。例如,模式不是“并行”处理的。不过,内置的处理比在PHP代码中手动编写循环更高效,也更具可读性。

为了说明这一点,考虑一个所有参数都是数组的示例:

$result_array = preg_replace($regex_array, $replace_array, $subject_array);

这相当于以下代码:

$result_array = array();
foreach ($subject_array as $subject)
{
    reset($regex_array); 
    reset($replace_array); 
    while (list(,$regex) = each($regex_array))
    {
        list(,$replacement) = each($replace_array);
        $subject = preg_replace($regex, $replacement, $subject);
    }
    $result_array[] = $subject;
}

下面是 preg_replace 函数使用的流程图:

graph TD;
    A[开始] --> B[设置参数: pattern, replacement, subject, limit, count];
    B --> C{subject是数组吗};
    C -- 是 --> D[遍历subject数组];
    C -- 否 --> E[处理单个subject];
    D --> F[对每个subject执行替换操作];
    E --> F;
    F --> G{是否设置limit};
    G -- 是 --> H[限制替换次数];
    G -- 否 --> I[无限制替换];
    H --> J[记录替换次数到count];
    I --> J;
    J --> K{是否完成所有替换};
    K -- 是 --> L[返回结果];
    K -- 否 --> F;
    L --> M[结束];

综上所述, preg_replace 函数在PHP中是一个强大的文本搜索和替换工具,通过合理使用其各种参数和模式修饰符,可以满足不同场景下的文本处理需求。结合之前介绍的 preg_match preg_match_all 函数,能够在PHP编程中高效地完成复杂的文本匹配和处理任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值