必须掌握的正则表达式,快来对对笔记

Posted 弘博软件教育

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了必须掌握的正则表达式,快来对对笔记相关的知识,希望对你有一定的参考价值。

什么是正则表达式?

    正则表达式(或Regex,或Regexp)是使用字符序列描述复杂搜索模式的一种方式。

然而,专门的Regex语法由于其复杂性使得有些表达式变得不可访问。例如,下面的这个基本的正则表达式,它表示24小时制HH / MM格式的时间。

\b([01]?[0-9]|2[0-3]):([0-5]\d)\b

    如果你觉得这看上去略显复杂,别担心,当我们完成这个教程时,理解这个表达式将会是小菜一碟。

Learn once, write anywhere

    几乎任何编程语言都可以使用Regex。Regex的知识对于验证用户输入,与Unix shell进行交互,在你喜欢的文本编辑器中搜索/重构代码,执行数据库文本搜索等等都非常有用。

在本教程中,我将尝试在各种场景、语言和环境中对Regex的语法和使用进行简明易懂的介绍。

    此Web应用程序是我用于构建、测试和调试Regex最喜欢的工具。我强烈推荐大家使用它来测试我们将在本教程中介绍的表达式。


0 – 匹配任何数字行

    我们将从一个非常简单的例子开始——匹配任何只包含数字的行。

^[0-9]+$

让我们一点一点的解释吧。

  • ^ ——表示一行的开始。

  • [0-9] ——匹配0到9之间的数字

  • + ——匹配前一个表达式的一个或多个实例。

  • $ ——表示行尾。

很简单,不是吗?

    我们可以用\d替换[0-9],结果相同(匹配所有数字)。

    这个表达式(和一般的正则表达式)的伟大之处在于它无需太多修改,就可以用到任何编程语言中。

    为了演示,我们先快速了解如何使用16种最受欢迎的编程语言对文本文件执行此简单的Regex搜索。

    我们使用以下输入文件(test.txt)为例。

1234

abcde

12db2

5362

    每个脚本都将使用这个正则表达式读取并搜索test.txt文件,并将结果('1234', '5362', '1')输出到控制台。

Java语言范例

import java.util.regex.Matcher;import java.util.regex.Pattern;import java.io.IOException;import java.nio.file.Files;import java.nio.file.Paths;import java.util.ArrayList;

class FileRegexExample {

public static void main(String[] args) {

    try {

        String content = new         String(Files.readAllBytes(Paths.get("test.txt")));

        Pattern pattern = Pattern.compile("^[0-9]+$", Pattern.MULTILINE);

        Matcher matcher = pattern.matcher(content);

        ArrayListmatchList = new ArrayList();


        while (matcher.find()) {

        matchList.add(matcher.group());

        }


        System.out.println(matchList);

    } catch (IOException e) {

    e.printStackTrace();

        }

    }

}

1 – 年份匹配

我们来看看另外一个简单的例子——匹配二十或二十一世纪中任何有效的一年。

\b(19|20)\d{2}\b

我们使用\b而不是^和$来开始和结束这个正则表达式。\b表示单词边界,或两个单词之间的空格。这允许我们在文本块(而不是代码行)中匹配年份,这对于搜索如段落文本非常有用。

  • \b ——字边界

  • (19|20) ——使用或(|)操作数匹配’19′或’20′。

  • \d{2}——两位数,与[0-9]{2}相同

  • \b ——字边界

1.0 – 真实示例 – 计数年份

我们可以在Python脚本中使用此表达式来查找维基百科历史部分的文章中提及20或21世纪内年份的次数。

import reimport urllib.requestimport operator

# Download wiki page

url = "https://en.wikipedia.org/wiki/Diplomatic_history_of_World_War_II"

html = urllib.request.urlopen(url).read()

# Find all mentioned years in the 20th or 21st century

regex = r"\b(?:19|20)\d{2}\b"

matches = re.findall(regex, str(html))

# Form a dict of the number of occurrences of each year

year_counts = dict((year, matches.count(year)) for year in set(matches))

# Print the dict sorted in descending orderfor year in sorted(year_counts, key=year_counts.get, reverse=True):

 print(year, year_counts[year])

上述脚本将按照提及的次数依次打印年份。

1941 137

1943 80

1940 76

1945 73

1939 71

...

2 – 匹配时间

    现在我们要定义一个正则表达式来匹配24小时格式(MM:HH,如16:59)的任何时间。

\b([01]?[0-9]|2[0-3]):([0-5]\d)\b

  • \b——字边界

  • [01]——0或1

  • ?——表示上述模式是可选的。

  • [0-9]—— 0到9之间的任何数字

  • |——OR操作数

  • 2[0-3]——2,后面跟0和3之间的任何数字(即20-23)

  • :——匹配:字符

  • [0-5]——0到5之间的任何数字

  • \d——0到9之间的任何数字(与[0-9]相同)

  • \b ——字边界

    你可能已经注意到上述模式中有了新内容—— 我们在括号 ( ... )中封装小时和分钟的捕获片段。这允许我们将模式的每个部分定义为捕获组。

捕获组允许我们单独提取、转换和重新排列每个匹配模式的片段。

2.1 – 真实示例 – 时间分析

    例如,在上述24小时模式中,我们定义了两个捕获组—— 时和分。

    我们可以轻松地提取这些捕获组。

    以下是我们如何使用javascript将24小时制的时间分解成小时和分钟。

const regex = /\b([01]?[0-9]|2[0-3]):([0-5]\d)/const str = `The current time is 16:24`const result = regex.exec(str)console.log(`The current hour is ${result[1]}`)console.log(`The current minute is ${result[2]}`)

第0个捕获组始终是整个匹配表达式。

上述脚本将产生以下输出。

The current hour is 16The current minute is 24

作为额外的训练,你可以尝试修改此脚本,将24小时制转换为12小时制(am/pm)。

3 – 匹配日期

    现在我们来匹配一个DAY/MONTH/YEAR样式的日期模式。

\b(0?[1-9]|[12]\d|3[01])([\/\-])(0?[1-9]|1[012])\2(\d{4})

    这个有点长,但它看起来与我们上面讲过的有些类似。

  • (0?[1-9]|[12]\d|3[01])——匹配1到31之间的任何数字(前面的0是可选的)

  • ([\/\-])——匹配分隔符/或-

  • (0?[1-9]|1[012])—— 匹配1到12之间的数字

  • \2——匹配第二个捕获组(分隔符)

  • \d{4}——匹配任意4位数(0000 – 9999)

3.0 – 捕获组替换

    通过使用捕获组,我们可以动态地重组和转换我们的字符串输入。

    引用捕获组的标准方法是使用$或\符号,以及捕获组的索引(请记住捕获组元素是完整的捕获文本)。

3.1 – 真实示例 – 日期格式转换

    假设我们的任务是将使用国际日期格式(DAY/MONTH/YEAR)的文档集合转换为美式(MONTH/DAY/YEAR)日期样式。

    我们可以通过替换模式$3$2$1$2$4或\3\2\1\2\4使用上述正则表达式。

    让我们分解捕捉组。

  • $1——第一个捕获组:日期。

  • $2——第二个捕捉组:分隔符。

  • $3——第三个捕获组:月份。

  • $4——第四个捕获组:年份。

    以下是我们如何在Javascript中进行这种转换:

const regex = /\b(0?[1-9]|[12]\d|3[01])([ \/\-])(0?[1-9]|1[012])\2(\d{4})/const str = `Today's date is 18/09/2017`const subst = `$3$2$1$2$4`const result = str.replace(regex, subst)console.log(result)

    上述脚本将打印Today's date is 09/18/2017到控制台。

    同样的脚本在Python中是这样的:

import re

regex = r'\b(0?[1-9]|[12]\d|3[01])([ \/\-])(0?[1-9]|1[012])\2(\d{4})'

test_str = "Today's date is 18/09/2017"

subst = r'\3\2\1\2\4'

result = re.sub(regex, subst, test_str)

print(result)

4 – 电子邮件验证

正则表达式也可用于输入验证。

^[^@\s]+@[^@\s]+\.\w{2,6}$

  • ^——输入开始

  • [^@\s]——匹配除@和空格\s之外的任何字符

  • +——1+次数

  • @——匹配’@'符号

  • [^@\s]+——匹配除@和空格之外的任何字符,1+次数

  • \.——匹配’.'字符。

  • \w{2,6}——匹配任何字符(字母,数字或下划线),2-6次

  • $——输入结束

假设我们要创建一个简单的Javascript函数以检查输入是否为有效的电子邮件。

 const regex = /^[^@\s]+@[^@\s]+\.\w{2,6}$/g;

 const result = regex.exec(input)


 // If result is null, no match was found

 return !!result

}

const tests = [

 `test.test@gmail.com`, // Valid

 '', // Invalid

 `test.test`, // Invalid

 '@invalid@test.com', // Invalid

 'invalid@@test.com', // Invalid

 `gmail.com`, // Invalid

 `this is a test@test.com`, // Invalid

 `test.test@gmail.comtest.test@gmail.com` // Invalid

]

此脚本的输出应为[ true, false, false, false, false, false, false, false ]。

5 – 匹配网址

    另一个非常有用的Regex是在文本中匹配URL。

    下面是一个来自Stack Overflow的URL匹配表达式的示例。

(https?:\/\/)(www\.)?(?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6})(? \/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?

  • (https?:\/\/)——匹配http(s)

  • (www\.)?——可选的“www”前缀

  • (?[-a-zA-Z0-9@:%._\+~#=]{2,256}——匹配有效的域名

  • \.[a-z]{2,6})——匹配域扩展扩展名(即“.com”或“.org”)

  • (?

    \/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?——匹配URL路径(/posts)、查询字符串(?limit=1)和/或文件扩展名(.html),这些都是可选的。

    你注意到没有,一些捕获组现在以?标识符开头。这是命名捕获组的语法,可以使得数据提取更加清晰。

5.1 – 真实示例 – 从Web页面上的URL解析域名

以下是我们如何使用命名捕获组来提取使用Python语言的网页中每个URL的域名。

import reimport urllib.request


html = str(urllib.request.urlopen("https://moz.com/top500").read())

regex = r"(https?:\/\/)(www\.)?(?P[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6})(?P \/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?"

matches = re.finditer(regex, html)

for match in matches:

 print(match.group('domain'))

脚本将打印在原始网页HTML内容中找到的每个域名。

...facebook.comtwitter.comgoogle.comyoutube.comlinkedin.comwordpress.orginstagram.compinterest.comwikipedia.orgwordpress.com

...

6 – 什么时候不使用Regex

    好的,知道Regex是一个强大又灵活的工具了吧?!那么,有没有应该避免编写Regex的时候?有!

6.0 – 语言解析

    解析结构化语言,从英语到Java到JSON,使用正则表达式都是一种真正的痛苦。

    当数据源中的边缘情况或次要语法错误导致表达式失败时,将导致最终(或即时)的灾难,出于此目的去编写你自己的正则表达式可能会让你心情沮丧。

    强化的解析器几乎可用于所有机器可读的语言,而NLP工具可用于人类语言——我强烈建议你使用其中一种,而不是尝试编写自己的语言。

6.1 – 安全 – 输入过滤和黑名单

    使用Regex过滤用户输入(例如来自Web表单),以及防止黑客向应用程序发送恶意命令(例如SQL注入),看上去似乎很诱人。

    在这里使用自定义的Regex是不明智的,因为它很难覆盖每个潜在的攻击向量或恶意命令。例如,黑客可以使用替代字符编码绕过编写得不全面的输入黑名单过滤器。

    这是另一个实例,对此我强烈建议你使用经过良好测试的库和/或服务,以及使用白名单而不是黑名单,以保护你的应用程序免受恶意输入。

6.2 – 性能密集的应用程序

6.3 – 对于不需要Regex的地方

    正则表达式是一个非常有用的工具,但这并不意味着你应该在任何地方使用它。

    如果问题有替代的解决方案,解决方案更简单和/或不需要使用Regex,那么请不要只是为了显摆而使用Regex。Regex很棒,但它也是最不可读的编程工具之一,而且很容易出现边缘情况和bug。

    过度使用Regex会让你的同事(以及需要工作在你的代码上的任何人)生气恼怒,甚至恨不得揍你一顿。


编辑|李世华        

以上是关于必须掌握的正则表达式,快来对对笔记的主要内容,如果未能解决你的问题,请参考以下文章

python笔记--2--字符串正则表达式

javascript学习笔记-正则表达式-少写1000行代码的正则表达式

js正则怎么判断一个字符串里必须包含大写字母,小写字母,数字,特殊字符? 看清楚了,是必须包含,求教

正则表达式入门以及高阶学习教程

一文掌握字符串之正则表达式,值得收藏!

正则表达式学习笔记——常用的20个正则表达式校验