Skip to content

理解正则表达式 #11

Description

@Lucifier129

理解正则表达式

在我初学正则表达式的时候,走了一些弯路,强行记忆了很多符号和用法。

等到我有更深入的理解的时候我发现,从翻译和概念的角度上切入,学习起来会顺畅得多。

本文以JavaScript里的正则表达式为例,讲解其中的关键要素。希望能帮助到初学者。

注:也只限于阐述关键要素,不会事无巨细地展开。

何为正则表达式?

在中文语境里,「正则」两个字有点让人发怵,仿佛高深数学或物理中的「正则化」和「归一化」,抽象而难懂。

其实放到英文里,它是regular expression,而regular有「规律、规范、整齐、合格、正规」等意味,「正则」只是其中一种翻译。

不把它翻译成一个词组,而翻译成一句话,大致是:表达规范和规则的句子。

这里的规范和规则,指的是一个字符串的形式规则。

至于JavaScript里的 RegExp 构造函数,是Regular Expression的前三个字母缩写。

正则表达式的格式

在JavaScript里,正则表达式有两个构造方式,一个是通过RegExp这个构造函数创建实例,另一个是正则表达式字面量写法。

var regexp1 = new RegExp('hello regular expression')
var regexp2 = /hello regular expression/

// test 方法,测试给定的字符串是否符合正则表达式所描述的字符串格式
regexp1.test('hello regular expression') // -> true
regexp1.test('hello word') // -> false

// exec 方法,是 execute 这个单词的缩写,「执行」。返回给定的字符串中符合「正则表达式所描述的字符串格式」的部分
regexp2.exec('hello regular expression') //  返回 'hello regular expression'
regexp2.exec('hello regular expression, more words') //  只返回 'hello regular expression',其它部分不匹配

正则表达式里的元字符

元字符听起来也很抽象,其实换个例子就容易理解:学习如何学习,叫元学习;关于知识的知识,叫元知识。

元字符,则是描述字符的字符,比如,数字,字母,空格,换行等。

元编程,就是能生成代码的代码,在 Javascript 构造符合语法的字符串,放到eval(code)里运行一下,你就在元编程了。

然后看看元字符、元编程的英文:meta-character与meta-programming,对 meta 长个记性,怯魅。

列举几个元字符。元字符大多以反斜杠开头 \,因为前面展示的「正则表达式字面量」写法里,用的是两个斜杠包裹,所以得用反斜杠或其他标识符。

  • \d,匹配单个数字;d 是 digit 这个单词的缩写,它的中文意思就是「数字」
  • \w,匹配单个单词字符,w 是 word 的缩写,就是字母 a-z,数字 0-9,不包括逗号、句号、加减乘除号、括号等。
  • \s,匹配单个空白字符,s 是 space 的缩写,就是空白的意思。
  • \n,匹配换行符,n 是 newline 的缩写,中文就是换行。
  • \r,匹配回车符,r 就是 return 的缩写,回车在这里就是它的中文意思。
  • \t,匹配制表符,就是 tab 键打出来的一串用以缩进的空白字符,tab 是 tabel 的缩写,table 就有表格和制表的意思。
  • \b,匹配单词边界,b 是 boundary 的缩写,中文就是边界的意思。

如你所见,所谓的元字符,就是反斜杠加单词缩写,来表征某个字符类型。这就是它们的设计原则。

正则表达式里的量词

元字符大多只能表示单个字符的类型。

我们还需要量词,以表示「有,有0到多个,有至少一个,有n个以上,有n到m个,以某个字符开头,以某个字符结尾等」。

这时你可以停下来,稍作思考,让你来设计,你会设计成什么样?

Javascript的设计如下:

  • n+,至少1个 n 类型的字符
  • n*,0到多个 n 类型的字符
  • n?,0 或 1 个 n 类型的字符
  • n{X},X 个 n 类型的字符
  • n{X,Y},X 到 Y 个 n 类型的字符
  • n{X,},至少 X 个 n 类型的字符
  • n$,以 n 类型的字符结尾
  • ^n,以 n 类型的字符开头

如你所见,大致是一些类似数学里表达区间的意思。

正则表达式里的表达式

你可以戏谑地说它是「元表达式」。

其实,它们也是描述范围的,只是不是所有范围都是关于某个字符类型n 的数量和出现位置,有些范围跟多个字符组成的集合有关。

比如,在这几个字符类型之内,在这几个字符类型之外的,便利地表示 26 个字母,便利地表示 10 个数字字符。

Javascript的设计如下:

  • [abc],匹配单个字符,它是abc的集合的元素
  • [^abc],匹配单个字符,它不是abc的集合的元素
  • [0-9],匹配单个字符,它是从0到9这个集合的元素
  • [a-z],匹配单个字符,它是26 字母这个集合的元素
  • (red|blue|green),匹配多个连续字符,它是 red blue green 这三个词的集合的元素

小试牛刀

匹配一个电话号码,形式如 020-88813243。

简单版本,(开头)三个数字+一个横杠+八个数字(结尾),就是/^\d{3}-\d{8}$/。

需求变化,只匹配 020 开头的电话号码,就是/^020-\d{8}$/。

需求变化,支持分机,分机为 5 个数字,加后缀,就是/^020-\d{8}-\d{5}$/。

需求变化,电话号码可以是7个,用区间量词,就是/^020-\d{7,8}-\d{5}$/。

需求变化,有可能没有分机,用区间量词,中括号包裹住分机为一组,后面加个问号,表示0或多个,就是/^020-\d{7,8}(-\d{5})?$/。

需求变化,区隔符可能是横杠,也可能是星号或空格,用集合表达式,就是/^020[-*\s]\d{7,8}([-*\s]\d{5})?$/

结语

在我们理解了正则表达式的概念和设计思路之后,剩下的,就是查文档和寻找模式的工作了。

实在有难题,网上也可以搜索到现成的坚实的正则表达式可用。这里面的门道还是很多的,在此我们入个门,打个基础即可。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions