加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

perl文件编码处理问题

发布时间:2020-12-16 00:10:33 所属栏目:大数据 来源:网络整理
导读:在看perl时,发现文件编码处理问题不是很懂。BG了下,发现还是有点东东,so,收藏下: --------------------------1----------------------------------- 一个脚本用于分析应用程序的日志,但是在这些日志文件中,有的编码是utf-8的,有的编码是gbk的.对于utf-8

在看perl时,发现文件编码处理问题不是很懂。BG了下,发现还是有点东东,so,收藏下:

--------------------------1-----------------------------------

一个脚本用于分析应用程序的日志,但是在这些日志文件中,有的编码是utf-8的,有的编码是gbk的.对于utf-8的日志文件,需要将内容转换成gbk的编码,这样看到的中文才不至于乱码:

因此,在perl脚本中加入以下行判断日志内容,如是不是gbk的编码,就对其转码:

$line=encode ("gbk",decode("utf-8",$line)) unless (detect($line) =~ /gb/);

这里用到的模块有:

use Encode;

use Encode::CN;

use Encode::Detect::Detector;

Perl5.8中读取其他编码的文本文件

通过改变PerlIO层的编码即可实现,配合encoding指示符可以使操作过程更为自然,如读取UTF-16编码的文本文件:
use encoding 'gbk';??????????????????????? ? ? # 系统默认编码为GBK
use open IN=>':encoding(utf16)';??? ? ?# 读入文件时认为数据按UTF-16编码,自动根据BOM头判断是LE还是BE
open(FH,"test.txt") or die;
while(<FH>) {???????????????????????????????? ? ??# 读入数据时自动从UTF-16转换为标准的UTF-8编码的Perl字符串
??? chomp;
??? print "$_n";????????????????????????????? ?? ??# 由于encoding指定了系统默认编码,输出时数据自动从UTF-8转换为GBK
}

也可以用open的3参数形式指定单个文件句柄的PerlIO层编码,并在输出时手工转换编码:
use Encode;?????????????????????????????? ? ? ??# 需要使用encode函数实现手工转码
open(FH,"<:encoding(utf16)","test.txt") or die;?????? ?# 指定FH句柄的数据为UTF-16编码
while(<FH>) {??????????????????????????? ??? ? ? # 读入数据同样自动转换为UTF-8
??? chomp;
??? print encode("gbk",$_),"n";????? ???# 手动将UTF-8编码字符串转换为GBK编码字符串输出
}

另外使用binmode可以随时切换某个文件句柄的PerlIO层编码,如:
binmode(FH,":encoding(utf16)"); ? ? # 将FH的数据编码置为UTF-16
binmode(FH,":raw");????????????????????? ???# 不对FH的数据进行编解码处理
binmode(FH,":utf8");???????????????????? ???# 将FH的数据编码置为UTF-8

--------------------------2-----------------------------------

Perl从5.6开始已经开始在内部使用utf8编码来表示字符,也就是说对中文以及其他语言字符的处理应该是完全没有问题的。我们只需要利用好Encode这个模块便能充分发挥Perl的utf8字符的优势了。

下面就以中文文本的处理为例进行说明,比如有一个字符串"测试文本",我们想要把这个中文字符串拆成单个字符,可以这样写:

use Encode;

$dat="测试文本";

$str=decode("gb2312",$dat);

@chars=split //,$str;

foreach $char (@chars) {

???????? print encode("gb2312",$char),"n";

结果大家试一试就知道了,应该是令人满意的。

这里主要用到了Encode模块的decode、encode函数。要了解这两个函数的作用我们需要清楚几个概念:

1、Perl字符串是使用utf8编码的,它由Unicode字符组成而不是单个字节,每个utf8编码的Unicode字符占1~4个字节(变长)。

2、进入或离开Perl处理环境(比如输出到屏幕、读入和保存文件等等)时不是直接使用 Perl字符串,而需要把Perl字符串转换成字节流,转换过程中使用何种编码方式完全取决于你(或者由Perl代劳)。一旦Perl字符串向字节流的编码完成,字符的概念就不存在了,变成了纯粹的字节组合,如何解释这些组合则是你自己的工作。

我们可以看出如果想要Perl按照我们的字符概念来对待文本,文本数据就需要一直用Perl 字符串的形式存放。但是我们平时写出的每个字符一般都被作为纯ASCII字符保存(包括在程序中明文写出的字符串),也就是字节流的形式,这里就需要 encode和decode函数的帮助了。

encode函数顾名思义是用来编码Perl字符串的。它将Perl字符串中的字符用指定的编码格式编码,最终转化为字节流的形式,因此和Perl处理环境之外的事物打交道经常需要它。其格式很简单:

$octets = encode(ENCODING,$string [,CHECK])

$string:  Perl字符串

encoding: 是给定的编码方式

$octets:  是编码之后的字节流

check:   表示转换时如何处理畸变字符(也就是Perl认不出来的字符)。一般不需使用编码方式视语言环境的不同有很大变化,默认可以识别utf8、ascii、ascii-ctrl、iso-8859-1等。

decode函数则是用来解码字节流的。它按照你给出的编码格式解释给定的字节流,将其转化为使用utf8编码的Perl字符串,一般来说从终端或者文件取得的文本数据都应该用decode转换为Perl字符串的形式。它的格式为:

$string = decode(ENCODING,$octets [,CHECK])

$string、ENCODING、$octets和CHECK的含义同上。

现在就很容易理解上面写的那段程序了。因为字符串是用明文写出的,存放的时候已经是字节流形式,丧失了本来的意义,所以首先就要用decode函数将其转换为Perl字符串,由于汉字一般都用gb2312格式编码,这里decode也要使用gb2312编码格式。转换完成后Perl对待字符的行为就和我们一样了,平时对字符串进行操作的函数基本上都能正确对字符进行处理,除了那些本来就把字符串当成一堆字节的函数(如vec、pack、unpack等)。于是split就能把字符串切成单个字符了。最后由于在输出的时候不能直接使用utf8 编码的字符串,还需要将切割后的字符用encode函数编码为gb2312格式的字节流,再用print输出。

--------------------------3-----------------------------------

耐心看完本文,相信你今后在unicode处理上不会再有什么问题.

本文内容适用于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是一个字符串比较操作符,只有当字符串的内容一致并且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的<head>标签里获得. 也有可能出现响应头和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: $@n" if $@;

eval {my $str2 = $str; Encode::decode("utf8",1)};
print "not utf8: $@n" if $@;

eval {my $str2 = $str; Encode::decode("big5",1)};
print "not big5: $@n" if $@;

输出:

程序代码:
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,CHECK]) 把字符串从utf8编码转成指定的编码,并关闭utf8 flag.

$string = decode(ENCODING,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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读