日常正则(一)——盼(Lookahead)

闲话少说,老白因工作需要,用 C# 2.0 写了个文件批量重命名工具,不是 ACDSee 那种单纯编个号就完了,是用正则表达式来匹配和替换的。

好比说,我们手头有这样一些文件,要重命名为如下形式:

源文件名目标文件名
No1《什么什么》.abc
...
No25《blahblah》.abc
...
No100《foobarfoo》.abc
...
001-什么什么.abc
...
025-blahblah.abc
...
100-foobarfoo.abc
...

怎么办呢?ACDSee 的批量重命名显然不行,虽然她那个把元数据加入文件名的设置很贴心。

老白我也是单纯崇拜正则,还没掌握,用了两次替换才把这事儿办了,惭愧惭愧……

操作顺序查找替换为
1^No(?<num>/d+)[^《]*《(?<name>[^》]+)》(?<ext>/.abc)$00${num}-${name}${ext}
2^/d*(?<num>/d{3})(?!/d)-(?<name>[^.]+)(?<ext>/.abc)$${num}-${name}${ext}

当然,上述写法还有待改进,只是鄙人一直无法想出只使用一次替换的方法来,苦于 Replace 表达式的表达方式太简单了,而且正则被设计用来处理字符串,不管你数据类型、前导零什么的(相对于格式化输出而言)……

啊,对了对了,本文的主角——Lookahead(《精通正则表达式(第3版)》中称其为“顺序环视”,见该书 P66)——刚才已经简单照了一小面,露了一小手:请看第二个正则表达式,其中的“(?!/d)”就是 Negative Lookahead(否定顺序环视)。MSDN 中是这么说的:

"(?!    ) Zero-width negative lookahead assertion. Continues match only if the subexpression does not match at this position on the right. For example, /b(?!un)/w+/b matches words that do not begin with un." from http://msdn.microsoft.com/en-us/library/bs2twtah(VS.80).aspx

引文大意是:(?!...) 零宽度否定顺序环视断言。仅当子表达式在当前位置右侧无法匹配时才继续匹配。例如,/b(?!un)/w+/b 匹配那些不以 un 开头的单词。后记:后来发现 MSDN 中文版是这么说的“(零宽度负预测先行断言。)仅当子表达式不在此位置的右侧匹配时才继续匹配。例如,/b(?!un)/w+/b 与不以 un 开头的单词匹配。”(http://msdn.microsoft.com/zh-cn/library/bs2twtah(VS.80).aspx)其实也差不多。

对自己的翻译没信心,我再就着她这例子解释解释吧:正则引擎从左向右依次扫描输入字符串,看是否符合“/b(?!un)/w+/b”这个模式(pattern)。首先匹配一个单词分界符,比如空格(斜体字表示这些是同一个空格),然后看这个空格右侧有没有“un”这两个连续的字符。要是有,那对不起,您这输入不合格,引擎我得重新开始,看看“un”右边有没有单词分界符;要是那个空格右边不是“u”,或者即使是“u”但“u”的右边不是“n”,那还是可以的,引擎就从空格右边的第一个字符,那个不是“u”的字符开始,尝试匹配“/w+”,就是一个或多个可用于单词的字符。

读者应该注意到上文中黑体字的部分了,下面我们就来说说怎么就“零宽度”了。通常的正则表达式匹配都是开弓没有回头箭,从左向右(当然,如果设置了 RegexOptions.RightToLeft,这个顺序就调过来了,但我后边要说的话同样适用)一次捡起眼前的一个字符来看,看完了就扔到身后的背篓(匹配)或者地上(未匹配)不再搭理,当然,不匹配的时候要把背篓中的都倒出来,那也是倒在身后。初学正则我们就知道有否定字符组“[^...]”这种表示法,匹配那些未在中括号中列出的字符。否定字符组的确也是否定,但仍需要匹配一些东西,而环视(Lookahead & Lookbehind)却给我们机会,可以顾(回头)、盼(远眺),那自然神采飞扬。就好比我先不急着拿起眼前的这个字符,我先往前多眺望一下,根据远方的情况再决定下一步的行动。

再举个例子吧,注意和上文那个不一样了啊。我们比较一下使用如下两个模式“/b[^u]/w+/b”和“/b(?!u)/w+/b”匹配字符串“ regular ”时的不同。对于使用否定字符组的那个模式,单词分界符自不必说,“r”匹配了“[^u]”,后面一串“egular”自然是满足“/w+”了。而使用否定顺序环视呢,单词分界符也成功匹配,“r”满足了“(?!u)”的条件,但是没有匹配(即没有被捡起来呢还),“regular”匹配“/w+”(一块儿捡起来扔进背篓)。所以你看,否定顺序环视能更好地满足我们的要求。

好了,回到我自己的问题。读者很容易看出两次替换前后文件名的变化:

源文件名第一次替换后第二次替换后
No1《什么什么》.abc
...
No25《blahblah》.abc
...
No100《foobarfoo》.abc
...
001-什么什么.abc
...
0025-blahblah.abc
...
00100-foobarfoo.abc
...
001-什么什么.abc
...
025-blahblah.abc
...
100-foobarfoo.abc
...

从上表可以看出,第二次替换必须去除高于三位的前导零才能满足需求。我说“/d*(?<num>/d{3})(?!/d)-”的意思就是,我想要减号左边数字中最右边的三个,实践证明我正确地表达了我的意思。当然,“(?!/d)”和“(?=/D)”是等价的(后者使用的是 Positive Lookahead,而 /D 匹配非数字字符),好像“茴”字有四样写法,正则有 N 多写法。

不知陋文能否有幸被 .NET 和/或正则表达式大牛瞥见,请不吝赐教,也欢迎偶然路过的朋友指教、探讨。

简单感谢一下 ryehou 提出的宝贵意见。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值