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 ('&' => '&', '<' => '<', '>' => '>', '"' => '"');
$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('&', '<', '>', '"'),
$text
);
当给定
$text
为
AT&T --> "baby Bells"
时,
$cooked
将被设置为
AT&T --> "baby Bells"
。
也可以提前构建参数数组,以下版本的功能相同且产生相同的结果:
$patterns = array('/&/', '/</', '/>/', '/"/');
$replacements = array('&', '<', '>', '"');
$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编程中高效地完成复杂的文本匹配和处理任务。
超级会员免费看
2762

被折叠的 条评论
为什么被折叠?



