使用 Encode::Guess 遇到的錯誤

46 views
Skip to first unread message

Calvin

unread,
Mar 19, 2009, 7:24:47 AM3/19/09
to PerlChina Mongers 讨论组
按照網上搜索到的 Encode:;Guess 的資料,寫了個 test.pl,內容:

#!/usr/bin/perl

use Encode;
use Encode::Guess qw/utf-8 euc-cn gbk utf-16le/;
$string="出征进行曲";
$decoder = guess_encoding($string);
$info=$decoder->decode($string);
print "$info\n";

這個 test.pl 保存成 utf8 txt檔案,執行,出錯:

Can't locate object method "decode" via package "utf-8-strict or
utf8" (perhaps you forgot to load "utf-8-strict or utf8"?) at ./
test.pl line 6.

按照網上的例子和官方說明,Encode::Guess是這樣用的沒錯啊,怎麼回事呢?

purl lamp

unread,
Mar 19, 2009, 8:50:32 AM3/19/09
to perl...@googlegroups.com
如果已经是 utf8,那么还有什么必要去 encode 呢?
应该是对其他码制做这个测试才对,如果 !ref $enc 就不必转换了。

2009/3/19 Calvin <calvi...@gmail.com>

Okàjn

unread,
Mar 19, 2009, 9:24:30 AM3/19/09
to perl...@googlegroups.com
use Encode::Guess qw/utf-8 euc-cn gbk utf-16le/;
改为
use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;

运行结果:

Wide character in print at ./test.pl line 7.
出征进行曲



2009/3/19 Calvin <calvi...@gmail.com>

Calvin

unread,
Mar 19, 2009, 10:12:48 AM3/19/09
to PerlChina Mongers 讨论组
你試試把下面的存為 utf8文件執行一下:

#!/usr/bin/perl
use Encode;
use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;
$string="出征进行曲";
$string=encode('gbk',$string);
$decoder = guess_encoding($string);
$info=$decoder->decode($string);
print "$info\n";

On 3月19日, 下午9时24分, Okàjn <okajn...@gmail.com> wrote:
> use Encode::Guess qw/utf-8 euc-cn gbk utf-16le/;
> 改为
> use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;
>
> 运行结果:
>
> Wide character in print at ./test.pl line 7.
> 出征进行曲
>
> 2009/3/19 Calvin <calvin.n...@gmail.com>

Okàjn

unread,
Mar 19, 2009, 11:14:41 AM3/19/09
to perl...@googlegroups.com
这么写本身就有点问题吧,encode实际是str2bytes,

$string = "出征进行曲";
代码里明文写出的字符串就是以bytes存在$string中,不应该对$string再encode


2009/3/19 Calvin <calvi...@gmail.com>

cnhack TNT

unread,
Mar 19, 2009, 9:42:26 PM3/19/09
to perl...@googlegroups.com
首先,需要分清楚“字符(character)”和“字节流(Octet stream)”的概念,你的perl程序所取到的输入,以及它对外的输出(就像你用print打印)都是字节流,字节流是没有语义的,对perl来说它就是一堆字节,没有额外的意义。
而字符串和字节流是不同的,字符串有语义,它代表某个或某些个字符,直白来说,我们看到的“abcd1234“等等都是字符。而字节流可以用来表示字符串,同一个字符串,它对应的字节流可以不同,为什么呢?因为同一个字符串可以用不同的编码方式(如utf8或gb2312)来编码,编码过后得到的,便是相对应的字节流。
在perl中,你输入的一个字符串是以字节流的形式传递给perl的,如果你想perl把你输入的字符串真正当成字符串来理解和操作,你需要告诉perl这串字符的编码是什么,比如(我的环境为utf8):

use Encode;
$string="中国";
$string_decoded=decode_utf8($string);

此时,$string 对perl来说只是用utf8编码过的字节流(十六进制为\xE4\xB8\xAD\xE5\x9B\xBD),这个时候,perl只会按字节来对他操作,因为perl不知道它是啥东西。
而 $string_decoded 对perl来说就是有语义的字符串了,虽然他本身在perl内部是以UTF8编码的方式存储的,但是它已经被打上了标记,perl知道它是字符串,该按字符来操作,此时如果你用substr之类的函数对 $string_decoded 操作的话,便是按 “中(\xE4\xB8\xAD)”,“国(\xE5\x9B\xBD)” 两个字符来操作了,而不是在未 decode 之前,以一个字节一个字节的方式处理。

在输出的时候,我们应该仍以字节流的方式输出,因为字符只是一个概念,一个具象,它可以有不同的表现形式(不同编码的字节流),这个时候你可以根据下一个需要取得你这个输出作为输入的目标程序的要求(网络要求,编码要求),来对你的输出进行编码(成字节流),然后再传输给它。所以这个时候,你需要对需要输出的字符串进行 encode,比如:

use Encode;
$string="中国";
$string_decoded=decode_utf8($string);
$string_encoded=encode("gb2312", $string_decoded);
print $string_encoded;

这个时候, $string_encoded 中便是以 gb2312 编码方式编码过的字节流了(它同样代表“中国”这个字符串)。
如果你直接输出 $string_decoded 而不做 encode 的话,perl 便会按这个字符串在其内存中保存的方式(也就是utf8)输出,如果你是 utf8 的环境,你可以看到正确的字符串,但如果你这个字符串里面一旦包含了大于\xFF的字节,那么perl会警告"Wide character in print..."。那为什么我 encode 了这个字符串后perl不会报这个警告呢?那是因为perl会对“字符串”做标记,decode会打上标记,encode后会去掉这个标记,一旦你输出的时候有这个标记,并且有字节大于\xFF,perl就会丢出那个警告,下次你看到这个警告,一眼就可以看出是因为你程序的某个输出没有经过编码转换成字节流的缘故的。


呃。。。我是不是有点太罗嗦了。。。
Anyway, 希望大家对 Perl 的 encoding 有个了解。

purl lamp

unread,
Mar 19, 2009, 10:21:48 PM3/19/09
to perl...@googlegroups.com
thanks a lot, 非常清晰的描述,让大家都明白什么是字节流,这个词选的很好,难以找出其他的替代。PerlIO 里面如此描述:

       :bytes
           This is the inverse of ":utf8" layer. It turns off the flag on the
           layer below so that data read from it is considered to be "octets"
           i.e. characters in range 0..255 only. Likewise on output perl will
           warn if a "wide" character is written to a such a stream.

perlrun 里面如此描述:

       -C [number/list]
            The "-C" flag controls some of the Perl Unicode features.

            As of 5.8.1, the "-C" can be followed either by a number or a list
            of option letters.  The letters, their numeric values, and effects
            are as follows; listing the letters is equal to summing the
            numbers.

                I     1   STDIN is assumed to be in UTF-8
                O     2   STDOUT will be in UTF-8
                E     4   STDERR will be in UTF-8

所以还可以用 -C 6 来关闭 "wide character" 警告。

更多的信息还可以参考 cnhacktnt 05 年在 perlchina 的演讲 unicode in 5 minute

2009/3/20 cnhack TNT <cnha...@gmail.com>

msmouse

unread,
Mar 19, 2009, 10:47:51 PM3/19/09
to perl...@googlegroups.com
高人讲得清晰。
另最后一段的\xFF是否指\x80 ?
----------------------------------
msm...@ir.hit.edu.cn
msm...@gmail.com


2009/3/20 cnhack TNT <cnha...@gmail.com>

Hua Zhen

unread,
Mar 19, 2009, 10:53:01 PM3/19/09
to perl...@googlegroups.com
拜谢,讲的真清楚啊!

2009/3/20 cnhack TNT <cnha...@gmail.com>



--
凡事包容,凡事相信,凡事盼望,凡事忍耐。

Okàjn

unread,
Mar 19, 2009, 11:15:59 PM3/19/09
to perl...@googlegroups.com
敬业精神,值得学习~~

2009/3/20 cnhack TNT <cnha...@gmail.com>

cnhack TNT

unread,
Mar 20, 2009, 12:53:35 AM3/20/09
to perl...@googlegroups.com
指的是 \xFF, 大于255,Unicode 的码值范围比一个字节能表示的大多了


2009/3/20 msmouse <msm...@gmail.com>
高人讲得清晰。
另最后一段的\xFF是否指\x80 ?
----------------------------------
msm...@ir.hit.edu.cn
msm...@gmail.com

purl lamp

unread,
Mar 20, 2009, 1:05:56 AM3/20/09
to perl...@googlegroups.com
用 blt 跟踪 twitter 的话,可以应用一下这个技术:

s/twitter_following($last_fetch)/encode('utf-8‘,twitter_following($last_fetch))/

当然还要 use Encode;
从而避免每次 blt -Sc 都看到警告。

2009/3/20 cnhack TNT <cnha...@gmail.com>

msmouse

unread,
Mar 20, 2009, 1:08:41 AM3/20/09
to perl...@googlegroups.com
收到。实测了下确实可以打印127以上的字节而不引发warning。
不过貌似不该用“大于\xFF的字节”的说法。
----------------------------------
msm...@ir.hit.edu.cn
msm...@gmail.com


2009/3/20 cnhack TNT <cnha...@gmail.com>
指的是 \xFF, 大于255,Unicode 的码值范围比一个字节能表示的大多了

purl lamp

unread,
Mar 20, 2009, 1:20:33 AM3/20/09
to perl...@googlegroups.com
确实应该说大于\xFF的字,而不是字节。

2009/3/20 msmouse <msm...@gmail.com>

Calvin

unread,
Mar 20, 2009, 1:32:30 AM3/20/09
to PerlChina Mongers 讨论组
可是,即使用 $string=decode_utf8($string) 先标记让perl明白它是一个字串,用 Encode::Guess 猜编
码仍然出错,比如下面的脚本:

#!/usr/bin/perl
use Encode;
use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;
$string="出征进行曲";

$string=decode_utf8($string);
$string=encode('gbk',$string);
print $string;
$decoder=guess_encoding($string);


$info=$decoder->decode($string);
print "$info\n";

这里运行的错误仍然如下:
Can't locate object method "decode" via package "euc-cn or cp936 or
UTF-16LE" (perhaps you forgot to load "euc-cn or cp936 or UTF-16LE"?)
at ./test.pl line 9.

> 2009/3/19 Okàjn <okajn...@gmail.com>


>
> > 这么写本身就有点问题吧,encode实际是str2bytes,
>
> > $string = "出征进行曲";
> > 代码里明文写出的字符串就是以bytes存在$string中,不应该对$string再encode
>

> > 2009/3/19 Calvin <calvin.n...@gmail.com>

Tian Yazhou

unread,
Mar 20, 2009, 2:43:40 AM3/20/09
to perl...@googlegroups.com


2009/3/20 Calvin <calvi...@gmail.com>:

> 可是,即使用 $string=decode_utf8($string) 先标记让perl明白它是一个字串,用 Encode::Guess 猜编
> 码仍然出错,比如下面的脚本:
>
> #!/usr/bin/perl
> use Encode;
> use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;
> $string="出征进行曲";
> $string=decode_utf8($string);
> $string=encode('gbk',$string);
> print $string;
> $decoder=guess_encoding($string);
> $info=$decoder->decode($string);
> print "$info\n";
>
> 这里运行的错误仍然如下:
> Can't locate object method "decode" via package "euc-cn or cp936 or
> UTF-16LE" (perhaps you forgot to load "euc-cn or cp936 or UTF-16LE"?)
> at ./test.pl line 9.
>
=head1 说明
我稍微改了下你的脚本,你看看
=cut

use Encode;
 
# 可能的编码个数越少越好,我不清楚euc-cn和gbk是什么关系,有人知道么?
# 我发现去掉gbk好像还是可以猜测出来的
# 但是加上以后,guess encoding那一步就会失败
use Encode::Guess qw/utf8 euc-cn gbk/;

#@list = Encode->encodings('Encode::CN');
#foreach(@list){
#    print;
#    print "\n";
#}

# 这里的字符串要够长才会比较准确,我在Encode::Guess的doc里面看到的
$string="出征进行曲阿似的阿阿似的发就哦阿发阿地方就阿阿姐防火墙喔喔去哦去恶气外婆千万千万放进去全家福气氛清华发送到发灰色的阿斯拉夫哈似的发哦发达哈阿发达抵抗力萨回到发思考发达克拉发哈利税分流喀什地方金卡方鸿渐阿叔地方金卡是地方起方法瓦斯大家宋江阿飞>垃圾阿发所发所发似的发所发所发发阿发达地方阿阿发的阿发发达发达发达";

#$string = encode('cp936', decode('utf8', $string));  这个应该不需要,因为这个时候string已经是Perl内部格式了
$string_en = encode('euc-cn',  $string);

print $string_en, ' ', $string, "\n";
$decoder=guess_encoding($string_en, qw/utf8 euc-cn gbk/);
use Data::Dumper;
print Dumper $decoder;

# 你少了这一步,这个是必须的
# 你的那个错误就是因为ref($decoder)是空值,也就是说猜测失败了
ref($decoder) or die "Can not determine\n";
print "I guess it's ", $decoder->name, "\n";

# 我在这里有个问题,及时猜测成功,这里在utf8编码的gnome-terminal下面打印出来的结果依然是乱码
# 真是奇怪,期待大牛出来解答
$info=$decoder->decode($string_en);
print "$info\n";


--
You should be the change you wish to see in the world

wd

unread,
Apr 2, 2009, 3:19:43 AM4/2/09
to perl...@googlegroups.com


2009/3/20 Tian Yazhou <phio...@gmail.com>



2009/3/20 Calvin <calvi...@gmail.com>:

> 可是,即使用 $string=decode_utf8($string) 先标记让perl明白它是一个字串,用 Encode::Guess 猜编
> 码仍然出错,比如下面的脚本:
>
> #!/usr/bin/perl
> use Encode;
> use Encode::Guess qw/utf8 euc-cn gbk utf-16le/;
> $string="出征进行曲";
> $string=decode_utf8($string);
> $string=encode('gbk',$string);
> print $string;
> $decoder=guess_encoding($string);
> $info=$decoder->decode($string);
> print "$info\n";
>
> 这里运行的错误仍然如下:
> Can't locate object method "decode" via package "euc-cn or cp936 or
> UTF-16LE" (perhaps you forgot to load "euc-cn or cp936 or UTF-16LE"?)
> at ./test.pl line 9.
>
=head1 说明
我稍微改了下你的脚本,你看看
=cut

use Encode;
 
# 可能的编码个数越少越好,我不清楚euc-cn和gbk是什么关系,有人知道么?
刚好今天看到了这个
http://search.cpan.org/~dankogai/Encode-2.33/lib/Encode/Supported.pod

Standard      DOS/Win Macintosh                Comment/Reference
----------------------------------------------------------------
euc-cn [1] MacChineseSimp
(gbk) cp936 [2]
gb12345-raw { GB12345 without CES }
gb2312-raw { GB2312 without CES }
hz
iso-ir-165
----------------------------------------------------------------

[1] GB2312 is aliased to this. See L<Microsoft-related naming mess>
[2] gbk is aliased to this. See L<Microsoft-related naming mess>

Reply all
Reply to author
Forward
0 new messages