|
发表于 2012-5-20 16:03:40
|
显示全部楼层
空白字符这个话题乍看上去很简单,实际上却隐藏着不少陷阱。空白字符包括水平空白字符,如英语单词的间隔,以及各种排版用的为便于阅读而增补的空白字符,等等。
而与此类似的,还有一种垂直空白字符,一般就是指换行符。其实,计算机才不管是水平还是垂直,对它来说,所有的内容都只是字符而已,并无高低上下之分。只不过为方便起见,程序特意指定某个字符作为分隔符,以便将文本划分为更小的单位罢了。
Perl的正则表达式提供了各种精确匹配空白字符的方法。
1. 水平空白字符
在Perl 5.10之前,预先定义好的有关空白字符处理的只有两组。一组是\s,匹配所有类型的空白字符,另一组是\S,匹配除空白字符外的任意字符。使用这种一刀切式的分类,稍有不慎就会导致意料之外的结果。比如下面这段代码,为了把连续几个空白字符替换成单个空格,第一直觉一定会想到用\s+匹配:
my $string = <<'HERE';
This is a line
This is another line
And a final line
HERE
$string =~ s/\s+/ /g;
由于换行符也属于空白字符,所以上述代码会将换行符也替换成空格,结果变成了单行文本:
This is a line This is another line And a final line
为了避免这种情况,你也许已经想到必须明确指定要替换的字符集合:
$string =~ s/[ \t]+/ /g;
你可能会觉得这样替换掉所有空格和跳格符,应该就没什么问题了吧。可是,Unicode字符集中其他形式的白字符又该如何处理呢?显然,上面这种非常局限的写法不能解决问题。
从Perl 5.10开始,我们就可以用\h字符组匹配任意水平空白字符:
use 5.010;
$string =~ s/\h+/ /g;
现在所有连续的水平空白字符都将被替换为单个空格,而换行符被保留下来。
正如Perl其他字符组一样,\h也有其相反表示,即大写的\H,它会匹配所有水平空白字符以外的任意字符,包括垂直空白字符以及所有非空白字符。
2. 垂直空白字符
和上面只需处理水平空白字符的情景类似,你或许会遇到只需处理垂直空白字符的情况。垂直空白字符可以分为回车符、换行符、进纸符(form feed)、垂直制表符,以及Unicode的行分隔符和段分隔符,等等。比如,想要看看某段文本是否为多行文本,可以用\v匹配任意垂直空白字符[1]:
use 5.010;
if ( $string =~ m/\v/ ) {
say 'Found a multiline string';
}
如果要把多行文本按行切分,也可直接在split中使用\v:
my @lines = split /\v/, $multi_line_string;
和\v对应的是大写的\V,它匹配垂直空白字符以外的任意字符,包括水平空白字符以及所有非空白字符。
3. 行终止符
长期以来,对于行终止符的处理,一直很麻烦。因为,不但有换行符,还有回车符;其组合方式也不一样,有的文件用换行符结束当前行,有的文件用回车符再加上换行符的形式表示换行。事实上,许多网络协议也是采用回车符换行符组合的方式,而Unicode又引入了某些新的换行符。
要是你的任务和行终止符相关,就需要同时处理所有可能的行终止符情况。一般你可能会将一个可选的回车符后跟一个换行符的形式,替换为普通的换行符:
$string =~ s/(?:\r?\n)/\n/g;
然而这种方式的效果并不好,因为\r和\n都属于逻辑字符,但在不同操作系统上对应的字符编码并不完全一致(Mac Classic,说的就是你!)。你或许会想到用字符组的形式,不管这些字符怎么组合出现几个,都能一次匹配:
$string =~ s/[\r\n]{1,2}/\n/g;
不过,这也可能会将\n\n替换为\n,即两个连续的空行会变为一行。更何况,它对Unicode行终止符一样无能为力。
为了处理所有种类的行终止符,我们可以写一条复杂但完备的正则表达式,它包含了各种原本不为所知的行终止符,像垂直制表符0x0B和进纸符0x0C:
$string =~
s/(?>\x0D\x0A?|[\x0A-\x0C\x85\x{2028}\x{2029}])/\n/g;
为了免去这种麻烦,Perl 5.10特意引入的\R字符组,以更简洁的方式表示,但作用则完全相同:
use 5.010;
$string =~ s/\R/\n/g;
现在,所有形式的行终止符都变成简单统一的换行符了。
4. 非换行符
Perl 5.12还引入了一种新的用来表示非换行符的字符组\N。之前,我们可以用.匹配任意非换行符的字符,例如:
if ( $string =~ m/(.+)/ ) { ... }
如果有人添加了/s修饰,这或许是因为不明就里地遵循了“最佳实践”那本书中推荐的做法,使得. 符号突然就能匹配换行符了,从而破坏程序原来的逻辑:
if ( $string =~ m/(.+)/xsm ) { ... } # 乱了!
如果我们只是需要匹配非换行符,那现在可以显式使用\N匹配,不必担心/s来捣乱:
use 5.012;
if ( $string =~ m/(\N+)/ ) { ... }
同字符组\N相反的,并不是对应字符组的补集,而仅仅是一个换行符\n。下次参加Perl掌故大赛(Perl Trivia Challenge)时,可别忘了这点哦。
5. 要点
q \h匹配水平空白字符。
q \v匹配垂直空白字符。
q \N匹配非换行符。
[1] 进纸符简记为\f,对应ASCII的<FF>字符;换行符简记为\n,对应ASCII的<LF>字符;回车符简记为\r,对应ASCII的<CR>字符。——译者注 |
|