Categories: 在线文档

正则表达式的与或非

本文由Yurii原创,转载请注明来源: Life Sailor

本文链接 正则表达式的与或非


今天我的同事老赵 @jeffz_cn 问我,有没有办法用正则表达式匹配“不包含某个字符串”的文本,正好,我在写作的《正则表达式傻瓜书》中也提到了这类问题,就把这一节放出来,给大家参考,也希望大家多提建议(尤其是配图方面)。

正则表达式的与或非

我们都知道,写正则表达式有点像搭积木,复杂的功能总可以拆分开来,由不同的元素(也就是子表达式)对应,再用合适的关系将它们组合起来,就可以完成。在这一节,我们讲解常见的与或非关系的表达。

“与”是最简单的关系,它表示若干个元素必须同时相继出现,比如匹配单词cat,其实就是要求字符c、字符a和字符t必须同时连续出现。

正则表达式表达“与”关系非常简单,直接连续写出相继出现的元素就可以,我们可以想象,在各个元素之间,存在看不见的连接操作符·,比如上面匹配单词cat的正则表达式,就是『cat』,我们可以将它想象为『c·a·t』。

“与”关系也不限于字符之间,任何子表达式都可以用它来连接,如果我们把上面单词中的a替换为字符组『[au]』,表达式就变为『c[au]t』,你可以想象为『c·[au]·t』。

“或”是正则表达式灵活性的重要体现,我们可以规定某个位置的文本的“多种可能”,比如要匹配cat或是cut,在正则表达式看来,就是“字符c,然后是a或u,然后是t”。

如果“或”的多种可能都是单个字符(一般要求ASCII字符,中文字符等多字节字符的情况,可以参考本书专门论述的章节,此处仅以ASCII字符为例),就可以用字符组来表达“或”的关系,比如上面的cat或者cut的情况,正则表达式写做『c[au]t』,其原理如下:

更复杂的情况是“或”的多种可能,并非都是单个字符,有些可能是多个字符。比如,我们可以看一个更复杂的例子,不仅要匹配cut,还要匹配c开头、t结尾的单词chart、conduct和court。也就是说,在开头的c,结尾的t之间“可能”出现的是:uharonducour。

遇到这种情况,就不应使用字符组,而应当使用多选分支『(…|…)』,将各个“可能选项”列在多选分支中。于是,正则表达式变为『c(u|har|onduc|our)t』,其原理如下:

关于多选分支,还有两点要补充:

多选分支也可用于“每个选择都是单个字符”的情况,比如『c[au]t』写成『c(a|u)t』是没错的,但字符组的效率要远高于多选分支,所以,在这种情况下,推荐使用字符组『c[au]t』;

默认的多选分支『(…|…)』使用的括号是会捕获文本的,也就是说,括号内的表达式真正匹配成功的文本会记录下来,匹配完成之后可以提取出来,具体到上面的例子,就是我们有办法在匹配完成后“提取”出u或har或onduc或our。但许多时候,我们需要的只是整个表达式的匹配,而不关心“匹配时到底选择的哪种可能情况”,在这种情况下,我们稍加修改,使用“不捕获文本的括号”,可以提高效率。不捕获文本的写法也很简单,只是在开扩号之后加上字符『?:』,也就是『(?:…|…)』,具体到上面的例子,就应该写成『c(?:u|har|onduc|our)t』。这样做虽然繁琐点,但效率有保障,阅读起来也不困难,我推荐养成这种习惯,只要用到了括号,就想想是否真的要捕获括号内表达式匹配的文本,如果不需要,就是用不捕获文本的括号。

“非”看起来简单,其实是最复杂的,以下分几种情况讨论。

首先讨论针对字符的“非”:不容许出现某个或某几个字符。这是最简单的情况,直接用排除型字符组就可以对付,仍然用上面的例子,如果要匹配的单词是c开头、t结尾,中间有一个字符,但不能是u(也就是说,整个单词不能是cut),直接用『c[^u]t』就可以了,若中间的字符不能是a或u(也就是说,整个单词不能是cat或cut),则表达式改为『c[^au]t』。

如果你认真读过关于排除型字符组的章节,肯定会知道,这个表达式能匹配的只是cot之类的单词,因为中间的排除型字符组『[^au]』必须匹配一个字符。可是,如果我们还想要匹配chart、conduct和court,怎么办?最简单的想法是去掉排除型字符组的长度限制,改成『c[^au]+t』——不幸的是,这样行不通,因为这个表达式的意思是:c和t之间,是由多于一个“除a或u之外的字符“构成的,而chart、conduct和court,都包含a或u。

我们回头仔细看看这个“非”的逻辑,我们发现,其实我们要否定的是“单个出现的a或u”,而不仅仅是“出现的a或u”,所以才出现这样的问题,要解决这个问题,就应当把意思准确表达出来,变成“在结尾的t之前,不容许只出现一个a或u”。想到这一步,我们就可以用否定顺序环视『(?!…)』来解决了,它表示“在这个位置向右,不容许出现子表达式能够匹配的文本,我们把子表达式规定为『[au]t\b』(最后的『\b』很重要,它出现在t之后,保证t是单词的结尾子母)。

有了这点限制,匹配a和t之间文本的表达式就随意很多了,我们可以用匹配单词字符的简记法『\w』表示,于是整个表达式就变成了『c(?![au]t\b)\w+t』。请注意,这里出现的并不是排除型字符组『[^au]』,而是普通的字符组『[au]』,因为否定顺序环视『(?!…)』本身已经表示了“否定”的功能。

如果我们再进一步,“整个匹配文本中都不能出现字符串cat”,要怎么办呢?许多人的思路就是借鉴处理“或”关系的思路:既然字符组对应单个字符的情况,多选分支对应多个字符的情况,那么在否定时也是这样。可惜,正则表达式并没有提供与多选分支对应的“否定”结构,那么,应该怎么办呢?

解决的办法还是得依靠否定顺序环视——“整个匹配文本中都不能出现字符串cat”,换句话说,就是“在文本中的任意位置,向右,都不能出现该字符串”。因此,我们用两个锚点『^』和『$』,分别匹配整个字符串的开头和结尾位置,再用否定顺序环视『(?!cat)』表达“不能出现字符串cat”。

即便知道了原理,也不见得能写对正则表达式,比如『^(?!cat).+$』就是不正确的,因为它只限定了在文本的开头(也就是『^』)右边不能出现cat,而我们真正要做的是,在文本的每一个位置右边,都不能出现cat,所以应该改成『^((?!cat).)+$』;但这还说不上完美,根据前面提到的关于括号捕获的知识,因为此处并不需要括号捕获的文本,所以最好使用非捕获型括号『(?:…)』,最终我们得到的表达式就是『^(?:(?!cat).)+$』。

Yurii

Share
Published by
Yurii

Recent Posts

在德国, 全远程+共享空间办公,是什么体验?

注:原文发布于2023年1月16日 到1月份为止,我已经体验了几个月的全远程+共享空间办公了。有不少朋友听说之后很有兴趣,问我到底是什么感觉,所以我简单介绍下个人的体验。 背景 2019年末、2020年初开始在全球流行的Covid-19对远程办公来说,绝对是黑天鹅一般的存在。因为疫情导致的社交隔离措施,极大影响了各大公司的正常运转。 所幸,IT类公司受到的影响比较小,只要求员工“面对屏幕编程”,不必亲临现场。所以,许多IT公司也谨小慎微地开展了远程办公的试验。 从我所知道的结果来看,不少美国公司并不特别喜欢远程办公,比如Google,一旦社交隔离措施有所放松,就忙不迭要求员工回到办公室,盖因为公司认为远程办公严重影响合作效率。 与此相反,不少德国公司反倒是逐渐适应了远程办公的节奏,纷纷降低对员工“到办公室上班”的要求,许多公司甚至可以支持百分百的远程办公。 这里要提到的是,德国公司说的“远程办公”往往是货真价实的“远程”,而不是一些人理解的“家和办公室在同一个城市,只是不用去办公室”而已。 因为德国IT行业缺人严重,而且许多德国公司并没有那么“互联网”,而是依托实业开展业务,所以据我所知,目前不少公司非但没有裁员,反而都在大力招人。 (more…)

2 weeks ago

成年人找工作,不值得那么多愁善感

注:本文发布于2023年2月6日 最近硅谷几大公司都在裁员,看了些报道,被裁的员工真是不好过。损失经济来源不说,有些人还面临身份问题,这可真是屋漏偏逢连夜雨。 我也留意到,不少被裁的人会不停追问自己:为什么我会遇到这样的事情?为什么这样的不幸会降临到我头上?…… 实话说,我挺能理解这种态度。这挫折如此巨大,似乎又来得全无预兆,不由得让人对命运、对人生、对世界产生深重的怀疑。尤其是对已经走入社会,取得一定成就(如果非要抠字眼,那就用“进展”吧)的人来说,更是如此。 但是我更想说,如果被裁员了,当务之急是赶紧找到下一份工作,哪怕只是机械地行动。要知道,成年人找工作,容不下那么多愁善感。 我之所以这么说,是有切身经历为基础的。之前我讲过找德国工作的经历。最开始是信心十足的,因为虽然毕业多年,手艺没丢,基础还在,随时打开leetcode,中等难度题目基本都不在话下,不但能解对,解法也基本接近最优。既然网上都说“刷题就能找到工作”,估计自己应该没大问题。 没想到真的找起工作来,仍然充满了意想不到的挫折。如果不相信,我且举几个例子吧。 (more…)

3 weeks ago

我读《园丁与木匠》

虽然早就听说《园丁与木匠》是关于育儿的好书,但一直没开始读。最近终于翻开这本书,才发现属于“拿起就很难放下”的类型,加班加点读完,收获不少。 关于这本书的价值,已经有许多书评讨论过了,所以我想略过微言大义、长篇大论的叙述,谈谈我印象最深,也是最打动我的三点细节。 第一,儿童的学习方式 小孩子觉得拧螺丝很好玩,想自己动手拧一颗螺丝。于是,他打开了工具箱,对着琳琅满目的工具,他不知所措。一会儿摸摸钳子,一会儿试试扳手……这时候,旁边的父母应当怎么办? 在大多数情况下,父母大概会直接告诉孩子,“亲爱的,你应该用螺丝刀,来,我告诉你”。耐心一点的父母,大概会潜心观察一段孩子的举动,再设法“引导”他到正确的工具上来。在父母眼里,孩子当然不可能一开始就找对正确答案,所以做各种尝试也是情有可原。但是另一方面,也不应该“在错误的路径上摸索太久,浪费时间”,应当“迅速识别出正确的答案”。 无论父母有多少耐心,在他们眼里,孩子找到拧螺丝的工具的过程,都是个“不断接近正确答案”的过程。这个过程越短,孩子就越“聪明”,或者说“学习效率”就越高。 (more…)

3 weeks ago

再见,或许就是再也不见

陈皓(Haoel,网名“左耳朵耗子”)上周六因为突发心梗去世了,享年47岁。 我跟他虽然聊过好些次,但只是微信好友,从未见过面。回看微信记录,当年稀松平常的一声“再见”,已经成了“再也不见”。 许多人在缅怀他,许多文章提到他的时候,会用到“骨灰级程序员”、“技术大牛”这样的称呼。但如果仅仅用这两个词描述他,断然难以解释,为什么他的突然去世,会引发互联网上怀念的狂潮。 所以,我更愿意按照自己的经验,把他描绘为“有坦诚追求,兼具趣味、操守、胸怀的技术人”。恰恰是因为这样的人在这个年代太稀少,而这些品质又让众多人赏识和受益,大家才会如此地怀念他。 这个年代,做技术(仅指狭义的IT)的人很多,愿意分享的人也不在少数,其中不少还可以算世俗意义上的“成功者”。 但是,若仔细去看他们分享的内容,总感觉不够真诚。总感觉作者希望往高深了靠,目的也没有那么纯粹。你若提一些小白问题,迎来的往往是“你怎么连这都不知道?”的反问,或者“要谈这个问题,你先去看几本书再说吧”。话是这么说没错,但无数的初学者也往往因此打了退堂鼓。 但是陈皓的分享不同。我已经不止一次地看到有人提起,他分享——更准确说,是“创作”——的内容质量很高,而且总能做到“深入浅出”。哪怕是小白读者,看完也确实能有收获,如果还有兴趣,更可以跟着文末的链接,顺藤摸瓜探究更广阔的世界。 这让我想起我佩服的一位记者说的:记者写文章的最高境界,就是不表达自己的观点,因为记者的观点应当来自于他的素材。只要把这些素材摆出来,读者读完报道,观点就自然形成了。要做到这一点,需要对素材有足够的信心和把握,外加真诚和坦荡。 能做到这一点的记者,着实不多。陈皓虽然不是记者,他写的技术文章却能让读者得到类似的结论——要知道,技术讨论往往是非常容易擦枪走火的——可见他运用素材和逻辑的功力,以及更重要的,他的真诚和坦荡。 (more…)

3 weeks ago

姨妈还是姑妈,这是一个问题

2022年我接手了一本技术图书的翻译,拖拖拉拉到现在,也快截稿了,现在能做的,就是反复审阅,查漏补缺。 但是,有个问题一直困扰着我,始终得不到解决,那就是“姑妈-姨妈”问题。 书的最前面有某业界大牛写的“丛书编辑前言”,里面提到my grandson is learning from his aunt, my youngest daughter。众所周知,英文里亲戚的称呼远比中文简单,grandson既可以指孙子,又可以指外孙,aunt既可以指姑妈,也可以指姨妈。 所以从逻辑分析,grandson可能是“孙子”也可能是“外孙”,那么my daughter对他来说既可能是“姨妈”也可能是“姑妈”,因为文中再没有相关的信息,任凭你分析,也不知道哪种组合才是对的。 因为之前一直忙着处理正文,这个问题一拖再拖。眼看要交稿了,没办法,我才给作者发电子邮件。对方是业界大牛,这个问题又如此的“细枝末节”,那么他会不会回复,有没有耐心回复,我完全不知道。…

3 weeks ago

家长能接受孩子“半途而废”吗?

注:原文发布于2023年12月22日。 上一篇文章(坚持了两年之后,小朋友突然不想去打冰球了…)发出来之后,出乎意料阅读量竟然创了近期新高,也收到了不少反馈,看来育儿确实是如今许多人关心的话题。 在我收到的留言中,有好几条都提到,小朋友“选择要学的东西之前应当谨慎认真,一旦自己做了决定,那么再苦再累也要坚持”,万万不能“半途而废”。这个说法我非常熟悉,“半途而废”这四个字更是深深触动了我。确实,我反复想过,也和家里领导讨论过这个问题:身为家长,你能接受孩子“半途而废”吗? 答案当然是“不能”。 我写过自己小时候学手风琴的故事。那时候也有很多泪水、挣扎、反抗,每次闹到不可开交,我父亲就一本正经地说:“这是你自己选的,当时问你要不要学,你说要学,既然说了就必须做到……” 然而我还是没有能坚持下来,学了十年之后终于以“学习更重要”为理由自我解脱了。 等到再捡起来,已经是自己成为父亲之后。有更多时间练琴,更是到了欧洲之后的奢侈享受。虽然现在周围人都反馈我弹得还可以,也因此交到了不少朋友,但内心仍然有遗憾,一些很想弹的曲子,因为对我来说太难,实在是举步维艰。 如果当时没有半途而废,该是多好的一回事啊。 (more…)

3 weeks ago