ZetCode

PHP 正则表达式

最后修改于 2025 年 5 月 19 日

本文探讨了 PHP 中正则表达式的使用,这是一种用于文本搜索和高级文本操作的强大工具。

正则表达式广泛应用于 grep、sed 和文本编辑器(如 vi 和 emacs)等应用程序中。它们还集成到许多编程语言中,包括 Tcl、Perl、Python 和 PHP,PHP 提供了对正则表达式处理的内置支持。

PHP 提供了两个正则表达式模块:POSIX Regex 和 PCRE。POSIX Regex 模块已弃用,因此本指南仅侧重于 PCRE(Perl 兼容正则表达式),它更高效且应用广泛。

在 PHP 中使用正则表达式需要两个关键要素:正则表达式函数和模式。

一个模式是一个结构化的表达式,它定义了要匹配或操作的文本。它由字面字符和元字符组成。该模式包含在分隔符内,通常是 //##@@。这些分隔符指示正则表达式的开始和结束位置,允许 PHP 的正则表达式函数正确解释和处理该模式。

以下是 PCRE(Perl 兼容正则表达式)中使用的一些元字符的简要列表。这些特殊字符允许在 PHP 中进行强大的模式匹配和文本处理。

元字符 / 语法 描述 用法示例
. 匹配除换行符外的任何单个字符。 /a.c/ 匹配 abc、a7c,但不匹配 ac。
* 匹配前一个元素零次或多次。 /go*/ 匹配 g、go、goo 等。
[ ] 括号表达式。匹配括号内的字符。 /[aeiou]/ 匹配字符串中的任何元音。
[^ ] 否定括号表达式。匹配除指定字符外的任何字符。 /[^0-9]/ 匹配任何非数字字符。
^ 匹配字符串的起始位置。 /^Hello/ 匹配 Hello world,但不匹配 World, Hello。
$ 匹配字符串的结束位置。 /world$/ 匹配 Hello world,但不匹配 world Hello。
| 交替运算符。作用类似于“或”条件。 /cat|dog/ 匹配 cat 或 dog。
\d 匹配任何数字 (0-9)。 /\d+/ 匹配 123、98,但不匹配 abc。
\D 匹配任何非数字字符。 /\D+/ 匹配 abc、hello,但不匹配 123。
\w 匹配任何单词字符(字母、数字、下划线)。 /\w+/ 匹配 hello123、var_name,但不匹配 #!。
\W 匹配任何非单词字符。 /\W+/ 匹配 @#$% 等符号。
\s 匹配空格(空格、制表符、换行符)。 /\s+/ 匹配“ ”(多个空格)。
\S 匹配任何非空格字符。 /\S+/ 匹配 Hello、word,但不匹配空格。
+ 匹配前一个元素一次或多次。 /go+/ 匹配 go、goo,但不匹配 g。
{n} 恰好匹配前一个元素的 n 次出现。 /\d{3}/ 匹配 123,但不匹配 12 或 1234。
{n,} 至少匹配 n 次出现。 /\w{5,}/ 匹配长度为 5 个或更多字符的单词。
( ) 用于提取子模式的捕获组。 /(\d{3})-(\d{3})-(\d{4})/ 匹配电话号码格式。

PHP PCRE 函数

PHP 通过 PCRE(Perl 兼容正则表达式)库提供了强大的正则表达式函数,所有函数都以 preg_ 为前缀。这些函数支持灵活的文本操作和模式匹配。

以下是四个常用的 PCRE 函数

让我们通过实际例子来研究每个函数是如何工作的。

main.php
<?php

$text = "Jane\tKate\nLucy Marion";
$names = ["Jane", "jane", "Joan", "JANE"];
$sentence = "I saw Jane. Jane was beautiful.";

// Splitting a string using whitespace characters (\s)
print_r(preg_split("@\s@", $text));

// Checking if 's' belongs to the lowercase alphabet
echo preg_match("#[a-z]#", "s") ? "Match found\n" : "No match\n";

// Replacing 'Jane' with 'Beky' in a sentence
$updatedSentence = preg_replace("/Jane/", "Beky", $sentence);
echo "Updated text: $updatedSentence\n";

// Filtering array values matching 'Jane' (case-sensitive)
print_r(preg_grep("#Jane#", $names));

// Filtering array values matching 'Jane' (case-insensitive)
print_r(preg_grep("#Jane#i", $names));

在第一个例子中,preg_split 通过空格字符 (\s)(包括空格、制表符和换行符)分割字符串。结果是一个由单个单词组成的数组。

接下来,preg_match 检查字母 's' 是否属于小写字符类 ([a-z])。由于匹配,该函数返回 1,表示成功。

使用 preg_replace,我们用“Beky”替换句子中所有出现的“Jane”。此函数使用基于正则表达式的替换来有效地修改文本。

preg_grep 函数根据模式过滤数组。在第一种情况下,它执行区分大小写的搜索,仅返回“Jane”的精确匹配。第二个例子包括 i 修饰符,使搜索不区分大小写,允许匹配“Jane”、“jane”和“JANE”。

这些函数使 PHP 中的文本处理、验证和数据提取变得更容易、更高效。了解如何正确应用它们可以提高基于正则表达式操作的可靠性。

$ php main.php
Array
(
    [0] => Jane
    [1] => Kate
    [2] => Lucy
    [3] => Marion
)
Match found
Updated text: I saw Beky. Beky was beautiful.
Array
(
    [0] => Jane
)
Array
(
    [0] => Jane
    [1] => jane
    [3] => JANE
)

PHP 正则表达式点元字符

.(点)元字符表示文本中的任何单个字符。

single.php
<?php

declare(strict_types=1);

$words = [ "Seven", "even", "Maven", "Amen", "Leven" ];
$pattern = "/.even/";

foreach ($words as $word) {

    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

$words 数组中,我们有五个单词。

$pattern = "/.even/";

这里我们定义搜索模式。该模式是一个字符串。正则表达式放在分隔符内。分隔符是强制性的。在我们的例子中,我们使用正斜杠 / / 作为分隔符。请注意,如果需要,我们可以使用不同的分隔符。点字符表示任何单个字符。

if (preg_match($pattern, $word)) {
    echo "$word matches the pattern\n";
} else {
    echo "$word does not match the pattern\n";
}

我们测试所有五个单词是否与该模式匹配。

$ php single.php 
Seven matches the pattern
even does not match the pattern
Maven does not match the pattern
Amen does not match the pattern
Leven matches the pattern

Seven 和 Leven 这两个词与我们的搜索模式匹配。

PHP 正则表达式锚点

锚点匹配给定文本中字符的位置。

在下一个例子中,我们查看一个字符串是否位于句子的开头。

anchors.php
<?php

declare(strict_types=1);

$sentence1 = "Everywhere I look I see Jane";
$sentence2 = "Jane is the best thing that happened to me";

if (preg_match("/^Jane/", $sentence1)) {
    echo "Jane is at the beginning of the \$sentence1\n";
} else {
    echo "Jane is not at the beginning of the \$sentence1\n";
}

if (preg_match("/^Jane/", $sentence2)) {
    echo "Jane is at the beginning of the \$sentence2\n";
} else {
    echo "Jane is not at the beginning of the \$sentence2\n";
}

我们有两个句子。模式是 ^Jane。该模式检查字符串 'Jane' 是否位于文本的开头。

$ php anchors.php 
Jane is not at the beginning of the $sentence1
Jane is at the beginning of the $sentence2
php> echo preg_match("#Jane$#", "I love Jane");
1
php> echo preg_match("#Jane$#", "Jane does not love me");
0

Jane$ 模式匹配一个字符串,其中单词 Jane 位于结尾。

PHP 正则表达式精确单词匹配

在以下示例中,我们展示了如何查找精确的单词匹配。

php> echo preg_match("/mother/", "mother");
1
php> echo preg_match("/mother/", "motherboard");
1
php> echo preg_match("/mother/", "motherland");
1

mother 模式适合单词 mother、motherboard 和 motherland。假设我们只想查找精确的单词匹配。我们将使用上述锚点 ^$ 字符。

php> echo preg_match("/^mother$/", "motherland");
0
php> echo preg_match("/^mother$/", "Who is your mother?");
0
php> echo preg_match("/^mother$/", "mother");
1

使用锚点字符,我们获得模式的精确单词匹配。

PHP 正则表达式量词

令牌或组后面的量词指定前一个元素允许出现的频率。

 ?     - 0 or 1 match
 *     - 0 or more
 +     - 1 or more
 {n}   - exactly n
 {n,}  - n or more
 {,n}  - n or less (??)
 {n,m} - range n to m

上面是常见量词的列表。

问号 ? 表示前一个元素出现零次或一次。

zeroorone.php
<?php

declare(strict_types=1);

$words = [ "color", "colour", "comic", "colourful", "colored", 
    "cosmos", "coloseum", "coloured", "colourful" ];
$pattern = "/colou?r/";

foreach ($words as $word) {
    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

$words 数组中,我们有四个九。

$pattern = "/colou?r/";

Color 在美式英语中使用,colour 在英式英语中使用。此模式匹配这两种情况。

$ php zeroorone.php 
color matches the pattern
colour matches the pattern
comic does not match the pattern
colourful matches the pattern
colored matches the pattern
cosmos does not match the pattern
coloseum does not match the pattern
coloured matches the pattern
colourful matches the pattern

* 元字符匹配前一个元素零次或多次。

zeroormore.php
<?php

declare(strict_types=1);

$words = [ "Seven", "even", "Maven", "Amen", "Leven" ];

$pattern = "/.*even/";

foreach ($words as $word) {
    
    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

在上面的脚本中,我们添加了 * 元字符。.* 组合表示零个、一个或多个单个字符。

$ php zeroormore.php 
Seven matches the pattern
even matches the pattern
Maven does not match the pattern
Amen does not match the pattern
Leven matches the pattern

现在该模式匹配三个单词:Seven、even 和 Leven。

php> print_r(preg_grep("#o{2}#", ["gool", "root", "foot", "dog"]));
Array
(
    [0] => gool
    [1] => root
    [2] => foot
)

o{2} 模式匹配包含正好两个 'o' 字符的字符串。

php> print_r(preg_grep("#^\d{2,4}$#", ["1", "12", "123", "1234", "12345"]));
Array
(
    [1] => 12
    [2] => 123
    [3] => 1234
)

我们有这个 ^\d{2,4}$ 模式。\d 是一个字符集;它代表数字。该模式匹配具有 2、3 或 4 位数字的数字。

PHP 正则表达式交替

下一个示例解释了交替运算符 |。此运算符能够创建一个具有多个选项的正则表达式。

alternation.php
<?php

declare(strict_types=1);

$names = [ "Jane", "Thomas", "Robert", "Lucy", "Beky", 
    "John", "Peter", "Andy" ];

$pattern = "/Jane|Beky|Robert/";

foreach ($names as $name) {

    if (preg_match($pattern, $name)) {
        echo "$name is my friend\n";
    } else {
        echo "$name is not my friend\n";
    }
}

$names 数组中,我们有八个名称。

$pattern = "/Jane|Beky|Robert/";

这是搜索模式。该模式查找 'Jane'、'Beky' 或 'Robert' 字符串。

$ php alternation.php 
Jane is my friend
Thomas is not my friend
Robert is my friend
Lucy is not my friend
Beky is my friend
John is not my friend
Peter is not my friend
Andy is not my friend

PHP 正则表达式子模式

我们可以使用方括号 在模式内创建子模式。

php> echo preg_match("/book(worm)?$/", "bookworm");
1
php> echo preg_match("/book(worm)?$/", "book");
1
php> echo preg_match("/book(worm)?$/", "worm");
0

我们有以下正则表达式模式:book(worm)?$(worm) 是一个子模式。? 字符跟在子模式之后,这意味着子模式可以在最终模式中出现 0、1 次。$ 字符在这里用于精确结束字符串匹配。如果没有它,bookstore、bookmania 等词也会匹配。

php> echo preg_match("/book(shelf|worm)?$/", "book");
1
php> echo preg_match("/book(shelf|worm)?$/", "bookshelf");
1
php> echo preg_match("/book(shelf|worm)?$/", "bookworm");
1
php> echo preg_match("/book(shelf|worm)?$/", "bookstore");
0

子模式经常与交替一起使用。(shelf|worm) 子模式能够创建几个单词组合。

PHP 正则表达式字符类

我们可以使用方括号将字符组合成字符类。字符类匹配括号中指定的任何字符。

characterclass.php
<?php

declare(strict_types=1);

$words = [ "sit", "MIT", "fit", "fat", "lot" ];

$pattern = "/[fs]it/";

foreach ($words as $word) {

    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

我们定义一个包含两个字符的字符集。

$pattern = "/[fs]it/";

[fs] 是字符类。请注意,我们一次只处理一个字符。我们要么考虑 f,要么考虑 s,但不能同时考虑。

$ php characterclass.php 
sit matches the pattern
MIT does not match the pattern
fit matches the pattern
fat does not match the pattern
lot does not match the pattern

我们还可以使用字符类的速记元字符。\w 代表字母数字字符,\d 代表数字,\s 代表空格字符。

shorthand.php
<?php

declare(strict_types=1);

$words = [ "Prague", "111978", "terry2", "mitt##" ];
$pattern = "/\w{6}/";

foreach ($words as $word) {

    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

在上面的脚本中,我们测试由字母数字字符组成的单词。\w{6} 代表六个字母数字字符。只有单词 mitt## 不匹配,因为它包含非字母数字字符。

php> echo preg_match("#[^a-z]{3}#", "ABC");
1

#[^a-z]{3}# 模式代表不在类 a-z 中的三个字符。“ABC”字符匹配该条件。

php> print_r(preg_grep("#\d{2,4}#", [ "32", "234", "2345", "3d3", "2"]));
Array
(
    [0] => 32
    [1] => 234
    [2] => 2345
)

在上面的例子中,我们有一个模式,匹配 2、3 和 4 位数字。

PHP 正则表达式提取匹配项

preg_match 接受一个可选的第三个参数。如果提供了此参数,则使用搜索结果填充它。该变量是一个数组,其第一个元素包含与完整模式匹配的文本,第二个元素包含第一个捕获的带括号的子模式,依此类推。

extract_matches.php
<?php

declare(strict_types=1);

$times = [ "10:10:22", "23:23:11", "09:06:56" ];

$pattern = "/(\d\d):(\d\d):(\d\d)/";

foreach ($times as $time) {

    $r = preg_match($pattern, $time, $match);
    
    if ($r) {
        
        echo "The $match[0] is split into:\n";
        
        echo "Hour: $match[1]\n";
        echo "Minute: $match[2]\n";
        echo "Second: $match[3]\n";
    } 
}

在这个例子中,我们提取了时间字符串的各个部分。

$times = [ "10:10:22", "23:23:11", "09:06:56" ];

我们有三种英文语言环境的时间字符串。

$pattern = "/(\d\d):(\d\d):(\d\d)/";

该模式使用方括号分成三个子模式。我们希望专门引用每个部分。

$r = preg_match($pattern, $time, $match);

我们将第三个参数传递给 preg_match 函数。如果匹配,则包含匹配字符串的文本部分。

if ($r) {
    
    echo "The $match[0] is split into:\n";
    
    echo "Hour: $match[1]\n";
    echo "Minute: $match[2]\n";
    echo "Second: $match[3]\n";
} 

$match[0] 包含与完整模式匹配的文本,$match[1] 包含与第一个子模式匹配的文本,$match[2] 包含与第二个子模式匹配的文本,$match[3] 包含与第三个子模式匹配的文本。

$ php extract_matches.php 
The 10:10:22 is split into:
Hour: 10
Minute: 10
Second: 22
The 23:23:11 is split into:
Hour: 23
Minute: 23
Second: 11
The 09:06:56 is split into:
Hour: 09
Minute: 06
Second: 56

PHP 正则表达式电子邮件示例

接下来有一个实际的例子。我们创建一个正则表达式模式来检查电子邮件地址。

emails.php
<?php

declare(strict_types=1);

$emails = [ "luke@gmail.com", "andy@yahoocom", "34234sdfa#2345", 
    "f344@gmail.com"];

# regular expression for emails
$pattern = "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,18}$/";

foreach ($emails as $email) {

    if (preg_match($pattern, $email)) {
        echo "$email matches \n";
    } else {
        echo "$email does not match\n";
    }
}

请注意,此示例仅提供一种解决方案。它不一定是最好的解决方案。

$pattern = "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,18}$/";

此正则表达式旨在验证电子邮件地址。开头的 ^ 和结尾的 $ 可确保完全匹配,这意味着在电子邮件结构之前或之后不能出现额外的字符。电子邮件本身分为五个关键组成部分

1. 本地部分:这通常是用户名、公司名称或昵称。模式 [a-zA-Z0-9._-]+ 允许字母、数字、点 (.)、下划线 (_) 和连字符 (-),出现一次或多次。

2. @ 符号:此字符是本地部分和域之间的固定分隔符,确保电子邮件格式正确。

3. 域名:代表电子邮件提供商(例如,gmail.com、yahoo.com)。模式 [a-zA-Z0-9-]+ 接受字母、数字和连字符,确保有效的域结构。

4. 点 (.) 字符:由于点 (.) 是正则表达式中的元字符,因此必须在其前面加上转义字符 (\.) 以按字面意思匹配它。

5. 顶级域 (TLD):模式 [a-zA-Z.]{2,18} 确保 TLD 由 2 到 18 个字符组成,适应常见的扩展,如 .net、.com、.info,甚至多部分 TLD,如 .co.uk。虽然域最多可以包含 63 个字符,但大多数域要短得多。

此正则表达式有助于确保电子邮件遵循结构化格式,使其适用于输入验证、表单提交和数据处理。

$ php emails.php 
luke@gmail.com matches 
andy@yahoocom does not match
34234sdfa#2345 does not match
f344@gmail.com matches 

PHP 正则表达式电话号码验证

让我们看看如何验证常见格式的电话号码,例如 (123) 456-7890123-456-7890

phone_numbers.php
<?php

declare(strict_types=1);

$numbers = [
    "(123) 456-7890",
    "123-456-7890",
    "123.456.7890",
    "1234567890",
    "(123)456-7890",
    "123-45-6789"
];

# pattern for US phone numbers: (123) 456-7890 or 123-456-7890
$pattern = "/^(\(\d{3}\)\s?|\d{3}[.-]?)\d{3}[.-]?\d{4}$/";

foreach ($numbers as $number) {
    if (preg_match($pattern, $number)) {
        echo "$number is a valid phone number\n";
    } else {
        echo "$number is not valid\n";
    }
}

此模式匹配几种常见的美国电话号码格式,包括带括号、空格、短划线或点的格式。您可以根据需要调整该模式以适应其他格式。

PHP 正则表达式先行断言和后行断言

先行断言和后行断言允许您仅在匹配另一个模式之前或之后(或不匹配)才匹配一个模式。这些被称为零宽度断言,因为它们不会消耗字符串中的字符。

lookaround.php
<?php

declare(strict_types=1);

$text = "Price: $20, Discounted: $15, Final: $10, Email: user@example.com";

// Positive lookahead: match dollar amounts only if followed by a comma or space
$pattern1 = "/\\$\\d+(?=[, ])/";
preg_match_all($pattern1, $text, $matches1);
echo "Prices found: ";
print_r($matches1[0]);

// Positive lookbehind: match domain names only if preceded by an email address
$pattern2 = "/(?<=@)[a-zA-Z0-9.-]+/";
preg_match_all($pattern2, $text, $matches2);
echo "Email domains found: ";
print_r($matches2[0]);

在此示例中,第一个模式仅在后面跟着逗号或空格时才匹配美元值(使用 (?=[, ]))。第二个模式仅在前面有“@”符号时才提取电子邮件域名(使用 (?<=@))。

先行断言和后行断言对于高级文本处理非常有用,例如验证用户输入、设置货币格式或提取结构化数据。

回顾

最后,我们提供一个关于正则表达式模式的快速回顾。

Jane         the 'Jane' string
^Jane        'Jane' at the start of a string
Jane$        'Jane' at the end of a string
^Jane$       exact match of the string 'Jane'
[abc]        a, b, or c
[a-z]        any lowercase letter
[^A-Z]       any character that is not an uppercase letter
(Jane|Beky)  matches either 'Jane' or 'Beky'
[a-z]+       one or more lowercase letters
^[98]?$      digit 9, 8 or empty string
([wx])([yz]) wy, wz, xy, or xz
[0-9]        any digit
[^A-Za-z0-9] any symbol (not a number or a letter)
foo(?=\d)    'foo' only if followed by a digit (lookahead)
(?<=bar)\d   digit only if preceded by 'bar' (lookbehind)

来源

preg_match 函数 - PHP 手册

在本章中,我们介绍了 PHP 中的正则表达式。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 PHP 教程。