paktc 发表于 2013-12-30 21:44

【分享】Perl读取和生成、显示UTF-8文本、UTF-16文本

本帖最后由 paktc 于 2013-12-31 18:04 编辑

    去年的时候就看了网上有关的一些文章。但也就是临时需要,临时看看。
没有去探索究竟是咋回事。最近又翻了不少文章,做了一个小小的总结,结合了自己的观点。
以下讨论全部是基于windows xp平台。

参考文章:
关于Perl对中文的处理问题
有了这篇文章我们知道以utf-8形式保存的perl脚本,如何在屏幕上输出我们想要的中文。

Perl Unicode全攻略
这篇就更详细了,如何转码,有几种方法,终端显示、GUI的时候是以什么编码输出,应有尽有

Perl如何创建一个UTF-8的文件
CSDN的一个问答贴(代码见7楼、11楼)

在windows下读取utf-8文本一定会遇到BOM的问题,于是下面几个文章也少不了了:
UTF-8、BOM、<feff>的问题
「带 BOM 的 UTF-8」和「无 BOM 的 UTF-8」有什么区别?
Windows 记事本的 ANSI、Unicode、UTF-8 这三种编码模式有什么区别?

   好了,资料那么多,其实都轮不到我说什么。做了几种情况下的测试,在ANSI格式下保存的perl脚本
处理utf-8文本的时候遇到了不少头痛的问题,最后自己做了一些总结,一句话描述就是:
用ANSI格式保存的perl 如何正确显示、读取、生成 UTF-8格式的文本。

用"中文"两个字作为示例,先做一个简单的解析,稍后作为参考:use Encode;
$text="中文";
printf "“$text”一词在三种状态下的编码数据:\n";
printf("%-25s","原GB2312编码:");
xcode($text,'x');
printf("%-25s","根据其编码信息,解码为统一码:");
xcode(decode('gb2312',$text),'x');
printf("%-25s","按UTF-8编码:");
xcode(encode('utf8',decode('gb2312',$text)),'x');
print "\n";
<STDIN>;

#一个用来显示编码的函数
sub xcode {
      # xcode("string",'Mode'); Mode = x(hex), b(bin), d(int)
      for my $v ( split(//,$_) ) {
                print sprintf ("%l$_ ",ord($v));      
      }
      print "\n" if (!defined $_);
}


“中文”一词在三种状态下的编码数据:
原GB2312编码:            d6 d0 ce c4
按GB2312解码为统一码:    4e2d 6587
按UTF-8编码:             e4 b8 ad e6 96 87

$data=decode('gb2312',$text) 对 $text 解码成 “统一码” 到 $data (参考 http://zh.wikipedia.org/wiki/Unicode)
这个过程就像有一个八进制的数字要转为十六进制,我们暂时先转到十进制,然后再转十六进制。
为什么不直接转呢?只因为我们想要先知道这个数在我们熟悉的形式是怎么样的,让我们有一个清楚的对照。

"写"的操作 (脚本用ANSI文本形式保存)use Encode;
#生成utf-8文本

#正确的方法1
open WRT,">:utf8","UTF-8 1.txt";
print WRT decode('gb2312',"中文");#通过decode解码为“统一码”
close WRT;                           #然后perl以:utf8形式写入文本时帮我们将“统一码”转为UTF-8编码

#错误示例
open WRT,">:utf8","UTF8 ERR.txt";
print WRT "中文";                   #此时"中文"的编码还是GB2312的形式
close WRT;                        #perl将其当做“中间数据”直接转为utf8将得到错误的结果

#正确的方法2
open WRT,">:raw","UTF8 2.txt";
print WRT encode("utf-8",decode('gb2312',"中文"));#用:raw模式,直接以UTF-8编码的形式写入。
close WRT;
用notepad打开三种方法输出的文本显示如下:


    也就是说,要让perl输出时做出正确的转换,先要把数据转化为 “通用” 的数据。
或者就干脆不要让perl帮你做转换,自己用函数编码出正确的UTF-8格式的“中文”编码,
并以:raw 形式写入文本。

下面是读取、在终端输出的操作:(脚本以ANSI格式保存)
错误的示例:use Encode;

open READ,"<:utf8","UTF-8 1.txt";
$text=<READ>;
print $text;
close READ;
<STDIN>;原本的中文被显示为:涓 枃 并提示wide character。
      原因是perl用<:utf8 模式读取的时候,分析了utf-8编码并转码为“统一码”:
0x4e2d 0x6587,在cmd终端上显示需要再编码为GB2312格式。把print $text 改
为 print encode('gb2312',$text);就可以显示正确的结果

      或者用<:raw模式读入,得到的是UTF-8编码的数据,
先 decode('utf-8',$text) 转到中间数据然后再将返回的结果encode('gb2312', ... 并打印到终端
use Encode;

open READ,"<:raw","UTF-8 1.txt";
$text=<READ>;
print encode('gb2312',decode('utf-8',$text));
close WRT;
<STDIN>;
windows下读取带BOM(\xef\xbb\xbf 位于文件头)的UTF-8文本
方法有几种:
一、"<:raw"模式读入文本数据,去掉\xef\xbb\xbf 对剩下的数据转码。
二、用 <:utf8模式读取(<:raw读取后进行decode也行) perl不会帮你去掉\xef\xbb\xbf
而是像对待所有文本一样,对其转码,变成了\x{feff} ,这个时候去掉\xfe\xff 就是了。use Encode;

#第一种方法
open FILE, "<:raw","UTF-8 with BOM.txt";
$raw=<FILE>;
$raw=~s/\xef\xbb\xbf//;
print encode('gb2312',decode('utf-8',$raw));
close FILE;

#第二种方法A
open FILE, "<:utf8","UTF-8 with BOM.txt";
$data=<FILE>;
$data=~s/\x{feff}//;
print encode('gb2312',$data);
close FILE;

#第二种方法B
open FILE, "<:raw","UTF-8 with BOM.txt";
$raw=<FILE>;
$data=decode('utf-8',$raw);
$data=~s/\x{feff}//;
print encode('gb2312',$data);
close FILE;
<STDIN>;2013-12-30
补充,以上所说的“统一码”,请参考 http://zh.wikipedia.org/wiki/Unicode

2013-12-31
读windows下的unicode文本(UTF-16)
这里一定会遇到BOM,参考这篇文章:
Unicode签名bom
         所谓的unicode保存的文件实际上是utf-16,只不过恰好跟unicode的码相同而已,
    在概念上unicode与utf是两回事,unicode是内存编码表示方案,而utf是如何保存和
    传输unicode的方案。

示例文本unicode.txt(另存的时候选unicode)

[*]中国制造
[*]第二行


第一行的编码是:ff fe 2d 4e fd 56 36 52 20 90 d 0 a   
第二行的编码是:0 2c 7b 8c 4e 4c 88
(ff fe 就是文件头,BOM)

decode('utf-16',$uni_str) 的时候和处理UTF-8的时候是不一样的。它会帮你去掉BOM。
所以不需要手动去掉\xFF\xFE ,在处理文本第二行的时候提示
UTF-16:Unrecognised BOM 2c …… 因为第二行是不带BOM信息的,所以
把全文合并到一个变量中,做一次处理就可以得到正确的结果



mcshell 发表于 2013-12-30 21:53

这个不错支持下:em03:

yinyuemi 发表于 2013-12-31 08:49

Mark~                                    

seufy88 发表于 2013-12-31 09:09

支持一下。

pitonas 发表于 2013-12-31 15:03

支持 {:2_172:}

paktc 发表于 2013-12-31 20:47

本帖最后由 paktc 于 2013-12-31 22:01 编辑

这里咨询大家一个问题,
在尝试写UTF-16BE文本的时候,\r\n 写入文本后,编码总是不正确。
其他字符比如 tab 都正常,这个问题如何解决?

代码保存为ANSI格式use Encode;
open WRT,'>:encoding(utf-16)','encoding.txt' or die "$!";
print WRT decode('gb2312',"中        文\r\n");      # 1
close WRT;好像是activeperl的问题?在ubuntu写了个脚本(用utf-8格式保存的),
#!/usr/bin/perl -s
use Encode;
chdir "~/桌面";
open WRT,">encoding(UTF-16):crlf","new.x";
print WRT decode('utf-8',"中文\n");
close WRT;
生成的txt文件复制到windows查看,换行正常。
同样的脚本复制到windows执行,行尾就变成了两个黑色符号。
……

Using :encoding and :crlf together?
The :crlf PerlIO layer doesn't like the :encoding layer.

在这里找到了最终答案 关键词 "perlwrite utf-16 text crlf"
Writing a Unicode file via perl ...
http://blogs.msdn.com/b/brettsh/archive/2006/06/07/620986.aspx

hztj2005 发表于 2019-03-08 10:03

留个记号,备用。
虽然用处理过许多中文文件,但有些东西记不住,经常折腾好半天才解决。

zhouzhen1 发表于 2019-05-06 00:01

支持一下。
页: [1]
查看完整版本: 【分享】Perl读取和生成、显示UTF-8文本、UTF-16文本