perl 脚本中中文比较的问题

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), 结果程序进行到这里后就没有在走下去了。
请问这是因为什么问题?非常感谢!!

还是贴全部代码吧
你的意思是程序hang在那一行了?
不太可能
比较的结果要们是等于要们是不等于
还有perl内部是用utf8来编码的
所以你用euc-cn的编码来比较肯定是不对的
用decode处理以后是对的,decode的意思只是说将外部的编码按照指定的编码方式转化成perl的内部编码


QUOTE:
原帖由 churchmice 于 2008-9-18 23:26 发表
还是贴全部代码吧
你的意思是程序hang在那一行了?
不太可能
比较的结果要们是等于要们是不等于
还有perl内部是用utf8来编码的
所以你用euc-cn的编码来比较肯定是不对的
用decode处理以后是对的,decode的 ...

代码大致如下:
  $string1 = "全部";
  if( "$number" eq $string1 )
  {
   #测试成功与否,只取3条看看效果
    $sqlquery .= " LIMIT $start, 3";
  }
  else
  {
    $sqlquery .= " LIMIT $start, $number";
  }
我做的是一个CGI的程序,调用了perl脚本去数据库中取一定量的记录来看,
选择10或者20等等数字后,就可以查出10条或者20条记录。
问题就是出在了选择"全部"后,if($string1 eq $number) 这个判断总是为假。
改成$string1 = "全部";
$string2 = Encode::decode("gb2312", $string1);
if($string2 eq $number)后,就没有显示出任何记录。
但是如果把网页上的"全部"改成"all",并且把代码里的全部也改成"all",就判断正确,只显示出了3条记录。
啊,终于解决了,需要把两个字符串的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.
另外楼主4楼的问题应该也很好解决了吧。

答案自己想,6楼中已经有了