首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP.步步为营 | 正则表达式详析 与 诸多运用实例

PHP.步步为营 | 正则表达式详析 与 诸多运用实例

作者头像
凌川江雪
发布2019-03-19 15:05:38
1.7K0
发布2019-03-19 15:05:38
举报
文章被收录于专栏:李蔚蓬的专栏李蔚蓬的专栏
1. PHP正则表达式(PCRE)定义
  • 正则表达式是对字符串进行操作的一种逻辑公式, 就是用一些特定的字符组合成一个规则字符串,称之为正则匹配模式。
$p = '/apple/';
$str = "apple banna";
if (preg_match($p, $str)) {
    echo 'matched';
}

其中字符串 '/apple/' 就是一个正则表达式, 他用来匹配源字符串中是否存在apple字符串。

  • PHP中使用PCRE库函数进行正则匹配, 比如上例中的preg_match用于执行一个正则匹配, 常用来 判断一类字符模式是否存在

2. 正则表达式的基本语法
  • PCRE库函数中,正则匹配模式使用分隔符与元字符组成;
  • 分隔符可以是非数字、非反斜线、非空格的任意字符。 经常使用的分隔符是正斜线(/)、hash符号(#) 以及取反符号(~)

例如:

/foo bar/
#^[^0-9]$#
~php~
  • 如果模式中包含分隔符,则分隔符需要使用反斜杠(\)进行转义

/http:\/\//

  • 如果模式中包含较多的分割字符,建议更换其他的字符作为分隔符, 也可以采用preg_quote进行转义。
$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p;
  • 分隔符后面可以使用模式修饰符,模式修饰符包括:i, m, s, x等, 例如使用i修饰符可以忽略大小写匹配:
$str = "Http://www.imooc.com/";
if (preg_match('/http/i', $str)) {
    echo '匹配成功';
}

正则表达式模式修饰符详细参考: 参考文1; 参考文2;

demo:

<?php
//请修改变量p的正则表达式,使他能够匹配BBC
$p = '/bbc/i';
$str = "BBC是英国的一个电视台";
if (preg_match($p, $str)) {
    echo '\'/bbc/i\'   匹配成功'.'<br>'.'<br>';
}   

$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p.'<br>'.'<br>';
$str = "http://www.imooc.com/";
if (preg_match($p, $str)) {
    echo '\'/http\:\/\//\'     匹配成功'.'<br>'.'<br>';
}   


$str = "Http://www.imooc.com/";
if (preg_match('/http/i', $str)) {
    echo '\'http/i\'    匹配成功';
}

运行结果:

'/bbc/i'   匹配成功

/http\:\/\//

'/http\:\/\//'     匹配成功

'http/i'    匹配成功

3. 元字符与转义

正则表达式中具有特殊含义的字符称之为元字符, 常用的元字符有:

\ 一般用于转义字符 ^断言目标的开始位置(或在多行模式下是行首) $断言目标的结束位置(或在多行模式下是行尾) .匹配除换行符外的任何字符(默认) [ 开始字符类定义 ] 结束字符类定义 | 开始一个可选分支 ) 子组的结束标记 ##下面三个元字符与贪婪特性和懒惰特性有关(下节讲释) ?作为量词,表示 0 次或 1 次匹配。  位于量词后面用于改变量词的贪婪特性。 (查阅量词) * 量词,0 次或多次匹配 + 量词,1 次或多次匹配 {自定义量词开始标记 }自定义量词结束标记 \s匹配任意的空白符,包括空格,制表符,换行符 [^\s]代表非空白符 \w匹配字母或数字或下划线

$p = '/^我[^\s]+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str)) {
    echo '匹配成功';
}
  • 元字符具有两种使用场景, 一种是可以在任何地方都能使用, 另一种是只能在方括号内使用,在方括号内使用的有: \转义字符 ^ 仅在作为第一个字符(方括号内)时,表明字符类取反 -标记字符范围 其中^在反括号外面,表示断言目标的开始位置, 但在方括号内部则代表字符类取反, 方括号内的减号-可以标记字符范围,例如0-9表示0到9之间的所有数字。
//下面的\w匹配字母或数字或下划线。
$p = '/[\w\.\-]+@[a-z0-9\-]+\.(com|cn)/';
$str = "我的邮箱是Spark.eric@imooc.com";
preg_match($p, $str, $match);
echo $match[0];

正则表达式'/[\w\.\-]+@[a-z0-9\-]+\.(com|cn)/'中的[\w\.\-]的意义是, 被匹配的字符串中对应位置的字符,只要符合\w\.\-三个正则符号中的其中一个,即可成功匹配; 也就是说其实[\w\.\-]就是将括号之中的\w\.\-三个符号做了一个并运算; +则是量词,指定了前面的[\w\.\-]的匹配次数;

demo:

<?php
//请修改变量p的正则表达式,使他能够匹配str中的电话
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0].'<br>'.'<br>';

$p = '/^我[^\s]+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str, $match)) {
    echo '匹配成功'.'结果是:'.$match[0].'<br>'.'<br>';
}

$p = '/^我.+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str, $match)) {
    echo '匹配成功'.'结果是:'.$match[0].'<br>'.'<br>';
}

//下面的\w匹配字母或数字或下划线。
$p = '/[\w\.\-]+@[a-z0-9\-]+\.(com|cn)/';
$str = "我的邮箱是Spark.eric@imooc.com";
preg_match($p, $str, $match);
echo '匹配成功'.'结果是:'.$match[0].'<br>';

运行结果:

010-12345678

匹配成功结果是:我喜欢吃苹果

匹配成功结果是:我喜欢吃苹果

匹配成功结果是:Spark.eric@imooc.com

4. 贪婪模式与懒惰模式

贪婪模式与懒惰模式,说到底就是元字符的特性 ?作为量词,表示 0 次或 1 次匹配。 * 量词,0 次或多次匹配 + 量词,1 次或多次匹配

  • 正则表达式中每个元字符匹配一个字符, 当使用 + 之后将会变的贪婪, 它将匹配尽可能多的字符,
  • 但使用问号 ? 字符时, 它将尽可能少地匹配字符, 既是懒惰模式
  • 贪婪模式:在可匹配与可不匹配的时候,优先匹配
//下面的\d表示匹配数字
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678
  • 懒惰模式:在可匹配与可不匹配的时候,优先不匹配
$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0];  //结果为:0-1

换句话说, 懒惰模式是在匹配最近的一个标志, 贪婪模式是匹配到最远的一个标志。

  • 当我们确切的知道所匹配的字符长度的时候,可以使用 {} 指定匹配字符数
$p = '/\d{3}\-\d{8}/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

demo:

<?php
//请修改变量p的正则表达式,使他能够匹配str中的姓名
$p = '/\w+\s\w+/';
$str = "name:steven jobs";
preg_match($p, $str, $match);
echo $match[0].'<br>'.'<br>'; //结果为:steven jobs
$p1 = '/\w+[^\s]\w+\s\w+/';
preg_match($p1, $str, $match);
echo $match[0].'<br>'.'<br>'; 

//下面的\d表示匹配数字
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0].'<br>'.'<br>'; //结果为:010-12345678

$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0].'<br>'.'<br>';  //结果为:0-1

$p = '/\d{3}\-\d{6}/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0].'<br>'.'<br>'; //结果为:010-123456

运行结果:

steven jobs

name:steven jobs

010-12345678

0-1

010-123456

5. 使用正则表达式进行匹配

使用正则表达式的目的是为了实现比字符串处理函数更加灵活的处理方式, 因此跟字符串处理函数一样,

其主要用来

  • 判断子字符串是否存在;
  • 实现字符串替换、分割字符串;
  • 获取模式子串等。
  • PHP使用PCRE库函数来进行正则处理, 通过设定好模式,然后调用相关的处理函数来取得匹配结果
  • preg_match用来执行一个匹配, 1.可以简单的用来判断模式是否匹配成功; 2.或者取得一个匹配结果, 3.他的返回值匹配成功的次数 0 或者 1 ,在匹配到1次以后就会停止搜索。
$subject = "abcdef";
$pattern = '/def/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => def )

上面的代码简单的执行了一个匹配,简单的判断def是否能匹配成功, 但是正则表达式的强大的地方是进行模式匹配, 因此更多的时候,会使用模式

$subject = "abcdef";
$pattern = '/a(.*?)d/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => abcd [1] => bc )

注意: 添加的圆括号相当于给要匹配的内容分组preg_match()是把整体匹配和分组匹配的内容都输出了; 具体的可以参考下方demo的实验结果。

通过正则表达式可以匹配一个模式, 得到更多的有用的数据。

  • 关于表达式(.*?)的理解: 1、 . 匹配任意除换行符“\n”外的字符; 2、 * 表示匹配前一个字符0次或无限次; 3、+*后跟?表示非贪婪匹配,即尽可能少的匹配,如*?重复任意次,但尽可能少重复; 4、 .*? 表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
  • 也就是说, .*匹配的往往是一个字符串, 而有时候符合匹配的 / 可以作为匹配结果的字符串有很多, 这时候在后面的便起到了作用——   在这些满足条件的诸多字符串中,挑出一个最短的字符串(即尽可能少地匹配)作为(.*?)的最终匹配结果。(所谓?引领的懒惰模式) 具体的可以看看下面这个例子:

以上我们可见,

字符串$subject中其实符合$pattern的匹配有两个:abcd/abccccd; 然而匹配的结果是前者,其原因便是方才所说的懒惰模式了。

综合demo:

<?php
$subject = "my email is spark@imooc.com";
//在这里补充代码,实现正则匹配,并输出邮箱地址
$pattern = '/[\w\.]+@[\w\.]+/';
echo '匹配成功的次数: '.preg_match($pattern, $subject, $matches).'<br>';
print_r($matches).'<br>'.'<br>'; //
echo $matches[0].'<br>'.'<br>';


echo '----------------------------------------------'.'<br>';
echo '$pattern = \'/a(.*?)d/\''.'<br>';
$subject = "abcdef";
$pattern = '/a(.*?)d/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';

echo '$pattern = \'/a(.*)d/\''.'<br>';
$subject = "abcdef";
$pattern = '/a(.*)d/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';

$subject = "abcccccdef";
$pattern = '/a(.*)d/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';


$subject = "abcdef";
$pattern = '/ad/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';

$subject = "abcdef";
$pattern = '/a?d/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';

$subject = "abcccdeeeef";
$pattern = '/a(.*)d(.*)f/';
preg_match($pattern, $subject, $matches);
print_r($matches).'<br>'.'<br>'.'<br>'; //
echo '----------------------------------------------'.'<br>';

运行结果:

匹配成功的次数: 1
Array
(
    [0] => spark@imooc.com
)
spark@imooc.com

----------------------------------------------
$pattern = '/a(.*?)d/'
Array
(
    [0] => abcd
    [1] => bc
)
----------------------------------------------
$pattern = '/a(.*)d/'
Array
(
    [0] => abcd
    [1] => bc
)
----------------------------------------------
Array
(
    [0] => abcccccd
    [1] => bccccc
)
----------------------------------------------
Array
(
)
----------------------------------------------
Array
(
    [0] => d
)
----------------------------------------------
Array
(
    [0] => abcccdeeeef
    [1] => bccc
    [2] => eeee
)
----------------------------------------------

6. 查找所有匹配结果
  • preg_match只能匹配一次结果,但很多时候我们需要匹配所有的结果,preg_match_all可以循环获取一个列表的匹配结果数组。
  • 可以使用preg_match_all匹配一个表格中的数据:
$p = "/<tr><td>(.*?)<\/td>\s*<td>(.*?)<\/td>\s*<\/tr>/i";
$str = "<table> <tr><td>Eric</td><td>25</td></tr> <tr><td>John</td><td>26</td></tr> </table>";
preg_match_all($p, $str, $matches);
print_r($matches);

$matches结果排序为$matches[0]保存完整模式的所有匹配, $matches[1]保存第一个子组的所有匹配,以此类推。


<?php
$userinfo = "Name: <b>PHP</b> <br> Title: <b>Programming Language</b>";
preg_match_all ("/<b>(.*)<\/b>/U", $userinfo, $pat_array);
print_r($pat_array);
?>

运行结果:(结果中标签<b>被略去是因为慕课浏览器的原因,然而事实上这个标签是存在于匹配结果的)


<?php
$userinfo = "Name: <b>PHP</b> <br> Title: <b>Programming Language</b>";
preg_match_all ("/<b>(.*)<\/b>/U", $userinfo, $pat_array);
print_r($pat_array[0]);
?>

运行结果:

Array
(
    [0] => <b>PHP</b>
    [1] => <b>Programming Language</b>
)

<?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>

分析输出结果,应该是: 1.首先,$matches[0]放第一段标签,$matches[1]放第二段标签;正所谓循环获取匹配结果列表; 2.接着,$matches[0]同且$matches[1]都是一个Array类型元素,各自在里面完成,完整匹配结果(整段标签)以及 各小分组匹配结果的存储; (当然这个逻辑仅仅属于PREG_SET_ORDER这个flag,同函数默认的排序不一,可见demo3.0&3.1)


demo2.1(个人修改版, 借慕课网平台调试结果):

$html = "<b>bold text</b><a href=howdy.html>click me</a>";
 
preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER);
print_r($matches);
echo '<br>'.'<br>'.'<br>'.'----------------------------------------------'.'<br>';
preg_match("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches);
print_r($matches);

运行结果(同样的,结果中标签<b>被略去是因为慕课浏览器的原因,然而事实上这个标签是存在于匹配结果的):

  • demo2.1 这里分别写了preg_match_all以及preg_match两种匹配方式并附上对应的输出结果,这里我们便可以具体形象地理解下面这段话了:

preg_match只能匹配一次结果,但很多时候我们需要匹配所有的结果,preg_match_all可以循环获取一个列表的匹配结果数组。


demo3.0(获取<li>标签对中的内容):

<?php
$str = "<ul>
            <li>item 1</li>
            <li>item 2</li>
        </ul>";
//在这里补充代码,实现正则匹配所有li中的数据
$p = "/<li>(.*)<\/li>/i";
preg_match_all($p, $str, $matches);
print_r($matches[1]);

print_r($matches);

运行结果:

Array
(
    [0] => item 1
    [1] => item 2
)
Array
(
    [0] => Array
        (
            [0] => <li>item 1</li>
            [1] => <li>item 2</li>
        )

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

)

demo3.1(附加标签PREG_SET_ORDER,调整输出顺序):

<?php
$str = "<ul>
            <li>item 1</li>
            <li>item 2</li>
        </ul>";
//在这里补充代码,实现正则匹配所有li中的数据
$p = "/<li>(.*)<\/li>/i";
preg_match_all($p, $str, $matches,PREG_SET_ORDER);
print_r($matches[1]);

print_r($matches);

运行结果:

Array
(
    [0] => item 2
    [1] => item 2
)
Array
(
    [0] => Array
        (
            [0] => <li>item 1</li>
            [1] => item 1
        )

    [1] => Array
        (
            [0] => <li>item 2</li>
            [1] => item 2
        )

demo3.2(稍改动,观察再一种情况):

<?php
$str = "<ul>
            <li>item 1</li><li>item 2</li>
        </ul>";
//在这里补充代码,实现正则匹配所有li中的数据
$p = "/<li>(.*)<\/li>/i";
preg_match_all($p, $str, $matches,PREG_SET_ORDER);
print_r($matches[1]);

print_r($matches);

运行结果:

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

)

7. 正则表达式的搜索和替换
  • 正则表达式的搜索与替换在某些方面具有重要用途, 比如调整目标字符串的格式改变目标字符串中匹配字符串的顺序等。
  • 例如我们可以简单的调整字符串的日期格式
$string = 'April 15, 2014';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$3, ${1} $2';
echo preg_replace($pattern, $replacement, $string); //结果为:2014, April 15

其中${1}$1的写法是等效的,表示第一个匹配的字串$2代表第二个匹配的

  • 通过复杂的模式,我们可以更加精确地替换目标字符串的内容:
$patterns = array ('/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/',
                   '/^\s*{(\w+)}\s*=/');
$replace = array ('\3/\4/\1\2', '$\1 =');//\3等效于$3,\4等效于$4,依次类推
echo preg_replace($patterns, $replace, '{startDate} = 1999-5-27'); //结果为:$startDate = 5/27/1999

分析一下正则表达式$patterns的意义 : (19|20)表示取19或者20中任意一个数字, (\d{2})表示两个数字, (\d{1,2})表示1个或2个数字, (\d{1,2})表示1个或2个数字。 ^\s*{(\w+)}\s*=表示以任意空格开头的,并且包含在{}中的字符, 并且以任意空格结尾的,最后有个=号的。

  • 用正则替换来去掉多余的空格与字符
$str = 'one     two';
$str = preg_replace('/\s+/', ' ', $str);
echo $str; // 结果改变为'one two'

综合demo:

<?php
$str = '主要有以下几个文件:index.php, style.css, common.js';
//将目标字符串$str中的文件名替换后增加em标签
$p = '/\w+\.\w+/i';
$str = preg_replace($p, '<em>$0</em>', $str);
echo $str.'<br>'.'<br>'.'<br>';
echo '----------------------------------------------'.'<br>';

$string = 'April 15, 2014';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$3, ${1} $2';
echo preg_replace($pattern, $replacement, $string).'<br>'.'<br>'.'<br>'; //结果为:2014, April 15
echo '----------------------------------------------'.'<br>';

$patterns = array ('/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/',
                   '/^\s*{(\w+)}\s*=/');
$replace = array ('\3/\4/\1\2', '$\1 =');//\3等效于$3,\4等效于$4,依次类推
echo preg_replace($patterns, $replace, '{startDate} = 1999-5-27'); //结果为:$startDate = 5/27/1999
echo '<br>'.'<br>'.'----------------------------------------------'.'<br>';

$str = 'one     two';
$str = preg_replace('/\s+/', ' ', $str);
echo $str; // 结果改变为'one two'
echo '<br>'.'<br>'.'----------------------------------------------'.'<br>';
  • $0指的是$str里所有的匹配结果;
  • <em>标签修饰的内容都是用斜体字来显示;

运行结果:

主要有以下几个文件:index.php, style.css, common.js


----------------------------------------------
2014, April 15


----------------------------------------------
$startDate = 5/27/1999

----------------------------------------------
one two

----------------------------------------------

8. 正则匹配常用案例
  • 正则匹配常用在表单验证上,一些字段会有一定的格式要求, 比如用户名一般都要求必须是字母、数字或下划线组成, 邮箱、电话等也都有自己的规则, 因此使用正则表达式可以很好的对这些字段进行验证。

通过一下的demo观察一般的用户注册页,都怎样对字段进行验证:

  • die() 函数 输出一条消息,并退出当前脚本;
  • empty() 判断一个变量是否被认为是空的;
  • preg_match()返回匹配成功的次数, 使用合法格式的模板作为参数, 若函数返回为0说明没有匹配成功过一次,那便是对应字符串不合法了。
<?php
$user = array(
    'name' => 'spark1985',
    'email' => 'spark@imooc.com',
    'mobile' => '13312345678'
);
//进行一般性验证
if (empty($user)) {
    die('用户信息不能为空');
}
if (strlen($user['name']) < 6) {
    die('用户名长度最少为6位');
}
//用户名必须为字母、数字与下划线
if (!preg_match('/^\w+$/i', $user['name'])) {
    die('用户名不合法');
}
//验证邮箱格式是否正确
if (!preg_match('/^[\w\.]+@\w+\.\w+$/i', $user['email'])) {
    die('邮箱不合法');
}
//手机号必须为11位数字,且为1开头
if (!preg_match('/^1\d{10}$/i', $user['mobile'])) {
    die('手机号不合法');
}
echo '用户信息验证成功';

运行结果:

用户信息验证成功
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.03.13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. PHP正则表达式(PCRE)定义
  • 2. 正则表达式的基本语法
  • 3. 元字符与转义
  • 4. 贪婪模式与懒惰模式
  • 5. 使用正则表达式进行匹配
  • 6. 查找所有匹配结果
  • 7. 正则表达式的搜索和替换
  • 8. 正则匹配常用案例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档