必须掌握的正则表达式,快来对对笔记
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})(?
(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
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会让你的同事(以及需要工作在你的代码上的任何人)生气恼怒,甚至恨不得揍你一顿。
编辑|李世华
以上是关于必须掌握的正则表达式,快来对对笔记的主要内容,如果未能解决你的问题,请参考以下文章
javascript学习笔记-正则表达式-少写1000行代码的正则表达式