Split运算符
04-13Ctrl+D 收藏本站
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的元素(也就是说,非空元素)。
my@NonEmpty=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。