适合懒惰开发人员的正则表达式
正则表达式是一种基于特殊模式符号系统的文本处理系统。简而言之,它为程序员提供了轻松处理和验证字符串的能力。它体现了 DRY(不要重复自己)原则,在几乎所有支持的语言中,正则表达式模式都不会改变形式。
后端和前端应用程序的代码将完全相同,从而节省团队实现相同功能的时间。值得强调的是,该模块非常适合处理大型或复杂的字符串,因此可以轻松快速地解决与它们相关的问题。
无论是在厨房喝茶,还是在团队 Zoom 会议中,你都会听到有人说正则表达式很难学、很难写、很难读,而且通常都是一些糟糕的人发明的😈。但事实真的如此吗?让我们来一探究竟。
注意:
本文适合那些认为正则表达式复杂、难以理解的人以及那些认为基础知识足以胜任工作的人。
它是什么样子的
以下是6种编程语言中用于确定俄罗斯电话号码的示例。
在这个例子中,您可以立即注意到 Regex 模块的第一个特性:条件模式将完全相同,并且您可以轻松地与使用其他编程语言编写的团队共享代码。在不同团队之间快速“摸索”代码库的能力节省了功能开发和实现的时间。
出现历史
正则表达式最早出现在20世纪50年代中期关于自动机理论和形式语言理论的科学论文中。Stefan Cole Kleen被誉为第一个提出正则表达式概念的人。
他在作品中提出的原则和思想被肯·汤普森切实地实现了,并以他灵巧的手法融入到了 Perl 语言中。
根据定义,正则表达式是编程语言的一个模块,用于搜索和操作文本。
正则表达式语言不是一种成熟的编程语言,尽管与其他语言一样,它有自己的语法和命令。
哪些编程语言支持它们?
列表很长,这里仅列举其中几种:
- 碳
- C#
- C++
- 科博尔
- 德尔福
- F#
- 去
- Groovy
- 哈斯克尔
- Java
- JavaScript
- 朱莉娅
- 科特林
- MATLAB
- Objective-C
- PHP
- Perl
- Python
- R
- 红宝石
- 锈
- Scala
- 迅速
- Visual Basic
- Visual Basic .NET
- ...
功能
- 输入数据的模式匹配。
- 通过模板搜索和更改输入数据。
- 返回输入字符串中的第一个或所有结果。
- 与常规搜索的结果一起返回,搜索时返回命名而不是子字符串。
- 传递后替换输入字符串中的字符、单词、短语。
- 最重要的是,编写一次,随处使用。
它在哪里有用?
- 在 IDE(VS Code、Rider、CLion、VS)中按模式搜索和替换代码
- 验证字符串的模式匹配(文件扩展名)。
- 验证前端字段(电子邮件、电话号码和其他)。
- 请求和响应数据的验证。
- 验证巨大的字符串,然后获取必要的文本片段,而无需花费大量时间。
基本语法^
- 字符串的开始(意味着输入字符串必须以其后的下一个字符开始。如果您不知道输入字符串的第一个字符,则不适用)。
$
- 字符串结尾(意味着此字符之前的所有条件将成为输入字符串的最终结果,并且在此字符之后没有其他结果。如果您想从输入字符串中返回多个结果,则不适用)。
*
- 表示给定符号之前的条件可能出现一次或多次,或者根本不出现(分别地,它可能重复)。
+
- 表示该符号之前的条件必须出现一次或多次(分别可以重复)。
[a-z]
- 枚举输入字符串中的有效字符,即它可以是任何小写拉丁字母(a 或 b 或 c ... 或 x 或 y 或 z)。
[0-9]
- 枚举输入字符串中的有效字符,即它可以是任何小写拉丁字母(1 或 2 或 3 ... 或 7 或 8 或 9)。
.
- 任何单个字符。
\
- 选择任何特殊字符。
|
– 或逻辑运算(该操作数左边的条件或右边的条件必须满足)
语法简化
\d
≡ [0-9]
- 0 到 9 之间的任意字符\D
≡ [^0-9]
- 除数字之外的任意字符\w
≡ [a-zA-Z0-9_]
- 任意拉丁字符、所有数字和“_” \W
≡ [^a-zA-Z0-9_]
- 除拉丁字符、数字和“_”之外的任意字符\s
≡ [ ]
- 仅空格\S
≡ [^ ]
- 除空格之外的任意字符
条件长度
除了验证字符串中的值之外,我们还可以指定有多少个字符符合同一条件。长度条件只有三种可能性:{3}
- 条件所需的字符数{3.5}
- 条件的最小和最大字符数{3,}
- 强制最小数量和无限最大数量
注意:该条件[0-9]
可以用缩写代替\d
与团体合作(高级)
这会有点棘手,所以做好准备。
()
- 创建匿名组(创建子字符串并为其分配内存)(?‘nameGroup’)
- (?<nameGroup>)
– 创建命名字符串(\k<nameGroup>)
- 用于摆脱重复代码的模式,因此,如果您有一个带有某些条件的命名组“nameGroup”,则不能在模式中写入第二个组,而只需将此指令与仅指示之前描述过的组名称的正则表达式一起使用。因此,条件将重复,您无需再次描述它。(?:)
- 在条件的逻辑括号中选择,无需命名和创建子字符串(<=)
- 排除括号内的条件,不将其包含在选择中。(?!)
- 检查括号内的条件,不将其包含在选择中。
现实生活中的例子
有一次,在工作中,我需要解析打印在支票上的二维码数据,这些支票用于购买/退回各种商品、服务等。解析器的第一个版本是在 C# 后端编写的。解析器的代码库大约有 150 行代码,它没有考虑到各种财务登记器(打印支票并向联邦税务局发送数据的设备)的某些功能。要更改此功能,必须仔细查看、检查每一行代码。后来,选项变得非常多,需要在前端使用它进行验证。因此,我们决定使用正则表达式重写它,以简化解析器,并使其能够轻松快速地移植到其他编程语言。
目标:
- 解析输入值以进行模式验证
- 记录购买日期和金额等必要字段,以供系统进一步使用。
- 检查字段“n”始终等于 1(0 - 退货,1 - 购买)
以下是输入数据的示例:t=20181125T142800&s=850.12&fn=8715000100011785&i=86841&fp=1440325305&n=1
此类数据解析的正则表达式:^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$
代码示例(C#):
private static (string date, string sum) parseQRCode(string data)
{
var pattern = new Regex(@"^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$", RegexOptions.ECMAScript);
var matchResult = pattern.Match(data);
if (!matchResult.Success)
throw new ArgumentException("Invalid qrCode");
var dateGroup = matchResult.Groups["Date"];
if(!dateGroup.Success)
throw new ArgumentException("Invalid qrCode, Date group not found");
var sumGroup = matchResult.Groups["Sum"];
if(!sumGroup.Success)
throw new ArgumentException("Invalid qrCode, Sum group not found");
return (dateGroup.Value, sumGroup.Value);
}
代码示例(Typescript):
此选项是通过异常进行的,但可以通过返回 false 或返回 null 来完成。
const parseQRCode = (data:string) : {date: string, sum: string} => {
const pattern = new RegExp("^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$");
const matchResult = pattern.exec(data);
if (!matchResult)
throw "Invalid qrCode";
const dateGroup = matchResult[1];
if(!dateGroup)
throw "Invalid qrCode, Date group not found";
const sumGroup = matchResult[2];
if(!sumGroup)
throw "Invalid qrCode, Sum group not found";
return {date: dateGroup, sum: sumGroup};
};
在输出处,我们得到两个值:
- 日期 - 指示购买日期和时间的字段(只需解析它并将其转换为日期对象)
- 总计 - 购买金额
现在让我们更详细地分析一下这个模式:
^
- 表示一行的开始t=(?<Date>[0-9-:T]+)
– 必需字符 t=(以下为一个或多个实例中的任意字符(从 0 到 9 或 - 或 : 或 T))&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)
– 必填字符&s=
– 所需的字符序列&
和s
和=
[0-9]+
(一个或多个实例中的字符 0 至 9)(?:\.[0-9]{2})?
.
- 非必需组以 2 个数字的符号开头
$
- 表示行尾&fn=[0-9]+
– 必需字符,&fn=
后跟[0-9]+
->(一个或多个实例中为 0 到 9 之间的任意数字)&i=[0-9]+
– 必需字符,&i=
后跟[0-9]+
->(一个或多个实例中为 0 到 9 之间的任意数字)&fp=[0-9]+
– 必需字符,&fp=
后跟[0-9]+
->(一个或多个实例中为 0 到 9 之间的任意数字)&n=1
– 必填字符&n=1
使用非拉丁字母的问题
当您需要使用整个拉丁字母时,只需写[a-zA-Z]
。许多人认为使用西里尔字母时,写 就足够了[а-яА-Я]
。看起来一切都合乎逻辑,一切都很好,但在某些时候您会发现它有时无法正常工作。问题在于范围[а-я]
不包含字母“ё”,因此,您需要将模式从 更改为 ,[а-яА-Я]
以便[а-яёА-ЯЁ]
代码考虑字母表中的特定字母。这个问题不仅存在于西里尔字母中,也存在于希腊语、土耳其语、中国和其他一些语言中。编写使用这些语言的模式时要小心。
JS 正则表达式标志
- 全局(g)- 找到第一个匹配项后不会停止搜索。
- 多行(m)-搜索包括换行符的行(^ 行首,$ 行尾)。
- 不敏感(i) - 不敏感搜索(a ≡ A)
- sticky (y) - 搜索除了返回匹配项之外,还返回从子选择匹配项开始的索引(IE 不支持)
- unicode (u) - 搜索包含 unicode 字符(IE 不支持)
- 单行 - 在此模式下,符号
.
还包括换行符(Chrome、Opera、Safari 支持)
C# 中的附加正则表达式设置
RegexOptions 会作为 Regex 类构造函数中的附加参数公开。它也可以在 Match、Matches 方法中指定。
- 无-默认设置。
- IgnoreCase (\i) - 不区分大小写地检查。
- 多行(\m)- 使用包含连字符 \n 的行。
- ExplicitCapture (\n) - 仅将命名组添加到结果中。
- 已编译(仅在静态版本中有用,加快正则表达式的速度,减慢编译速度)。
- 单行(该
.
符号将匹配除 \n 之外的任何字符,并在搜索时忽略它) - IgnorePatternWhitespace (\x) . (删除所有空格,但构造 [],{} 除外)
- RightToLeft-从右到左搜索。
- ECMAScript(类似 JS 的版本,但样式分组与 .NET 中的相同)。
- CultureInvariant(比较忽略键盘布局)。
良好实践和优化技巧
- 分组越少,执行速度越快。如果不需要,请尽量避免使用它们。
- 使用缩写(
\d
、\w
等)时,请确保它们完全匹配您的搜索词。最好再检查一遍。 - 如果经常使用正则表达式,则全局创建一次,从而减少重复代码量。
- 几乎所有地方都存在编译正则表达式的可能性,这通常可以优化你的表达式并加快其执行速度。但是,在验证之后使用它们,可以加快你的代码速度。
- 尝试减少特殊符号选择的数量(
\
),此功能会降低许多编程语言的执行速度。 - 正则表达式支持 UTF 字符编码。在某些情况下,这会提高性能,但会降低可读性。如果您决定使用它们,请确保您的决定得到团队的认可,并且这样做是值得的。
结论:
正则表达式看似复杂,但实际上,它提供的功能提供了许多机会,可以简化和加快每个人(从初级到高级/主管)的工作。
如果您有任何疑问,请随时发表评论,我们可以与您讨论。
链接
PS:别忘了一条重要规则:“编程仍然很酷。”祝你工作愉快。
文章来源:https://dev.to/sineni/regex-for-lazy-developers-cg1