首页 » 精通正则表达式(第3版) » 精通正则表达式(第3版)全文在线阅读

《精通正则表达式(第3版)》Split运算符

关灯直达底部

The Split Operator

功能多样的split运算符(在不那么严格的时候,人们通常称其为函数)常被视为list context中m/…/g (☞311)的对立物。后者返回表达式匹配的文本,而split返回由表达式匹配的文本分隔的文本。把$text=~m/:/g应用到/'IO.SYS:225558:95-10-03:-a-sh:optional/'中,返回四个元素的list:

(/':/',/':/',/':/',/':/')

这似乎没什么用,但是split(/:/,$text)返回5个元素的list:

(/'IO.SYS/',/'225558/',/'95-10-03/',/'-a-sh/',/'optional/')

两个例子都告诉我们,「:」匹配了4次。使用split,这4次匹配把目标字符串的副本分隔为5段,返回包含5个字符串的list。

这个例子只用单个字符分隔目标字符串,其实我们可以使用任何正则表达式:

@Paragraphs=split(m/s*<p>s*/i,$html);

它会按照<p>或者<P>(两边可能有空白字符)把$html中的HTML代码分隔开来。你甚至可以按位置分隔:

@Lines=split(m/^/m,$lines);

把字符串按行切分。

对最简单形式的数据来说,split非常有用也很容易理解。不过,因为存在许多参数、特殊情况和特殊情形,事情会变得复杂。在深入这些细节之前,先给出两个特别有用的例子:

●特殊的 match 运算符//,会把目标字符串切分为单个字符,也就是说,split(//,"short test")得到10个元素的list:("s","h","o",...,"s","t")。

●特殊的 match 运算符"·" (包含单个空格的普通字符串)把目标字符串按照空白字符切分,等于使用 m/s+/,只是会忽略开头和结尾的空白字符。这样,split("·","···a·short···test···") 得到三个字符串:‘a’、‘short’和‘test’。

稍后讨论各种特殊情况,我们先来看基础的部分。

Split基础知识

Basic Split

split运算符看起来像函数,它接收3个参数:

split(match operand,target string,chunk-limit operand)

括号是可选的,未提供的运算元会设置为默认值(本节稍后讨论)。

split总是在list context中使用,常用的模式包括:

match运算元

运算元match有几种特殊情况,不过它通常等价于match操作中的 regex运算元。也就是说,你可以使用/…/和m{…}之类的形式,它可以是regex对象,或者任何能够求值为字符串的表达式。它只支持第292页介绍的核心修饰符。

如果继续要用括号来分组,请务必使用非捕获型括号「(?:…)」。我们稍后将会看到,在split中使用捕获型括号会触发极特殊的功能。

target string运算元

target string只用于检测,绝不会被修改。如果没有设定,默认使用$_。

chunk-limit运算元

chunk-limit 运算元的主要功能是设定 split切分字符串的数目上限。对上面的例子中同样的目标字符串调用split(/:/,$text,3)得到:

(/'IO.SYS/',/'225558/',/'95-10-03:-a-sh:optional/')

这告诉我们,/:/匹配两次之后split会终止,产生所需的3段。它可能可以匹配更多的次数,但这里不需要,因为存在段的数目限制。设定的数目将作为上限,所以最多只能返回规定数目的元素(除非正则表达式包含捕获型括号,后文会论及)。得到的元素数目可能少于上限,如果得到的分段少于规定的数目,也不会有多余的内容来“填充”。对于示例所用的数据,返回的list只有5个元素。不过,split(/:/,$text)和有重要的区别,这里暂时还看不出来——请记住这一点,稍后我们会仔细讲解。

记住,chunk-limit运算元指向的是各匹配之间的分段,而不是匹配的数目本身。否则,前面那个上限为3的例子就应该得到这个:

(/'IO.SYS/',/'225558/',/'95-10-03/',/'-a-sh:optional/')

这不是程序运行的结果。

这里谈谈效率:假设只希望取开头的几个元素,例如:

($filename,$size,$date)=split(/:/,$text);

为了提高效率,在需要的元素赋值之后,Perl 会停止 split操作。它会自动把 chunk-limit设置为list的元素个数+1。

深入split

从我们接触过的例子来看,split 很容易使用,但有三个特殊的问题增加了它实践起来的复杂程度:

●返回空元素。

●特殊的regex运算符。

●包含捕获型括号的regex。

下面分别讨论。

返回空元素

Returning Empty Elements

split的基本功能是返回由各个匹配分隔的分本,但有的时候,返回的文本是空字符串(长度为0的字符串,例如""),比如:

它返回:

("12","34","","78")

正则表达式「:」匹配了3次,所以应当返回4个元素。第3个元素为空,表示正则表达式在一行中匹配了两次,它们之间没有文本。

结尾的空元素

通常情况下,结尾的空元素不会返回,例如:

同样会返回4个元素:

("12","34","","78")

即使这个正则表达式在字符串的末尾能匹配更多的次数,结果也没有变化。在默认情况下,split不会返回list末尾的空元素。不过,我们可以通过设定chunk-limit运算元,让split返回所有的末尾元素。

chunk-limit运算元的次要职能

chunk-limit的主要用途是设定分段数目的上限,任何不等于0的chunk-limit都会保留末尾的空元素(chunk-limit设置为零等价于不设置chunk-limit)。如果你不希望限制返回的chunk的数目,但是希望保留末尾的空元素,只需要设置一个非常大的限制即可。或者更好的办法是,设置为-1,因为chunk-limit为负数表示一个足够大的上限:split(/:/,$text,-1)会返回所有的元素,包括末尾的空元素。

另一个极端是,如果你不希望保留任何空元素,可以在split之前使用grep{length}。使用grep之后,只会返回长度不为0的元素(也就是说,非空元素)。

[email protected]=grep {length} split(/:/,$text);

字符串末尾的特殊匹配

在字符串开头的匹配会产生一个空元素:

@num的值为:

("","12","34","","78")

开始的空元素表明,正则表达式在字符串的开头能够匹配。不过也有例外,如果一个正则表达式没有匹配任何文本,如果它在字符串的开头或者结尾匹配,那么开头和/或结尾的空元素将不会生成。来看个简单的例子:split(/b/,"a simple test"),它能够匹配其中的6个位置’。即使它能匹配6次,也不会返回7个元素,而是5个元素:("a","·","simple","·","test")。其实,这种特殊情况我们已经见过,即第321页的@Lines=split(m/^/m,$lines)。

Split中的特殊Regex运算元

Split/'s Special Regex Operands

split的match运算元通常就是正则文字或者regex对象,这与match运算符的情况一样,不过也有例外:

●regex为空的意思不是“使用当前的默认正则表达式”,而是把目标字符串分割为字符。在刚开始讨论 split 的时候我们见过这个例子,split(//,"short test")返回 10个元素的list:("s","h","o",…,"s","t")。

●如果match运算元是由单个空格构成的字符串(而不是正则表达式),则是另一种特殊情况。它基本等同与/s+/,只是会忽略开头的空白字符。这主要是为了模拟 awk 对输入进行的默认的输入-记录-分隔(input-record-separator)操作,尽管对普通情况来说它也很有用。

如果希望保留开头的空白字符,可以直接使用m/s+/。如果希望保留末尾的空白字符,只需要把chunk-limit设置为-1。

●如果没有设置regex运算元,则默认使用一个空格符(上面提到的特殊情况)。这样,不带任何运算元的split就等价于split(/'·/',$_,0)。

●如果regex为「^」,会自动使用修饰符/m(增强型行锚点匹配模式)。(因为某些原因,「$」则不行)。因为明确使用m/^/m非常容易,我推荐这种更清晰的写法。m/^/m是把包含多行文本的字符串按行切分的简便方法。

Split不产生伴随效应

请注意,split的match运算元看起来很像普通的match运算符,但是它没有任何伴随效应。split中的正则表达式不会影响到之后的match或是substitution操作所用的默认正则表达式,也不会设置$&、、$1 之类的变量。split 在伴随效应上完全独立于程序的其他部分(注8)。

Split中带捕获型括号的match运算元

Split/'s Match Operand with Capturing Parentheses

捕获型括号会从整体上改变split。一旦使用了捕获型括号,返回的list中会多出些独立的元素,它们对应于括号捕获的元素。也就是说,split 没有返回的部分或全部的文本,会包含在返回的list中。

例如,在处理HTML时,对下面的文本调用split(/(<[^>]*>)/):

…·and·<B>very·<FONT·color=red>very</FONT>·much</B>·effort…

返回:

如果去掉捕获型括号,split(/<[^>]*>/)返回:

(/'...·and·/',/'very·/',/'very/',/'·much/',/'·effort.../')

多出来的元素不受分段上限的限制(chunk-limit 限制原来字符串切分之后的分段数目,而不是返回元素的数目)。

如果包含多个捕获型括号,每次匹配之后,list会多出多个元素。如果某个捕获型括号没有参与匹配,对应的元素为undef。