【进阶】正则的调出功能简谈

作者:amnesiac 首发:官方论坛中文版

导言:一般人接触正则表达式、了解一些基本用法后,遇到普通字符串函数难以或无法处理的文本问题时,就会想到正则表达式了。然而,使用正则过程中,常常会遇到许多问题:

  • 每次使用都需要查阅手册熟悉规则
  • 每次使用都是痛苦的在一边看手册一边修改(重复猜测-验证的过程)
  • 常常写的正则逻辑上分析找不到问题却为何无法匹配

出现这些情况有个很重要的原因是正则表达式在执行时是个黑箱子,我们只能看到输入和输出,却不知道里面究竟怎么执行的。尽管可以通过一些正则辅助工具减轻这种痛苦,不过仍很难比较方便快速的定位并解决问题。

调出功能的意义

调出功能相当于在这个黑箱子上打孔,这样我们可以看到里面是怎样进行匹配的。

Source := "Haystack`nxyz`nabc"
FoundPos := RegexMatch(Source, "m)xyz$")
MsgBox, % FoundPos

这里显示为 0,告诉我们它没有找到匹配。从逻辑分析:源字符串中 xyz 在换行符之前,在匹配模式中使用了多行匹配选项(即 m),这样 $ 应该可以匹配换行符之前的位置,为什么没有匹配呢?现在开始一步步排除问题,首先直接使用 xyz 直接匹配源文本肯定没问题,那么问题会在哪里?

Source := "Haystack`nxyz`nabc"
FoundPos := RegexMatch(Source, "m)xyz(?CCallout)$")
MsgBox, % FoundPos

Callout(m) {
  MsgBox, m=%m%
}

其中,(?CCallout) 是调出语法,详细说明请参阅正则表达式调出。在正则表达式进行匹配过程中,调用了 Callout() 函数且显示此时模式匹配的字符串为 xyz,注意调出语法的插入点是在 xyz 后但 $ 之前,它表示在源字符串中寻找到该插入点之前模式的匹配字符串时即执行相应的调出函数(并把此时的匹配传递过去),这样说明 m)xyz 也是能匹配的,显然问题出在 $ 上。再仔细看看正则表达式快速参考会发现,AutoHotkey 中默认的新行符为 `r`n,而这里只有单个换行符,所以无法匹配。

顺便和大家分享个小技巧:在使用 m 选项时尽量搭配 `a 选项,保证你会省却很多麻烦。这里的换行符我们可以直接看到,较容易发现,实际的情况就复杂了,如:

Source =
(
Haystack
xyz
abc
)

假设从网上复制这个代码,自己执行时却总是匹配失败,容易想到是换行符的问题吗?

返回值的用途

在 AutoHotkey 中,目前 RegExMatch()RegExReplace() 都支持调出功能,这里简单说说它们具体是如何支持的。

调出函数最多可以定义 5 个参数:

  • Match: 相当于 RegExMatch 中的 UnquotedOutputVar, 包含需要时数组变量的创建.

在调出函数中这个参数最重要,默认保存了对应调出插入点之前那部分模式所匹配的字符串(若有子模式则存于伪数组),通过 PO 选项分别可保存位置和长度、匹配对象。下面看看调出函数的返回值。

模式匹配是继续进行或失败,取决于调出函数的返回值:

  • 如果函数返回 0 或没有返回数值,则匹配操作如常进行。
  • 如果函数返回 1 或更大的数字,则在当前位置匹配失败,但继续进行剩余部分的匹配测试。

先看看 RegexMatch 函数:

Haystack = Whoa, the quick brown fox jumps over the lazy dog.
FoundPos := RegExMatch(Haystack, "(the) (\w+)\b(?CCallout)")

Callout(m) {
    MsgBox m=%m%`nm1=%m1%`nm2=%m2%
    return 0
}

此时,在匹配到 the quick 后就退出了,显然没有继续匹配,把 Callout() 中返回给调用者的值改为 1 再执行看看。这次看到了什么,还显示了 the lazy 对吗?所以,对于 RegexMatch() 若调出函数返回 0 则匹配成功并返回(且把当前匹配位置保存到 FoundPos 中),若返回 1 则继续往后寻找匹配。换句话说,通过让调出函数返回 1 我们可以执行一次 RegexMatch 即可获取源字符串中符合指定模式的所有匹配(不用再像以前只能通过循环了)。

现在到 RegexReplace 函数:

Haystack = Whoa, the quick brown fox jumps over the lazy dog.
NewText := RegExReplace(Haystack, "(the) (\w+)\b(?CCallout)", "the wild")

Callout(m) {
    MsgBox m=%m%`nm1=%m1%`nm2=%m2%
    return 0
}

现在 Callout() 返回 0,那么替换了几次?嗯,两次,即在第一次替换后继续往后搜索,直至替换所有匹配。把返回的值改为 1 看看,出现什么情况了?提示找到了两次,但都没有替换,这里表示调出函数的返回值可以控制 RegexReplace 是否进行替换。

小结:调出函数返回值为 0 时匹配操作如常进行,但对于这两个函数是不一样的。此时,RegexMatch 匹配成功并返回,而 RegexReplace 则进行本次替换并继续往后搜索。而返回值为 1 时,则 RegexMatch 匹配失败并继续往后搜索,RegexReplace 则不进行本次替换并继续往后搜索。尽管还有其他返回值,等大家自行探索啦!

异想天会开吗?

这个例子改自 ahk8.com 一个问题的答案:

Text =
(
1 Lesser Heal
2 Fen Creeper
2 Holy Nova
1 Mind Control
)
CardList := {"Lesser Heal":"次级治疗术","Fen Creeper":"沼泽爬行者","Holy Nova":"神圣新星","Mind Control":"精神控制"}

NewText := RegExReplace(Text,"`amO)^([12] )(.+)$(?CCallout)", r)
MsgBox % NewText
Return

Callout(o){
    global CardList, r
  r := o[1] . CardList[o[2]] "`n"
  return 0
}

这个脚本的目的是希望把源文本中特定字符串根据对应关系替换为相应的字符串,可以在循环中通过普通字符串函数实现,不过这里是想在正则调出中进行替换:在调出函数中为 Replacement 参数赋值并用于 RegexReplace 函数中的替换。这个操作乍一看似乎行,实际不起作用,因为执行 RegexReplace 函数时 r 是空的即此时已经被替换为空字符串,之后 r 值的变化不会产生影响。
要获取替换后的字符串却是可行的,只需把 Callout() 函数中赋值 r 的语句替换,这时替换后的字符串会保存在变量 r 中了(不是 NewText):

r .= o[1] . CardList[o[2]] "`n"

小结

通过调出功能揭示了正则表达式的内部匹配机制,可帮助找出匹配不成功的问题点(在怀疑处前后都进行调出,若之前匹配而之后没有则找到问题了),对于解决正则表达式的问题有直接的帮助。当然,只要你喜欢,可以在一个模式中多次使用调出语法,让这个黑箱子四处透光,不用担心有人会找你麻烦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容