1

正则表达式的定义

正则表达式就是描述字符排列模式的一种自定义的语法规则。
由于正则表达式本身具有一套非常完整的、可以编写模式的语法体系,提供了一种灵活且直观的字符串处理方法,故正则表达式也称为模式表达式。

正则表达式的特点

  1. 正则表达式并不是PHP特有的,JavaScript、Java、Perl、MySQL中都可以应用到正则表达式。
  2. 正则表达式通过构建具有特定规则的模式,与输入的字符串信息进行比较,从而实现字符串的匹配、查找、替换及分割等操作。

例子:

"/^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i"                // 匹配网址URL的正则表达式
"/<(\S*?)[^>]*>.*?<\/\\1>|<.*?/>/i"                                // 匹配HTML标记的正则表达式
"/\w+([-+.]\w)+*@\w+([-.]\w+)*\.\w+([-.]\w+)*/"                    // 匹配E-mail地址的正则表达式
"/^<img\s+[^>]*\s*src\s*=\s*([']?)(?<url>\S+)'?[^>l ]*>$/"        // 匹配img标签
注意
  1. 正则表达式是由普通字符与具有特殊意义的字符组成字符串
  2. 效率问题,如果可以使用字符串函数完成任务,那尽量不使用正则表达式函数
  3. 对于一些复杂操作,如表单验证,计算发布文章中有多少个句子,抓取网页中某种格式的句子等使用正则表达式以及相关函数能够更好地实现
  4. 正则表达式具有一定的编写规则(语义),是一种模式
  5. 若正则表达式不与 相应的正则表达式函数使用,那么正则表达式仅仅是字符串,要达成匹配、查找、替换以及分割功能,正则表达式必须与相应的正则表达式函数配套使用。

PHP中两套支持正则表达式的函数库

  1. PCRE(Perl Compatible Regular Expression)

    由PCRE库提供,与Perl语言兼容的正则表达式、使用"preg_"为前缀命名的函数,而且表达式都应被包含在定界符中。

  2. POSIX(Portable Operation System Interface)

    自从PHP5.3.0开始废弃.使用一套"ereg_"为前缀命名的函数。

注意
1. 两套 函数库功能类似,执行效率不同,一般来说,实现相同功能,使用 PCRE库提供的正则表达式效率略占优势。
2. PCRE 函数需要模式以定界符闭合。
3. 不像POSIX,PCRE 扩展没有专门用于大小写不敏感匹配的函数。
   取而代之的是,支持使用i (PCRE_CASELESS) 模式修饰符完成同样的工作。 其他模式修饰符同样可用于改变匹配策略。
4. POSIX 函数从最左面开始寻找最长的匹配,但是 PCRE 在第一个合法匹配后停止。如果字符串 
   不匹配这没有什么区别,但是如果匹配,两者在结果和速度上都会有差别。 为了说明
   这个不同, 考虑下面的例子(来自Jeffrey Friedl 的《精通正则表达式》一书)。 
   使用模式 one(self)?(selfsufficient)? 在字符串oneselfsufficient 上匹配,
   PCRE 会匹配到oneself,但是使用 POSIX,结果将是整个字符串 oneselfsufficient。
   两个子串都匹配原始字符串,但是 POSIX 将 最长的作为结果。

函数对比

POSIX PCRE
ereg_replace() preg_replace()
ereg() preg_match()
eregi_replace() preg_replace()
eregi() preg_match()
split() preg_split()
spliti() preg_split()
sql_regcase() 无对等函数
可参照PHP Manual

正则表达式语法规则

正则表达式描述了一种字符串的匹配模式,通过这个模式在特定的函数中对字符串进行匹配、查找、替换以及分割等操作。

正则表达式作为一个匹配的模板,是由定界符,原子(普通字符,例如a-z)、有特殊功能的字符(称为元字符,例如*、+、?等),
以及模式修正符等部分组成的文字模式。

例子:

"/^https?\/\/(([a-zA-Z0-9_-])+(\.)?)*(\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i/"

或者

'/^<a.*?(?|\\t|\\r|\\n)?href=[\'"]?(.+?)[\'"]?(?(?|\\t|\\r|\\n)+.*?)?>(.+?)<\/a.*?>$/sim'


1. 定界符中使用的是两个斜线"/",将模式放在它之间说明。
2. 原子用到了<、a、href、=、'、"、/、>等普通字符和\t、\r、\n等转义字符。
3. 元字符使用了[]、()、|、.、?、*、+等具有特殊含义的字符。
4. 用到模式修正符是在定界符最后一个斜线之后的三个字符"s"、"i"、"m"。

定界符

定界符为PCRE不同于POSIX的特点之一。

除了字母、数字和反斜线以外的字符皆可为定界符

例子:
{ }# #||!!

/<\/w+>/              // 使用反斜线作为定界符号 合法
|(\d{3})-\d+|Sm     // 使用竖线"|"作为定界符号 合法
!^(?i)PHP[34]!        // 使用感叹号"!"作为定界符 合法
{^\s+(\s+)?$}       // 使用花括号"{}"作为定界符号 合法
/href='(.*)'        // 非法定界符,缺少结束定界符
1-\d3-\d3-\d4/      // 非法定界符号,缺少起始定界符 


注意

如果没有特殊要求,一般使用//作为定界符

定界符是成对的,有开始符号,也有结束符号。

原子

原子是正则表达式中最基本的组成单位,而且在每个模式中最少要包含一个原子。

原子是所有那些未显式指定为元字符的打印(可以在屏幕上输出的字符)和非打印字符(看不到的)组成的。
简单地说,能够在正则表达式中单独使用的字符就可称为原子

将其划分为五类

1. 普通字符作为原子

普通字符是编写正则表达式时最常见的原子,包括所有的大写和小写字母字符、所有数字等,例如:a-z、A-Z、0-9。

2. 非打印字符(看不到的)作为原子

原子字符                    \x09 含义描述

+ \cx        匹配由x指明的控制符。
             例如,\c\M匹配一个Control-M或者回车符。x的值必须为A-Z或a-z之一,
             否则,将c视为一个原义的'c'字符。

+ \f         匹配一个换页符。等价于\x0c和\cL

+ \n         匹配一个换行符。等价于\x0a和\cJ

+ \r         匹配一个回车符。等价于\x0d和\cM

+ \t         匹配一个制表符。等价于\x09和\cI

+ \v         匹配一个垂直制表符。等价于\x0b和\cK

3.通用字符类型做为原子

前面介绍的原子,都是一个原子只能匹配一个字符。但是有时候我们需要一个原子可以匹配一类字符。

在正则表达式中可以直接使用一些代表范围的原子

原子字符            含义描述

\d                     匹配任意一个十进制数字,等价于[0-9]

\D                    匹配任意一个除十进制数字以外的字符,等价于[^0-9]

\w                    匹配任意一个数字、字母、下划线,等价于[0-9a-zA-Z_]

\W                    匹配除数字、字母、下划线以外的任意一个字符,等价于[^0-9a-zA-Z_]

\s                    匹配任意一个空白字符,等价于[\f\t\n\v\r]

\S                    匹配除空白字符以外的任何一个字符,等价于[^\t\f\n\v\r]

4. 自定义原子表([])作为原子

有些时候,上面六个通用字符并不能满足我们的需求,我们需要自定义一类原子,比如说奇数(1,3,5,7,9).
所以这时候需要我们自定义一类原子(称之为类原子),使用原子表"[]"就可以定义一组彼此平等的原子,
只能匹配原子表中一个字符。

1表示匹配除表内原子外的任意字符,通常称之为排除原子表。

此外,在原子表中可以使用连字符(-)连接一组按照ASCII码顺序排列的原子,可简化书写。

例子

'/[apj]sp/'             //  可以匹配asp、jsp、PSP三种,从原子表中仅选择一个作为原子
'/[^apj]sp/'            //   可以匹配除了asp、PSP、jsp三种以外的字符串,如xsp,ysp,zsp等
'/0[xX][0-9a-fA-F]/'    //   可以匹配一个简单的十六进制数,如0x2f、0X3AE或0x4aB等

5. 一些特殊字符和元字符作为原子

在正则表达式中,任何一个符号都可以作为原子使用,但如果该符号在正则表达式中有特殊意义,可以使用转义字符""
取消它的特殊意义,作为一个普通字符使用。 如 "\. \* \+ \? \( \<\>"

" "转义字符可以将有意义的字符转成没意义的字符,还可以将没意义的字符转为有意义的字符

元字符

元字符是用于构建正则表达式的具有特殊含义的字符,如"*"、"+"、"?"等。

元字符不能单独出现,必须用来修饰原子。

如果要在正则表达式中使用包含元字符本身,为了使其失去特殊含义,则必须在前面加上""进行转义

正则表达式的元字符

元字符                含义描述

\*                     匹配0次,1次或者多次其前面的原子

\+                   匹配1次或多次其前的原子

?                   匹配0次或者1次其前的原子

.                  匹配除了换行符外的任意一个字符

|                   匹配两个或多个分支选择

{n}                表示其前面的原子恰好出现n次

{n,}               表示其前面的原子出现不少于n次

{n,m}              表示其前面的原子至少出现n次,最多出现n次

^或\A              匹配输入字符串的开始位置(或在多行模式下行的开头,即紧随一换行符后)

$或\Z              匹配输入字符串(或者在多行模式下行的结尾,即紧随一换行符后)

\b                 匹配单词的边界

\B                 匹配除单词边界以外的部分

[]                 匹配方括号中指定的任意一个原子

[^]                匹配除方括号中的原子以外的任意一个字符,排除原子表

()                 匹配其整体为一个原子,即模式单元。可以理解为由多个单个原子组成的大原子

\xn                   匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。
                   例如,`"\x41"`匹配`A`。`"\x041"`则等价于`"\x04&1"`。正则表达式中可以使用ASCII编码。

1. 限定符

限定符用来指定正则表达式的一个给定原子必须要出现多少次才能满足匹配。

总共有"*""+""?""{n}""{n,}""{n,m}"六种限定符,他们之间的区别主要是重复匹配的次数不同。

其中"*""+""{n,}"限定符是贪婪的,因为它们会尽可能地匹配文字。

注意

元字符 "*" 表示0次、1次或多次匹配其前的原子,也可以使用"{0,}"完成同样的匹配

"+"可以使用"{1,}"表示,

"?"可以使用"{0,1}"表示。

2. 边界限制(断言)

用来限定字符串或单词的边界范围,以便获得更准确的匹配结果。元字符"^"("\A")"$"("\Z")
分别指字符串的开始于结束,而"\b"用于描述字符串中每个单词的前或者后边界,
与之相反的元字符"\B"表示非单词边界。

例如:

有一个字符串 "this is a test", 使用边界限制如下:

"/^this/"                匹配此字符是否以字符串"this"开始的,匹配成功

"/test$/"                匹配此字符是否以字符串"test"结束的,匹配成功

"/\bis\b/"                匹配此字符串中是否含有单词"is",因为在字符串"is"两边都需要有边界

"/\Bis\b/"                查找字符串"is"时,左边不能有边界而右边必须右边界,如"this"匹配成功

3. 句号(.)

在字符类以外,模式中的圆点可以匹配目标中的任何一个字符,包括不可打印字符。但不匹配换行符号(默认情况下),相当于"[^\n]"(UNIX系统)或者"[^\n\r]"(Windows系统)。

但是如果设定了模式修正符"s",则圆点也会匹配换行符。

处理圆点与处理^和$是完全独立的,唯一的联系是涉及换行

注意
1. `".*?"`或者`".+?"`组合来匹配除换行符以外的任何字符串。
   例如:`"/<b>.*?<\/b>/"`可以匹配以`<b>`,`</b>`标签开始于结束的任何不包括换行符的任意字符串 

4. 模式选择符(|)

竖线字符"|"用来分隔多选一模式,在正则表达式中匹配两个或更多的选择之一。

|的优先级是最低的,所以应在最后考虑其功能。

例如:LAMP | J2EE表示匹配LAMP,也可以匹配J2EE,由于|其优先级最低,所以并
不表示匹配'LAMP2EE'或者'LAMJEE'

也可以像这样使用 "/Linux|Apache|MySQL|PHP/",表示可以从中任意匹配一组。

5. 模式单元

模式单元是使用元字符"()"将多个原子组成大原子使用。

一个模式单元中的表达式将被优先匹配

例子:

'/(very)*good/' //可以匹配good、very good、very very good

6. 后向引用

后向引用是一个正则表达式中一个重要的应用点。

使用()标记的开始和结束的多个原子,不仅仅是一个独立的单元,也是一个子表达式(也称之为子模式)。

**对于一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,可以被捕获供

以后使用**。

所捕获的每个子匹配都按照正则表达式模式中从左之后所遇到的内容存储。存储子匹配的缓冲区编号从1开始,
连续编号直至最大99个表达式。

每个缓冲区都可以使用\n访问,其中n为一个便是特定缓冲区的一位或两位十进制数。

例如:"\1""\2""\3"等形式的引用,在正则表达式模式中使用时还需要在前面加上一个反斜线,
将反斜线再次转义,
例如:"\\1""\\2""\\3"等形式的引用。

如下所示:

'/^\d{4}\W\d{2}\W\d{2$}/'    // 这是一个匹配日期的格式,如2008-08/08或者2008/08-08 等
'/^\d{4}(\W)\d{2}\\1\d{2$}/'    // 这是一个匹配日期的格式,如2008-08-08或者2008/08/08 等

当你要使用模式单元而又不想存储匹配结果时,可以使用非捕获元字符"?:""?=""?!"来忽略对相关匹配的保存。

注意

在一些正则表达式中,使用非存储模式单元是必要的,可以改变其后向引用的顺序。

`/(Windows)(Linux)\\2OS/`          --- 使用"\2"再次引用第二个缓冲区中的字符串"Linux"
`/(?:Windows)(Linux)\\1OS/`        --- 使用"?:"忽略了第一个子表达式的存储,所以"\1"引用的就是"Linux"







7. 模式匹配的优先级

在使用正则表达式时,需要注意匹配的书序。通常相同优先级从左到右进行运算,不同优先级的运算先高后低。

** 顺 序 **    ** 元 字 符 **               ** 描 述 **

1                \                         转义符号

2                ()、(?:)、(?=)、[]        模式单元和原子表

3               *、+、?、{n}、{n,}、{n,m}  重复匹配

4               ^、$、\b、\B、\A、\Z        边界限制

5               |                           模式选择

模式修正符

模式修正符在正则表达式定界符之外使用(最后一个斜线"/"之后)。

模式修正符可以调整正则表达式的解释,扩展了正则表达式在匹配、替换等操作时的某些功能;
而且模式修正符可以组合使用,更增强了正则表达式处理能力

模式修正符可以单个使用,也可以多个组合使用

模式修正符

**模式修正符**                        **功能描述**

i                                 在和模式进行匹配时不区分大小写

m                                 将字符串视为多行。默认开的正则开始`"^"`和`"$"`将目标字符串作为单一的一"行"
                                  字符(甚至其中包含有换行符也是如此)。如果在修饰符中加上`"m"`,那么开始和结束将会指字符串的每一行,每一行的开头就是`"^"`,结尾就是`"$"`。

s                                 如果设定了次修正符,则模式中的圆点元字符`"."`匹配所有的字符,包括换行符。
                                  即将字符串视为单行,换行符作为普通字符看待。

x                                 模式中的空白忽略不计,除非它已经被转义。

e                                 **只有在`preg_replace()`函数中,在替换字符串对逆向引用做正常的替换,将其作为
                                  PHP代码求值,并且其结果来替换所搜索的字符串**

U                                 本修正符反转了匹配数量的值使其使其不是默认的重复,
                                  而变成在后面跟上`"?"`才变得重复。**__这和Perl语言不兼容__**。也可以通过在模式中设定(U)修正符或者数量符之后跟一个问号`"?"`(例如.*?)来使用此选项。

D                                 模式中的美元元字符仅匹配目标字符串的结尾。没有此选项时,如果最后
                                  一个字符是换行符,则美元符号也会匹配次字符之前的内容。如果设定了m修正符,则忽略此选项

注意:

  1. 模式 "/Web Server/ix"可以用来匹配字符串"webserver",忽略大小写和空白。
  2. 贪婪匹配,在匹配成功的前提下,尽可能多的去匹配*,+,{n,},.*是贪婪的,例如:/a.*e/去匹配字符串"abcd fsdfsdfsesfdfsdfsesdfedfsdfses",由于'.*'是贪婪的匹配,会从这个字符串中匹配出"abcd fsdfsdfsesfdfsdfsesdfedfsdfse",从第一个a开始,知道最后一个字母e结束,都属于'.*'内容。如果想取消这种贪婪模式,可以使用模式修正符"U"或者在模式中使用.*?,在.*后面加个?。为了兼容Perl正则函数,可能会没有模式修正符"U",我们建议使用在".*"后加"?"来实现懒惰模式(即非贪婪模式)如果"U""?"同时使用,像这样"/a.*?e/U",则匹配"abcdfsdfsdfsesfdfsdfsesdfedfsdfse",相当于又启用了贪婪模式。
  3. 惰性模式,在匹配成功的前提下,尽可能少的去匹配(注意点同上)
  4. 模式"/^is/m"可以匹配字符串"this\nis\nais\ntest"中的'is',因为使用模式修正符"m"将字符串视为多行,第二行出现的"is"匹配成功。默认的正则开始^和结束'$'将目标字符串作为单一的一“行”(甚至包含换行符也是如此)。

Note:

看到这里是不是懵逼了?没错,我也懵逼。
不过没有关系,秉承Learning by doing的准则,
在接下来的教程中,我会一一演示。

更多文章请访问我的博客:Noapes

实例

/**
 *  无乱码截取中文字符
 * @param $str
 * @param int $start
 * @param $length
 * @param string $charset
 * @param bool|true $suffix
 * @return string|void
 */

function msubstr($str, $start=0, $length, $charset="utf-8", $suffix=true)
{
    if(function_exists("mb_substr"))
        return mb_substr($str, $start, $length, $charset);
    elseif(function_exists('iconv_substr')) {
        return iconv_substr($str,$start,$length,$charset);
    }
    $re['utf-8']   = "/[x01-x7f]|[xc2-xdf][x80-xbf]|[xe0-xef][x80-xbf]{2}|[xf0-xff][x80-xbf]{3}/";
    $re['gb2312'] = "/[x01-x7f]|[xb0-xf7][xa0-xfe]/";
    $re['gbk']    = "/[x01-x7f]|[x81-xfe][x40-xfe]/";
    $re['big5']   = "/[x01-x7f]|[x81-xfe]([x40-x7e]|xa1-xfe])/";
    preg_match_all($re[$charset], $str, $match);
    $slice = join("",array_slice($match[0], $start, $length));
    if($suffix) return $slice;
    return $slice;
}

echo msubstr('哈哈哈你好啊啊',2,3,'gb2312');



  1. sdf

未进化的类人猿
101 声望2 粉丝

Talk is cheap,Show me the code.