perl–绑定操作符=~&模式中的内插&捕获变量&捕获变量的存续期&不捕获模式&命名捕获&自动捕获变量

七、绑定操作符=~

默认情况下模式匹配的操作对象是$_,绑定操作符(binding operator,=~)告诉Perl,拿右边的模式来匹配左边的字符串,而不是匹配$_。例如:

图片

绑定操作符虽然看起来像某种赋值操作符,其实并非如此!它只是说明本来这个模式配会针对$_变量,但现在请匹配左边给出的字符串。若没有绑定操作符,表达式就会用默认的$_。在下面这个(有点不寻常的)例子里,$likes_perl会被赋予一个布尔值,这个结果取决于用户键入的内容。这段代码属于快速消费型(quick-and-dirty),因为判断之后就丢弃了用户的输人。这段代码的大致功能是读取输入行,匹配字符串与模式,然后舍弃输入行的内容。这里的匹配操作完全没有用到默认变量$_,当然也没有改动它的值:

图片

因为绑定操作符的优先级相当高,也就没必要用圆括号来括住模式测试表达式。所以下面这一行如同上面的表达式一样,会将匹配结果(而非该行输入的内容)存进变量:

my $likes_perl = <STDIN> =~ /byesb/i

八、模式中的内插正则表达式内部可以进行双引号形式的内插,这样我们就可以很快写出下面这样类似 grep命令的程序:

图片

不管$what的内容是什么,当我们进行模式匹配时,该模式都会由$what的值决定。在这里,它等效于/A(larry)/,也就是在每行的开头寻找larry。但$what的内容未必一定要事先写在直接量中,我们可以通过@ARGV取得命令行参数:

my $what= shift @ARGV;

如果第一个命令行参数是fred|barney,则模式会变成/A(fred|barney)/,也就是在每一行开头寻找fred或barney。上面搜寻larry时两边看似多余的圆括号其实非常重要。如果省略圆括号,当输入的模式改成现在这样两者选一的话,就会产生另外的效果:要么在字符串开头匹配fred,要么在字符串的任何地方匹配barney。将程序代码改成从@ARGV读取匹配模式后,这个程序就和Unix的grep命令很像了。但我们必须注意字符串里的元字符。如果$what的值为‘fred(barney’,该模式就会变成/A(fred(barney)/,你也知道这样是无法正常工作的,它会报告非法正则表达式的错误并中断程序运行。不过运用某些高级技术的话,是可以捕获这类错误的(或者从一开始就解除元字符的魔法),以避免程序崩溃。不过目前你只需要记住,一旦赋予用户使用正则表达式的权力,他们就该负起正确使用的责任。

九、捕获变量圆括号出现的地方一般都会触发正则表达式引擎捕获匹配到的字符串捕获组会把匹配圆括号中模式的字符串保存到相应的地方。如果不止一个括号,也就不止一个捕获组。每个捕获组包含的都是原始字符串中的内容,而不是模式本身。我们可以通过反向引用取得这些捕获内容,但也可以在匹配操作结束后立即通过相应的捕获变量取得这些内容既然这些捕获变量保存的是字符串,那就说明它是标量变量。在Perl里面,它们的名称就是$1和$2这样的形式。模式中有多少个捕获括号,就有多少个对应名称的捕获变量可用。所以,变量$4的意思就是模式中第4对括号所匹配的字符串内容,这个内容和模式运行期间反向引用4所表示的内容是一样的。但它们并非同一个事物的两种名称:4反向引用的是模式匹配期间得到的结果,而$4则是模式匹配结束后对得到的捕获内容的索引。可以说,捕获变量是正则表达式无比强大的重要原因之一,因为有了它,我们才得以拥有提取字符串中某些特定部分的能力:

图片

或者也可以一次捕获多个字符串:

图片

运行结果是words were Hello there neighbor。请注意,在输出结果里没有逗号。因为模式里的逗号放在圆括号外面,所以第二个捕获中不会有逗号。使用这个技巧,我们可以精确筛选捕获的(和跳过的)数据。有时得到的捕获变量可能是空的,因为给出的字符串完全有可能不符合模式要求。所以,捕获变量有可能出现包含空字符串的情况:

图片

十、捕获变量的存续期这些捕获变量通常能存活到下次成功匹配为止。也就是说,失败的匹配不会改动上次成功匹配时捕获的内容,而成功的匹配会将它们的值重置。这就意味着,捕获变量只应该在匹配成功时使用,否则就会得到之前一次模式匹配的捕获内容。下面的(失败)案例本来应该输出从$_捕获的某个单词。但是如果匹配失败,它会输出之前遗留在$1里的字符串:

图片

这就是为什么模式匹配总是出现在if或while条件表达式里的原因:

图片

既然这些捕获内容不会永远留存,那么$1之类的捕获变量只应该在模式匹配后的数行内使用。如果程序维护员在原先的正则表达式和$1的使用之间加入了一个新的正则表达式,$1将会是第二次匹配捕获的值,而非第一次。因此,如果需要在数行之外使用捕获变量,通常最好的做法是将它复制到某个普通变量里。这其实也能改善程序代码的可读性:

图片

十一、不捕获模式目前所见的圆括号都会捕获部分的匹配字符串到捕获变量中,但有时候却需要关闭这个功能,而仅仅只是用它来进行分组。比如某个模式中有些部分是可选的,之后的部分却是要捕获的。好比在某些时候巨无霸(bronto)是可选的,而之后的部分,比如牛排(steak)或者汉堡(burger)却正是我们感兴趣的一样:

图片

尽管有时“bronto”不存在,还是得把$1变量留给它。Perl只是按左圆括号的序号来决定捕获变量名,这导致我们真正想捕获的内容只能进入$2。在更复杂的模式中,这种情况非常让人困惑。还好Perl的正则表达式允许使用圆括号分组但不进行捕获。我们把这叫做不捕获圆括号

(noncapturing parentheses),书写的时候也有些差别,需要在左括号后面加上问号和冒号(?:),告诉Perl这一对圆括号完全是为了分组而存在的。可以在这里使用不捕获圆括号来跳过“bronto”,这样就可以用$1捕获实际需要的内容:

图片

之后若要修改正则表达式,以支持巨无霸汉堡的熏肉特色版,就可以在这个模式中加入不捕获选项。这时候你感兴趣的字符串仍然会进人捕获变量$1。若是没有这个功能,就必须在每次加入分组括号之后修改捕获变量名:

图片

Perl的正则表达式有许多其他用在圆括号内部的修饰符,能达成更复杂的功能,比如前瞻、后顾、内嵌注释,甚至可以在模式中运行代码。

十二、命名捕获

我们可以利用圆括号的捕获功能提取特定字符串并保存到诸如$1、$2这样的变量中。不过就算是较为简单的模式,要维护数字变量和圆括号之间的对应关系也是一件比较困难的事。比如下面这个正则表达式,匹配$names中出现的两个名字:

图片

实际上看不到say的输出,因为模式字符串中期望的是and,而实际变量中给出的是or。为了应对这种情况,我们觉得应该允许两者并存,所以在正则表达式中加入择一匹配(alternation),不管and还是or都没关系。当然,作为择一匹配的部分必须要加上圆括号以表示候选列表范围:

图片

呀!虽然现在能看到输出的消息,但第二个名字不对,因为我们刚又加上了一对圆括号捕获其中内容。现在从$2拿到的是择一匹配部分中的内容,而原本希望拿到的第二个名字推后存放到了$3变量中,而我们事后并未输出该变量: I saw Fred and or当然我们可以用不捕获圆括号的写法来解决,但换汤不换药,问题是我们仍旧要记住括号的序号。要是模式中需要捕获的内容有很多该怎么办呢?为了避免记忆$1之类的数字变量,Perl5.10增加了对捕获内容直接命名的写法最终捕获到的内容会保存在特殊哈希%+里面:其中的键就是在捕获时用的特殊标签,对应的值则是被捕获的字符串。具体的写法是(?<LABEL>PATTERN),其中LABEL可以自行命名。下面把第一个捕获标签定为name1,第二个标签定为name2。所以,提取捕获内容时需要访问的变量变成了$+{name1}和$+{name2}:

图片

现在可以看到正确结果了:I saw Fred and Barney一旦使用了捕获标签,就可以随意移动位置并加入更多的捕获圆括号,不会因为次序变化导致麻烦:

图片

在使用捕获标签后,反向引用的用法也随之有所变化。之前我们用1或者g{1}这样的写法,现在我们可以使用g{label}这样的写法:

图片

我们也可以用另一种语法来表示反向引用。k<label>等效于g{label}

图片

十三、自动捕获变量有三个免费的捕获变量,就算不加捕获圆括号也能使用。听起来不错,不过它们的名字不太好记,甚至有点诡异。虽然Larry可能不会反对给它们取比较正常的名字,像$gazoo或$ozmidiar,但这些都是在你自己的程序里可能会用到的名称。为了让普通的Perl程序员在为第一个程序的第一个变量命名时不必绕开Perl所有的特殊变量名,Larry给内置变量起了一些稀奇古怪的名字,也可以说是“惊世骇俗”的名字。在这里,他选用的是标点符号:$&$`$’。它们看起来很奇怪,也很丑陋,非常诡异,不过这就是它们的名字。字符串里实际匹配模式的部分会被自动存进$&里:

图片

当上面的程序运行时,会告诉我们字符串里匹配的部分是” there,”(一个空格、一个单词以及一个逗号)。第一个捕获内容存在$1中,是具有5个字母的单词there,但$&里存的是整个匹配区段匹配区段之前的内容会存到$`里,而匹区段之后的内容则会存到$’里。另外一个理解方法是,$`保存了正则表达式引擎在找到匹配区段之前略过的部分,而$’则保存了字行串中剩下的从未被匹配到的部分。如果将这三个字符串依次连接起来,就一定会得到原来的字符串:

图片

程序运行时会把字符串显示为(Hello)( there,)(neighbor),展现出这三个自动捕获变量的实际用途。这三个自动捕获变量也都有可能是空字符串,它们的存续范围和编号的捕获变量完全一样。一般而言,它们的值会一直持续到下一次模式匹配成功之前。我们之前提过这三个变量可以免费使用。不过,免费也是有代价的。在这里的代价是:一旦在程序中任何部分使用了某个自动捕获变量,其他正则表达式的运行速度也会变慢。这虽不会严重拖慢速度,但足以成为隐患,许多Perl程序员干脆永远不碰这些自动捕获变量。他们会找出变通办法。比如你可以将整个模式加上括号,然后以$1来代替$&(当然,你可能需要调整模式的捕获编号)。如果你用的是Perl5.10或以上的版本,那么就更方便了。修饰符/p只会针对特定的正则表达式开启类似的自动捕获变量,但它们的名字不再是$`,$&和$’,而是用${^PREMATCH}${^MATCH}${^POSTMATCH}表示。于是,之前的例子可以改写成:

图片

这些变量名看起来有点古怪,名字前面加上了^又在外面围上了花括号。随着Perl的进化,用于特殊事物的名字已经不敷使用,加上开头的^的目的是避免名称冲突(实际上,程序员能够自由命名的变量是不能以^开头的,它是一个非法字符),外面围住花括号的目的是表示其中的内容是完整的变量名。捕获变量(包括这里介绍的自动捕获变量以及之前介绍的有编号的那些)常常用于正则表达式的替换操作。

2022-05-02 16:08 发表于陕西

阅读原文

简介:生信小技能,欢迎关注微信公众号:生信小猪
(0)
打赏 喜欢就点个赞支持下吧 喜欢就点个赞支持下吧

声明:本文来自“生信小猪”,分享链接:https://www.zyxiao.com/p/309312    侵权投诉

网站客服
网站客服
内容投稿 侵权处理
分享本页
返回顶部