perl 字符集处理

本文内容适用于perl 5.8及其以上版本.

perl internal form

在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string. 也就是说: Perl只认识两种编码: Ascii(octets)和utf8(string).

utf8 flag

那 么perl如何确定一个字符串是octets还是utf8编码的字符串呢? perl可没有什么智能, 他完全是靠字符串上的utf8 flag. 在perl内部, 字符串结构由两部分组成: 数据和utf8 flag. 比如字符串"中国"在perl内部的存储是这样:

utf8 flag      数据
On 中国

如果utf8 flag是On的话, perl就会把中国当成utf8字符串来处理, 如果utf8 flag为Off, perl就会把他当成octets来处理. 所有字符串相关的函数包括正则表达式都会受utf8 flag的影响. 让我们来看个例子:

程序代码:

use Encode;

use strict;

my $str = "中国";

Encode::_utf8_on($str);

print length($str) . "\n";

Encode::_utf8_off($str);

print length($str) . "\n";

运行结果是:

程序代码:

2

6

这 里我们使用Encode模块的_utf8_on函数和_utf8_off函数来开关字符串"中国"的utf8 flag. 可以看到, utf8 flag打开的时候, "中国"被当成utf8字符串处理, 所以其长度是2. utf8 flag关闭的时候, "中国"被当成octets(字节数组)处理, 出来的长度是6(我的编辑器用的是utf8编码, 如果你的编辑器用的是gb2312编码, 那么长度应该是4).

再来看看正则表达式的例子:

程序代码:

use Encode;

use strict;

my $a = "china----中国";

my $b = "china----中国";

Encode::_utf8_on($a);

Encode::_utf8_off($b);

$a =~ s/\W+//g;

$b =~ s/\W+//g;

print $a, "\n";

print $b, "\n";

运行结果:

程序代码:

Wide character in print at unicode.pl line 10.

china中国

china

结果第一行是一条警告, 这个我们稍后再讨论. 结果的第二行说明, utf8 flag开启的情况下, 正则表达式中的\w能够匹配中文, 反之则不能.

如何确定一个字符串的utf8 flag是否已开启? 使用Encode::is_utf8($str). 这个函数并不是用来检测一个字符串是不是utf8编码, 而是仅仅看看它的utf8 flag是否开启.

eq

eq是一个字符串比较操作符, 只有当字符串的内容一致并且utf8 flag的状态也是一致的时候, eq才会返回真.

理论就是上面这些, 一定要搞明白, 记清楚! 下面是实际应用.

unicode转码

如 果你有一个字符串"中国", 它是gb2312编码的. 如果它的utf8 flag是关闭的, 它就会被当成octets来处理, length()会返回4, 这通常不是你想要的. 而如果你开启它的utf8 flag, 则它会被当做utf8编码的字符串来处理. 由于它本来的编码是gb2312的, 不是utf8的, 这就可能导致错误发生. 由于gb2312和utf8内码范围部分重叠, 所以很多时候, 不会有错误报出来, 但是perl可能已经错误地拆解了字符. 严重的时候, perl会报警, 说某个字节不是合法的utf8内码.

解决的方法很显然, 如果你的字符串本来不是utf8编码的, 应该先把它转成utf8编码, 并且使它的utf8 flag处于开启状态. 对于一个gb2312编码的字符串, 你可以使用

程序代码:

$str = Encode::decode("gb2312", $str);

来将其转化为utf8编码并开启utf8 flag. 如果你的字符串编码本来就是utf8, 只是utf8 flag没有打开, 那么你可以使用以下三种方式中的任一种来开启utf8 flag:

程序代码:

$str = Encode::decode_utf8($str);

$str = Encode::decode("utf8", $str);

Encode::_utf8_on($str);

最后一种方式效率最高, 但是官方不推荐. 以下划线开头的函数是内部函数, 出于礼貌, 一般不从外部调用.

字符串连接

. 是字符串连接操作符. 连接两个字符串时, 如果两个字符串的utf8 flag都是Off, 那么结果字符串也是Off. 如果其中任何一个字符串的utf8 flag是On的话, 那么结果字符串的utf8 flag将是On. 连接字符串并不会改变它们原来的编码, 所以如果你把两个不同编码的字符串连在一起, 那么以后不管对这个字符串怎么转码, 都总会有一段是乱码. 这种情况一定要避免, 连接两个字符串之前应该确保它们编码一致. 如有必要, 先进行转码, 再连接字符串.

perl unicode编程基本原则

对于任何要处理的unicode字符串, 1)把它的编码转换成utf8; 2)开启它的utf8 flag

字符串来源

为了应用上面说到的基本原则, 我们首先要知道字符串本来的编码和utf8 flag开关情况, 这里我们讨论几种情况.

1) 命令行参数和标准输入. 从命令行参数或标准输入(STDIN)来的字符串, 它的编码跟locale有关. 如果你的locale是zh_CN或zh_CN.gb2312, 那么进来的字符串就是gb2312编码, 如果你的locale是zh_CN.gbk, 那么进来的编码就是gbk, 如果你的编码是zh_CN.UTF8, 那进来的编码就是utf8. 不管是什么编码, 进来的字符串的utf8 flag都是关闭的状态.

2) 你的源代码里的字符串. 这要看你编写源代码时用的是什么编码. 在editplus里, 你可以通过"文件"->"另存为"查看和更改编码. 在linux下, 你可以cat一个源代码文件, 如果中文正常显示, 说明源代码的编码跟locale是一致的. 源代码里的字符串的utf8 flag同样是关闭的状态.

如果你的源代码里含有中文, 那么你最好遵循这个原则: 1) 编写代码时使用utf8编码, 2)在文件的开头加上use utf8;语句. 这样, 你源代码里的字符串就都会是utf8编码的, 并且utf8 flag也已经打开.

3) 从文件读入. 这个毫无疑问, 你的文件是什么编码, 读进来就是什么编码了. 读进来以后, utf8 flag是off状态.

4) 抓取网页. 网页是什么编码就是什么编码, utf8 flag是off状态. 网站的编码可以从响应头里或者html的标签里获得. 也有可能出现响应头和html head里都没说明编码的情况, 这个就是做的很不礼貌的网页了. 这时候只能用程序来猜:

程序代码:

use Encode;

use LWP::Simple qw(get);

use strict;

my $str = get "http://www.sina.com.cn";

eval {my $str2 = $str; Encode::decode("gbk", $str2, 1)};

print "not gbk: [email protected]\n" if [email protected];

eval {my $str2 = $str; Encode::decode("utf8", $str2, 1)};

print "not utf8: [email protected]\n" if [email protected];

eval {my $str2 = $str; Encode::decode("big5", $str2, 1)};

print "not big5: [email protected]\n" if [email protected];

输出:

程序代码:

not utf8: utf8 "\xD0" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162.

not big5: big5-eten "\xC8" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162.

我 们给decode函数传递了第三个参数, 要求有异常字符的时候报错. 我们用eval捕获错误, 转码失败说明字符串本来不是这种编码. 另外注意我们每次都把$str拷贝到$str2, 这是因为decode第三个参数为1时, decode以后, 传给它的字符串参数(第二个参数会被清空). 我们拷贝一下, 这样每次被清空的都是$str2, $str不变.

来看结果, 既然不是utf8, 也不是big5, 那就应该是gbk了. 对于其他不知编码的字符串, 也可以使用这种方法来猜. 不过因为几种编码的内码范围都差不多, 所以如果字符串比较短, 就可能出不了异常字符, 所以这个方法只适用于大段的文字.

输出

字 符串在程序内被正确地处理后, 要展现给用户. 这时我们需要把字符串从perl internal form转化成用户能接受的形式. 简单地说, 就是把字符串从utf8编码转换成输出的编码或表现界面的编码. 这时候, 我们使用$str = Encode::encode(‘charset‘, $str);. 同样可以分为几种情况.

1) 标准输出. 标准输出的编码跟locale一致. 输出的时候utf8 flag应该关闭, 不然就会出现我们前面看到的那行警告:

程序代码:

Wide character in print at unicode.pl line 10.

2) GUI程序. 这个应该是不用干什么, utf8编码, utf8 flag开启就行. 没有实际测试过.

3) 做http post. 看网页表单要求什么编码. utf8 flag开或关无所谓, 因为http post发送出去的只是字符串中的数据部分, 不管utf8 flag.

PerlIO

PerlIO为我们的输入/输出转码提供了便利. 它可以针对某个文件句柄, 输入的时候自动帮你转码并开启utf8 flag, 输出的时候, 自动帮你转码并关闭utf8 flag. 假设你的终端locale是gb2312, 看下面的例子:

程序代码:

use strict;

binmode(STDIN, ":encoding(gb2312)");

binmode(STDOUT, ":encoding(gb2312)");

while (<>) {

chomp;

print $_, length, "\n";

}

运行后输入"中国", 结果:

程序代码:

中国2

这样我们就省去了输入和输出时转码的麻烦. PerlIO可以作用于任何文件句柄, 具体请参考perldoc PerlIO.

相关API

都是Encode模块的:

$octets = encode(ENCODING, $string [, CHECK]) 把字符串从utf8编码转成指定的编码, 并关闭utf8 flag.

$string = decode(ENCODING, $octets [, CHECK]) 把字符串从其他编码转成utf8编码, 并开启utf8 flag, 不过有个例外就是, 如果字符串是仅仅ascii编码或EBCDIC编码的话, 不开启utf8 flag.

is_utf8(STRING [, CHECK]) 看看utf8 flag是否开启. 如果第二个参数为真, 则同时检查编码是否符合utf8. 这个检测不一定准确, 跟decode方式检测效果一样.

_utf8_on(STRING) 打开字符串的utf flag

_utf8_off(STRING) 关闭字符串的utf flag

最后两个是内部函数, 不推荐使用.

参考perldoc Encode.

utf8和utf-8

前 面我们提到的一直都是utf8. 在perl中, utf8和utf-8是不一样的. utf-8是指国际上标准的utf-8定义, 而utf8是perl在国际标准上做了一些扩展, 能兼容的内码要比国际标准的多一些. perl internal form使用的是utf8. 另外顺便提一下, 字符集的名称是不区分大小写的并且"_"和"-"是等价的.

EBCDIC

EBCDIC是一套遗留的宽字符解决方案, 不同于unicode, 它不是Ascii的超集. 上面介绍的方案并不完全适用于EBCDIC. 关于EBCDIC, 请参考perldoc perlebcdic

时间: 2024-08-05 19:32:13

perl 字符集处理的相关文章

perl字符集处理

本文内容适用于perl 5.8及其以上版本. perl internal form 在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串,  perl管它叫string. 也就是说: Perl只认识两种编码: Ascii(octets)和utf8(string). utf8 flag 那 么perl如何确定一个字符串是octets还是utf8编码的字符串呢? perl可没有什么智能, 他完全是靠字符串上的utf8

[perl] 连接mysql

先写在这里吧,之后再改进~~ package mysql_conn; use DBI; use strict; sub new{ my $class = shift(); print ("CLASS=$class\n"); my $self={}; $self->{"location"} = shift(); $self->{"db_name"} = shift(); $self->{"db_user"} =

如何用Perl对Excel的数据进行提取并分析

巡检类工作经常会出具日报,最近在原有日报的基础上又新增了一个表的数据量统计日报,主要是针对数据库中使用较频繁,数据量又较大的31张表.该日报有两个sheet组成,第一个sheet是数据填写,第二个sheet则是基于第一个sheet的数据进行的文字描述和图表展示. 文字描述主要包括两部分:一.呈现该31张表中数据量最大的9张表.呈现结果类似于:emp(约14万),dept(约100万)...当然,这个只是举例,为了避免引起不必要的麻烦(主要是企业信息安全方面的考虑),我这里不可能将具体日报的内容贴

Python 与 Perl的优缺点

一:Python 与 Perl对比 (1)设计一个语言的初始目的决定了该语言将会内建什么功能: perl初始是Larry为了格式化处理文本而创建的,所以内建了正则 :python内建一个复数型别,猜测Guido最初一定是为了数值计算而创建了python.因此perl擅长文字处理,python擅长数值处理. (2)应用领域和需求不一样: Perl 设 计之初就是为了方便编写复杂高效的系统脚本,它也是应该最为广泛的脚本编程语言.它在编程方面相当于瑞士军刀,对字符.文本文件处理能力很强,以前要求 sh

2016.7.27 VS搜索正则表达式,在UltraEdit中可选用Perl正则引擎,按C#语法搜索

表达式 语法 说明 任一字符 . 匹配除换行符外的任何一个字符. 最多 0 项或更多 * 匹配前面表达式的 0 个或更多搜索项. 最多一项或更多 + 匹配前面表达式的至少一个搜索项. 最少 0 项或更多 @ 匹配前面表达式的 0 个或更多搜索项,匹配尽可能少的字符. 最少一项或更多 # 匹配前面表达式的一个或更多搜索项,匹配尽可能少的字符. 重复 n 次 ^n 匹配前面表达式的 n 个搜索项.例如,de>[0-9]^4de> 匹配任意 4 位数字的序列. 字符集 [] 匹配 [] 内的任何一个

Perl爬虫的简单实现

由于工作中有个项目需要爬取第三方网站的内容,所以在Linux下使用Perl写了个简单的爬虫. 相关工具 1. HttpWatch/浏览器开发人员工具 一般情况下这个工具是用不到的,但是如果你发现要爬取的内容在页面的HTML源码里找不到,如有的页面是通过AJAX异步请求数据的,这时候就需要HttpWatch之类的工具来找到实际的HTTP请求的URL了,当然现在很多浏览器都有开发人员工具(如Chrome, Firefox等),这样可以更方便查看所有请求的URL了. 2. curl/wget 这是爬虫

Perl 学习笔记-正则表达式

1.Perl中的正则表达式 在Perl中叫做模式, 是一个匹配(或不匹配)某字符串的模板, 是一种小程序, 对于一个字符串, 要么匹配, 要么不匹配. 使用简易模式: 将模式写在一对正斜线(/)中即可. 2.元字符  .  匹配非换行符 \n 的任何单字符, 需要注意的是转义字符如  \t  算做一个字符. 匹配  .  应该使用  \.  *  匹配前面的内容0次或多次, 使用  .* 表示匹配任意单字符任意次(这种模式也称为捡破烂模式)  +  匹配前面的内容至少1次  ?  匹配前面的内容

&lt;摘录&gt;perl正则表达式中的元字符、转义字符、量词及匹配方式

Linux平台上被广泛使用的正则表达式库PCRE - Perl-compatible regular expressions,从其名字即可知道,PCRE提供的是一套与Perl中相兼容的正则表达式. 元字符(Meta-character) '\' : 在任何元字符前面加上反斜线,就会使它失去元字符的特殊作用.例如/3\.1415/这个模式里没有通配符 '^' : 匹配行首:在字符集中它是脱字符,表示求补集 '$' : 匹配行尾(或结尾处新行之前字符) '.' : 除新行(newline)外的任一字

Perl 正则表达式语法

1. 概要 Perl正则表达式是Boost.regex 默认行为,也可以将perl传入basic_regex 构造. boost::regex  e1(my_expression); boost::regex  e2(my_expression,  boost::regex::perl  |  boost::regex::icase); 2. 特殊字符 . [ { ( ) \ * + ? | ^ $ 3. 通配符 ‘ .’ 在字符集之外使用时可以匹配任意单字符,除了以下两种特殊情况: (1)NU