正则表达式必知必会

本文共有2571字,阅读耗费11分钟。本文首发于个人博客:http://tanlehua.top/posts/tech/others/learn-regex/

什么是正则表达式

一(fei)言(hua)蔽(shao)之(shuo),使用单个表达式来描述句法规则,用来匹配出符合这一句法规则的字符串。

使用场景

  1. 批量提取/替换有规律的字符串
  2. 用户输入的合法性验证
  3. 网络爬虫
  4. 此处省略六点……

善假于物

推荐大家使用RegexBuddy这个强大好用的神器来学习和使用正则表达式,文章底部有下载链接,有能力请支持正版。

元字符

正则表达式可以理解成一门描述语言,而元字符可以理解成不可再分的关键字(key word)。

测试栗子:

String in Java is very useful. What am I doing? I am building Structure. I am ingenIoUs!

Ring is on.

下文“任意”的意思可以理解为“所有”。

  • . 点号匹配除换行符以外的任意字符
  • \w 匹配任意字母/下划线/数字/汉字
  • \s 匹配出现的任意空格

  • \d 匹配数字

  • \b 匹配单词的开始或结束。比如ing/b匹配以ing结尾的单词,而\bing则匹配以ing开头的单词。

  • ^ 匹配字符串的开始

  • $ 匹配字符串的结束。
  • [abc] 匹配abc这几个字符。[0-5] 匹配0到5这几个数字。[a-z] 匹配所有小写的字母。[a-z0-9A-Z] 匹配所有的字母和数字。

说明:

^$的作用对象是单个字符串。字符串有两种划分方式,一种是把一段测试文本为一个完整的字符串,比如上面的栗子就是一个字符串;另一种是用回车换行符来划分字符串,比如当一段测试文本有2个回车换行符(有3行),那么这段文本就有3个字符串。而单词是用空格来划分的,这样字符串和单词的区别就很明显了吧

那么,若用回车换行符来划分字符串的模式,要想匹配上述的测试栗子的第一行,可以用^S.*!$,表达的意思是被匹配的字符串必须以’S’开头,中间允许有若干个字符,但结尾必须是’!’。

那它们与\b有什么区别呢?\b的作用对象是单个字符,比如我想匹配上述的测试栗子中以ing结尾的的单词,可以用\w*ing\b


反义

  • \W 匹配不是字母/下划线/数字/汉字的字符
  • \S 匹配任意的不是空格的字符
  • \D 匹配不是数字的字符
  • \B 匹配不是单词开头或结束的位置。
  • [^x]匹配除x以外的任意字符
  • [^asS]匹配除asS这几个字母以外的任意字符

重复模式

单靠元字符来做正则表达式匹配是远远无法满足我们对海量字符串中匹配所要字符串的需求的,所以需要学习使用正则表达式的重复模式。下面的一些控制重复的字符需要跟在匹配表达式的后面。

  • * 重复零次或多次。这是汇编原理里面关于星闭包的概念。 比如\w匹配的是字母/下划线/数字/汉字的单个字符,而\w*匹配的是任意长度的单词。
  • + 重复一次或多次。
  • ? 重复零次或一次。比如\b.?ing\b可以匹配上面栗子中Ring这个单词。
  • {n} 重复n次。
  • {n,} 重复n到正无穷次。
  • {n,m} 重复n次到m次。

贪婪与懒惰 Greedy&Lazy

这是两个非常重要的概念。

用默认的重复模式来匹配字符串用的是贪婪模式,它会尽可能多地匹配文本。

而在上述重复符号后面加上?即可启动懒惰模式(如*? +? ?? {n}?等),它会尽可能少地匹配文本。

比如:
a.*b (aababcc) -> aabab 贪婪模式遇到最后一个b时才完成匹配。

a.*?b (aababcc)-> aab 懒惰模式只要遇到b就完成匹配。

分支条件

|把每个条件分隔开,如(条件一)|(条件二) ,从左到右测试每个条件,如果满足就匹配,不满足则测试下一个条件直到测试完所有条件为止。

分组

Why:

有时我们需要整体匹配后,在其中进行局部的字符串提取,这时就需要用到分组了。比如在一个url字符串中,我需要提取出域名。这需要先匹配出”http://”头部,接着利用分组匹配提取出域名。

How:

使用()括号划分正则式来分组。

More:

  • (?<groupname>exp)进行分组命名。默认地,每个括号所划分的组都用数字索引标记,如果括号之间嵌套过多容易造成混乱,所以用自定义分组命名比较方便明了。
  • (?:exp)匹配文本但不为此分组编号。因为给分组编号或命名的目的是便于我们提取匹配出来的文本,但很多时候加括号并不是为了要局部把文本提取出来。

比如匹配Url的正则式:

\b(?<protocol>https?|ftp)://(?<domain>[-a-zA-Z0-9.]+)(?<file>/[-a-zA-Z0-9+&@#/%=~_|!:,.;]*)?(?<parameters>\?[a-zA-Z0-9+&@#/%=~_|!:,.;]*)?

其中(?<domain>[-A-Z0-9.]+) 就用到了分组匹配的方式提取域名,表达意思是字母或数字或’-‘或’.’出现一次或n多次的字符串,遇到’/’停止。

高阶玩法

  1. 向后引用

场景:有时需要将分组提取出来的文本在后面重复多次匹配。如”Good,very Good”需要匹配两次Good。

  • 可以用(Good).*\1 来匹配。即\groupNum 转义字符加上分组编号即可。
  • 也可以用(?<txt>Good).*\k<txt>来匹配。即\<groupName>转义字符加上分组名即可。

    1. 零宽断言

断言就是判断真假,只有断言为真时,正则表达式才会往下匹配。通常来说,用一般的正则式匹配出来的文本都是要占一定宽度的。如何理解?

比如,现在有一测试文本”Script JavaScript ShellScript Javac Java”。

要匹配”JavaScript”中的”Script”,若用\bJava这个断言的话,”Java” 就会在匹配出来的文本中占4个宽度。

怎么让”Java”不出现在匹配出的文本当中,又能表达作为匹配文本前缀的断言作用呢?零宽断言就是为此而生。

正向零宽断言:

  • (?<=\bJava)Script 匹配出以”Java”为前缀的”Script”。即”Script Java**Script** ShellScript Javac Java”。
  • Java(?=Script\b) 匹配出以”Script” 为后缀的”Java“。即”Script **Java**Script ShellScript Javac Java”。

注意,它们上面两种断言语法的细微差异,当原来的要占位断言表达式在前面,用(?<=exp)包住表达式;当原来的要占位的断言表达式在后面,用(?=exp)包住表达式。下面的负向零宽断言也差不多。

负向零宽断言:

  • Java(?!c) 匹配出后面不跟”c”的”Java”。即”Script Java**Script ShellScript Javac **Java“。
  • (?<!a)Script 匹配出前一个字符不是”a”的”Script”。 即”Script JavaScript Shell**Script** Javac Java”

实战

想必大家都用过Github,Github的repository的URL是有规律的:

https://github.com/{userName}/{repoName}/{...}

我们要做的很简单,就是匹配提取出userName和repoName。

测试文本:

  • “https://github.com/TellH/GitClub/blob/dev/app/build.gradle”
  • https://github.com/TellH/GitClub/
  • https://github.com/TellH/GitClub

    1. 先匹配前缀,很自然想到了https://github.com/ ,但别忘了对’.’转义,因为点号是元字符,即

https://github\.com/

  1. 先提取userName,要用分组匹配:

https://github\.com/(.*)/

咦,什么鬼,怎么匹配到了最后一个”/”。原来默认匹配模式是贪婪模式,需要改成懒惰模式:

https://github\.com/(.*?)/

  1. 接着再分组提取RepoName。

https://github\.com/(.*?)/(.*?)

发现,它懒惰过头了,竟然一个字符也不匹配。

我们来分析一下,RepoName后面如果还有内容,那必须后面紧跟着”/”,并且应该用懒惰模式。

而如果RepoName后面没有内容了,比如第三个测试文本,那就应该用贪婪模式,将剩余的全部字符作为RepoName。

因此,我们需要用分支条件:

https://github\.com/(.*?)/
(
   (
      (.*?)/.*   #RepoName后面如果还有内容,那必须后面紧跟着"/",并且应该用懒惰模式
   )
   |
   (.*)          #RepoName后面没有内容了,那就应该用贪婪模式,将剩余的全部字符作为RepoName
)

因为括号嵌套过多,我们可以用(?<groupname>exp)对感兴趣的分组进行命名,并且用(?:exp)屏蔽其他无用的分组:

https://github\.com/(?<userName>.*?)/
(?:
   (?:
      (?<repoName>.*?)/.*
   )
   |
   (?<repoName>.*)
)

值得注意的是,Java对分组命名的支持不太好,分组的命名不能有重复。而且各种编程语言对正则表达式的支持可能有所差异。

  1. 然而就这样结束了吗?还不够好,因为我们在第4行中的/.*把多余的”/”也匹配进来了,怎么样这些多余部分不占位呢?这时应该想到零宽断言了吧。
https://github\.com/(?<userName>.*?)/
(?:
   (?<repoName>.*?
      (?=/.*)      # 正零宽断言
   )
   |
   (?<repoName>.*)
)
  1. 大功告成!然而就这样结束了吗?其实据我了解,Github的UserName和RepoName中是不允许有”/”的,所以正则式只需简单地用:

https://github\.com/([^/]*)/([^/]*)

哈哈,有种被耍的感觉,但通过这个比较简单而全面栗子,我们算是掌握正则表达式的大部分用法了。

干货

正则表达式入门视频教程

RegexBuddy的安装包 ,有能力的请支持正版哈!

正则表达式必知必会的更多相关文章

  1. Html5 canvas实现粒子时钟的示例代码

    这篇文章主要介绍了Html5 canvas实现粒子时钟的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. 解析html5 canvas实现背景鼠标连线动态效果代码

    流行的动态背景连线特效。今天小编通过实例代码给大家解析html5 canvas实现背景鼠标连线动态效果,感兴趣的朋友一起看看吧

  4. PhoneGap / iOS上的SQLite数据库 – 超过5mb可能

    我误解了什么吗?Phonegap中的sqlitedbs真的有5mb的限制吗?我正在使用Phonegap1.2和iOS5.解决方法您可以使用带有phonegap插件的原生sqliteDB,您将没有任何限制.在iOS5.1中,Websql被认为是可以随时删除的临时数据…

  5. ios – 使用带有NodeJs HTTPS的certificates.cer

    我为IOS推送通知生成了一个.cer文件,我希望将它与NodeJSHTTPS模块一起使用.我发现HTTPS模块的唯一例子是使用.pem和.sfx文件,而不是.cer:有解决方案吗解决方法.cer文件可以使用两种不同的格式进行编码:PEM和DER.如果您的文件使用PEM格式编码,您可以像使用任何其他.pem文件一样使用它(有关详细信息,请参见Node.jsdocumentation):如果您的文件使

  6. ios – CFNetwork内部错误:URLConnectionLoader.cpp:289

    当我在一段时间后打开我的应用程序时,我收到了日志:440:CFNetworkinternalerror(0xc01a:/buildroot/Library/Caches/com.apple.xbs/Sources/CFNetwork/CFNetwork-758.4.3/Loading/URLConnectionLoader.cpp:289)它从未出现在过去.我的项目使用网络库AFNetworkin

  7. ios – 使用大写符号在字符串swift中获取URL的正则表达式

    我尝试在文本中获取URL.所以,在此之前,我使用了这样一个表达式:但是当用户输入带有大写符号的URL时(例如Http://Google.com,它与它不匹配)我遇到了问题.我试过了:但什么都没发生.解决方法您可以使用正则表达式中的i内联标志关闭区分大小写,有关可用正则表达式功能的详细信息,请参阅FoundationFrameworkReference.(?ismwx-ismwx)Flagsetti

  8. 在Xcode4中,你可以更改用于显示隐形字符的字符吗?

    我更喜欢VisualStudio显示隐形的方式……

  9. ios – 应用程序商店描述特殊字符

    是不是可以在AppStore描述中使用像星星这样的特殊字符了?我得到这个错误:描述不得包含标记语言.说明不得包含以下字符:★提前致谢:)解决方法仍然允许一些unicode字符.以下字符已经过测试并仍然有效:◆√至于现在他们工作正常,但苹果可以随时再次改变条件.

  10. ios – 将数组中的字符转换为整数

    即使我搜索了文档,我似乎无法弄清楚如何做到这一点.我试图弄清楚如何将数组中索引处的字符转换为整数.例如,假设我有一个名为“容器”的字符数组,我无法弄清楚该怎么做:谢谢您的帮助!解决方法Swift并不容易在原始和类型表示之间进行转换.这是一个在此期间应该有所帮助的扩展:这使您可以非常接近您想要的:对于遇到此问题的任何工程师,请参阅rdar://17494834

随机推荐

  1. 法国电话号码的正则表达式

    我正在尝试实施一个正则表达式,允许我检查一个号码是否是一个有效的法国电话号码.一定是这样的:要么:这是我实施的但是错了……

  2. 正则表达式 – perl分裂奇怪的行为

    PSperl是5.18.0问题是量词*允许零空间,你必须使用,这意味着1或更多.请注意,F和O之间的空间正好为零.

  3. 正则表达式 – 正则表达式大于和小于

    我想匹配以下任何一个字符:或=或=.这个似乎不起作用:[/]试试这个:它匹配可选地后跟=,或者只是=自身.

  4. 如何使用正则表达式用空格替换字符之间的短划线

    我想用正则表达式替换出现在带空格的字母之间的短划线.例如,用abcd替换ab-cd以下匹配字符–字符序列,但也替换字符[即ab-cd导致d,而不是abcd,因为我希望]我如何适应以上只能取代–部分?

  5. 正则表达式 – /bb | [^ b] {2} /它是如何工作的?

    有人可以解释一下吗?我在t-shirt上看到了这个:它似乎在说:“成为或不成为”怎么样?我好像没找到’e’?

  6. 正则表达式 – 在Scala中验证电子邮件一行

    在我的代码中添加简单的电子邮件验证,我创建了以下函数:这将传递像bob@testmymail.com这样的电子邮件和bobtestmymail.com之类的失败邮件,但是带有空格字符的邮件会漏掉,就像bob@testmymail也会返回true.我可能在这里很傻……当我测试你的正则表达式并且它正在捕捉简单的电子邮件时,我检查了你的代码并看到你正在使用findFirstIn.我相信这是你的问题.findFirstIn将跳转所有空格,直到它匹配字符串中任何位置的某个序列.我相信在你的情况下,最好使用unapp

  7. 正则表达式对小字符串的暴力

    在测试小字符串时,使用正则表达式会带来性能上的好处,还是会强制它们更快?不会通过检查给定字符串的字符是否在指定范围内比使用正则表达式更快来强制它们吗?

  8. 正则表达式 – 为什么`stoutest`不是有效的正则表达式?

    isthedelimiter,thenthematch-only-onceruleof?PATTERN?

  9. 正则表达式 – 替换..与.在R

    我怎样才能替换..我尝试过类似的东西:但它并不像我希望的那样有效.尝试添加fixed=T.

  10. 正则表达式 – 如何在字符串中的特定位置添加字符?

    我正在使用记事本,并希望使用正则表达式替换在字符串中的特定位置插入一个字符.例如,在每行的第6位插入一个逗号是什么意思?如果要在第六个字符后添加字符,请使用搜索和更换从技术上讲,这将用MatchGroup1替换每行的前6个字符,后跟逗号.

返回
顶部