grep 的 -v 参数可以找出不匹配正则表达式的所有字符串。那是否有办法直接用正则达到同样的目的?
答案是肯定的,利用『零宽度断言』就可以做到。不过请先让我解释一下什么叫『零宽度断言』这个概念。
不知道是谁翻译的,听起来是不是很装逼。事实上,零宽度是指要匹配的部分没有长度,比如匹配一句话中以 ing 结尾的词,但又要求匹配结果不包含 ing 本身,那么 ing 就是零宽度。『断言』就是 assert(一直好奇为啥翻译成断言),其实也就是验证的意思。
举个使用零宽度断言的栗子:给定一组文件名,找出 jpg 格式文件的文件名(但是不包含 .jpg 本身,零宽度嘛),相对来说可能大家对 JavaScript 的正则规则以及用法都更熟悉一些,我后面都用 JavaScript 的 String.prototype.match 方法来举例
var files = ["foo.jpg", "bar.png", "baz.gif"];
files.forEach(function (file) {
console.log(file.match(/^.+(?=\.jpg$)/));
});
结果只有 foo.jpg 能够返回匹配结果,并且返回的结果是 ["foo"],即不包含扩展名。
(?=) 叫做正预测,那反过来负预测是 (?!),表示后面不跟特定字符,不过
file.match(/^.+(?!\.jpg$)/)
将能匹配到所有的文件名,并且返回的结果是 ["foo.jpg"], ["bar.png"], ["baz.gif"]。为什么foo.jpg 还是被匹配了呢,而且还是包含扩展名的?仔细想想也会觉得上面的结果其实没有问题:^.+ 匹配了 foo.jpg,其最后一个字符 g 的后面也的确不是 \.jpg$,所以能匹配出来也就不奇怪了。
问题到此似乎变得很不明朗,用了负预测也不能选出非 .jpg 结尾的文件名么?其实再加一个条件就好了:文件名结尾必须是 \.\w+$ 但又不能是 \.jpg$
file.match(/^.+(?=\.\w+$)(?!\.jpg$)/)
更改后运行观察结果 foo.jpg 将不会再匹配了,并且匹配的结果也不再包含扩展名。
如上面例子所示,正预测和负预测是可以连在一起用的。必须满足两个条件,才算匹配,把上面正预测和负预测的表达式换一下,效果是一样的。
如果不需要获取匹配结果,只需要知道是否匹配,那么正则表达式还可以更简单,举一个有用的例子,在 NGINX 的设置中,如果用户访问的路径不是 .php 结尾的,就让 NGINX 直接返回此文件并加上超长的缓存时间头信息:
location ~ \.(?!php$) {
expires max;
}
另外提一下如何利用 grep 做测试,正则表达式其实也不止一种,我们上面所说的是 perl 风格的正则表达式,所以在用 grep 测试的时候一定要记得加 -P 参数来让上面的正则生效。但也要注意 grep 的版本,在 MacOS 里自带的 grep 是不支持 -P 参数的:
grep -P -e '^((?!\.jpg).)*
此问题我已经在 segmentfault.com 上做过介绍
写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人