(转)Perl处理字符编码

来源:互联网 发布:大数据研究中心 编辑:程序博客网 时间:2024/05/22 03:46

<script type="text/javascript"><!-- google_ad_client = "pub-1992382271196226";/* 728x90, 创建于 08-3-9 */google_ad_slot = "1653402536";google_ad_width = 728;google_ad_height = 90;// --></script><script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>

 

以下内容转自zol技术论坛,看原文点击此处。

 

perl 脚本中中文比较的问题
脚本为了支持中文,用了这句use encoding 'euc-cn', STDIN => 'euc-cn', STDOUT => 'euc-cn';
用queryvar()从网页的一个下拉框取到了一个字符串$number,
$number = $queryvar("line");
我用print把$number写到文件中,发现是utf8编码的"全部"。
在perl脚本里
$string1 = "全部";
if($string1 eq $number) 这个判断总是为假。

我改成
$string1 = "全部";
$string2 = Encode::decode("gb2312", $string1);
if($string2 eq $number), 结果程序进行到这里后就没有在走下去了。
请问这是因为什么问题?非常感谢!!

--------------------------------------------------------------------------------

啊,终于解决了,需要把两个字符串的utf8 flag的状态都调成一样的,最终改成
$string1 = /"全部/";
$string1 = Encode::decode(/"gb2312/", $string1);
Encode::_utf8_off($string1);
if($string1 eq $number)就可以了。

我还有一个问题:
怎么样可以把整个脚本的utf8 flag的状态都调成开启或关闭的呢?


--------------------------------------------------------------------------------

#4的代码有点问题。
虽然Encode::_utf8_off能关闭utf8标志,但并不是好办法。

先来看看下面的原因。

perl的内部格式就是所谓的“宽字符”,用一个正整数(好像是16位)来表示字符,而不仅仅限于ASCII的0-255的范围。
这样Unicode的所有字符都可以通过这个正整数来表示。
但问题是,这些16位正整数是无法存储到磁盘上的,因为存储时需要按照字节来存储,字节只有8位,无法保存16位正整数。
如何将这个16位数变成8位的字节,这就是我们通常所说的编码。

把16位数直接保存成两个字节,这就是 UTF-16,根据高位在前还是低位在前,可分别称为 UTF-16BE 和 UTF-16LE。
Windows默认的Unicode编码就是这个。你可以打开记事本,随便输入点什么,保存,
在保存对话框中就能看到 Unicode(即UTF-16LE)和Unicode big endian(即 UTF-16BE)。

但这样有两个缺点:
1. 任何字符都会占两个字节,即使是纯英文也需要两个字节——一个是00,一个是英文字母的ascii。极大地浪费空间。
2. 字节顺序不好确定,仅通过编码本身你无法判断究竟是UTF-16BE还是UTF-16LE。因此必须引入一个称为 BOM 的标志。用notepad编辑个文件,保存成Unicode再用16进制打开,就能看到前两个字节为 FFFE 或是 FEFF,这就是用来区分是BE还是LE的BOM标志。

于是就诞生了UTF-8,将16进制整数用可变长度的编码来处理。最常见的英文字母用一个字节,而中文、日文等亚洲文字就使用三字节。

回到perl里,其实明白了几个概念之后还是相当容易的。
1. 宽字符:就是前面说过的16位整数,是perl处理多字节字符的内部格式。
2. UTF-8:下文中指保存到磁盘上、用1~4个字节表示的真正的UTF-8可变长编码的字符串。

那么规则如下:
1. 从外部文件读入的都是字节流,编码与外部文件相同,即外部文件是UTF-8,字节流就是UTF-8。但Perl看不懂,perl只认为它是个普通的字节流。
2. 如果1中的字节流恰好是UTF-8编码的,那么你可以用 utf8::decode 将它转换成宽字符。
3. 2中的宽字符可以用utf8::encode转换成UTF-8(字节流)。
4. 对于源代码中直接写出的字符串(如楼上的 $string1 = /"全部/" ),如果在出现之前使用了 use utf8; ,那么字符串为宽字符;如果没有指定,或者指定了no utf8; ,那么字符串为UTF-8字节流。
5. 如果1中的字节流不是UTF-8编码,那么可以用 Encode::decode(编码名,字符串)将其转换为宽字符。
6. 与4相对,宽字符可以通过 Encode::encode(编码名,字符串) 转换成指定编码的字节流。

--------------------------------------------------------------------------------

回到楼主最初的问题。

use encoding /'euc-cn/', STDIN => /'euc-cn/', STDOUT => /'euc-cn/';

所以queryvar得到的结果的编码是euc-cn。

$string1 = /"全部/";

估计你没有使用use utf8,而且源代码也只是保存成了GB2312格式,
因此这里的$string1格式为gb2312的。

要想让 $string1 等于 $number,至少有三种方法:

1. 都转换成euc-cn做比较:
$string1 = Encode::from_to($string1, /'GB2312/', /'euc-cn/');
$string1 eq $number;

2. 都转换成gb2312做比较:
$number = Encode::from_to($number, /'euc-cn/', /'GB2312/');
$string1 eq $number;

3. 都转换成宽字符做比较:
Encode::decode(/'euc-cn/', $number);
Encode::decode(/'GB2312/', $string1);
$string1 eq $number;


但是,个人认为均不是好的解决方案。推荐使用以下做法:

1. 将提交数据的网页的编码做成UTF-8的,这样提交到这个页面的数据就是UTF-8编码,queryvar得到的字符串也是utf-8字节流;
2. 源代码保存成UTF-8编码;
3. 源代码中不要使用 use utf8,保证$string1为 UTF-8字节流,而不是宽字符;
4. 直接做 $number eq $string1.

如果你想使用宽字符,那么可以
3. 源代码使用use utf8;
4. utf8::decode($number);
5. $number eq $string1.

 

 

 

-------------------

最后还有一篇文章也是跟这个有关的,

Perl with UTF-8 mode

 

<script type="text/javascript"><!-- google_ad_client = "pub-1992382271196226";/* 728x15, 创建于 08-9-3 */google_ad_slot = "9127232582";google_ad_width = 728;google_ad_height = 15;// --></script><script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>