为啥我的 Perl 程序使用 Tie::File 和 Unicode/UTF-8 编码失败?

Posted

技术标签:

【中文标题】为啥我的 Perl 程序使用 Tie::File 和 Unicode/UTF-8 编码失败?【英文标题】:Why is my Perl program failing with Tie::File and Unicode/UTF-8 encoding?为什么我的 Perl 程序使用 Tie::File 和 Unicode/UTF-8 编码失败? 【发布时间】:2012-10-23 22:52:05 【问题描述】:

我正在从事一个处理外语数据的项目。我的 Perl 脚本运行良好。

然后我想使用 Tie::File,因为这是一个简洁的概念(并且可以节省时间和编码)。

似乎 Tie:File 在 Unicode/UTF-8 下失败(除非我遗漏了什么)。

这是一个描述问题的程序:(数据是英语、希腊语和希伯来语的混合体):

use strict;
 use warnings;
 use 5.014; 
 use Win32::Console;
 use autodie; 
 use warnings qw< FATAL utf8 >;
 use Carp;
 use Carp::Always;
 use utf8;
 use feature        qw< unicode_strings>;
 use charnames      qw< :full>;
use Tie::File;

my ($i);
my ( $FileName);
my (@Tied);
binmode STDOUT, ':unix:utf8';
binmode STDERR, ':unix:utf8';
binmode $DB::OUT, ':unix:utf8' if $DB::OUT; # for the debugger
Win32::Console::OutputCP(65001);         # Set the console code page to UTF8

$FileName = 'E:\\My Documents\\Technical\\Perl\\Eclipse workspace\\Work\\'.
        'Tie File test res.txt';
tie @Tied, 'Tie::File', $FileName, recsep => "\x0D\x0A", discipline => ':encoding(utf8)'
            or confess 'tie @Tied failed';
$i =0;
while (<DATA>) 
    chomp;
    $Tied[$i] = $_;
    ++$i;
 # end while (<DATA>) 
$i =0;
foreach (@Tied) 
    say "$i $Tied[$i]";
    ++$i;
 # end foreach (@Tied)
untie $FileName;
__DATA__
τι κάνετε;
πάρτε το ή αφήστε το
שלום חברים
abc לא כןכן efg
מתי ולאן This is it
מעכשיו לעכשיו 
Σήμερα είναι Τρίτη
Θέλω να φάω
τι κάνετε;
שורה מס' 5

这会产生大量的警告:这里有一些:

utf8 "\xCE" does not map to Unicode at F:/Win7programs/Dwimperl/perl/lib/Tie/File.pm line 917
        Tie::File::_read_record('Tie::File=HASH(0x24cb72c)') called at F:/Win7programs/Dwimper
l/perl/lib/Tie/File.pm line 175
        Tie::File::_fetch('Tie::File=HASH(0x24cb72c)', 0) called at F:/Win7programs/Dwimperl/p
erl/lib/Tie/File.pm line 210
        Tie::File::STORE('Tie::File=HASH(0x24cb72c)', 0, 'τι κάνετε;') called at tie file test
.pl line 31
utf8 "\xCF" does not map to Unicode at F:/Win7programs/Dwimperl/perl/lib/Tie/File.pm line 917
        Tie::File::_read_record('Tie::File=HASH(0x24cb72c)') called at F:/Win7programs/Dwimper
l/perl/lib/Tie/File.pm line 175
        Tie::File::_fetch('Tie::File=HASH(0x24cb72c)', 0) called at F:/Win7programs/Dwimperl/p
erl/lib/Tie/File.pm line 210
        Tie::File::STORE('Tie::File=HASH(0x24cb72c)', 0, 'τι κάνετε;') called at tie file test
.pl line 31
utf8 "\xD7" does not map to Unicode at F:/Win7programs/Dwimperl/perl/lib/Tie/File.pm line 917
        Tie::File::_read_record('Tie::File=HASH(0x24cb72c)') called at F:/Win7programs/Dwimper
l/perl/lib/Tie/File.pm line 175
        Tie::File::_fetch('Tie::File=HASH(0x24cb72c)', 0) called at F:/Win7programs/Dwimperl/p
erl/lib/Tie/File.pm line 210
        Tie::File::STORE('Tie::File=HASH(0x24cb72c)', 0, 'τι κάνετε;') called at tie file test
.pl line 31
utf8 "\xD7" does not map to Unicode at F:/Win7programs/Dwimperl/perl/lib/Tie/File.pm line 917
        Tie::File::_read_record('Tie::File=HASH(0x24cb72c)') called at F:/Win7programs/Dwimper
l/perl/lib/Tie/File.pm line 175
        Tie::File::_fetch('Tie::File=HASH(0x24cb72c)', 0) called at F:/Win7programs/Dwimperl/p
erl/lib/Tie/File.pm line 210
        Tie::File::STORE('Tie::File=HASH(0x24cb72c)', 0, 'τι κάνετε;') called at tie file test
.pl line 31

然后它在 STDOUT 上打印:

0 τι κάνετε;
1 πάρτε το ή αφήστε το
2 שלום חברים
3 abc לא כןכן efg
4 מתי ולאן This is it
5 מעכשיו לעכשיו
6 Σήμερα είναι Τρίτη
7 Θέλω να φάω
8 τι κάνετε;
9 שורה מס' 5
10
11
12
13
14 \xA4\xΘέλω\xA8\x

15
16
17
18

19

请注意,前 10 行没问题,但第 10 到 19 行不知从何而来!? 此外,捆绑文件的输出包含损坏的数据:

 τι κάνϏN͏Ŏՠτήστε של חברءbc לؗܗࠗܗߠeמתולאן This is מעיו לעכ؎Ďώݎ֏ναι ΤρΘέώގѠφϏŎ٠κτε;שרה מס'



\xA4\xΘέλω\xA8\x

这里有些不对劲。要么我遗漏了什么,要么 Tie:File 无法处理 Unicode/UTF-8? 我在 Windows 7 系统上运行 Strawberry Perl 5.14。

许多 TIA - 海伦

注意:也发布在http://perlmonks.org/?node_id=1002104

【问题讨论】:

(可能最有可能的)问题可能是您的数据一开始就没有正确编码。无论如何,这就是警告告诉你的。 @Mat:数据已正确编码。就像我上面说的,没有 Tie::File 一切正常。另请注意,STDOUT 上的打印输出很好(前 9 行) 您使用的是哪个编辑器,您确定它将源文件保存为 UTF-8 吗? (而且你不需要指定use feature qw&lt;unicode_strings&gt;;,因为它是通过use v5.14; 启用的。) @titanofold:我使用的是 Notepad++,以 UTF8 显式编码。 (有或没有 BOM - 结果是一样的)。主要问题不是警告:主要问题似乎是当绑定文件处理 UTF8 时,Tie::File 会破坏数据(删除一些字符并添加其他字符) 【参考方案1】:

我提出的建议很大程度上取决于您要解决的实际问题。 孤立地看这个问题,我不会有太多的编码/解码“魔法”,而只会使用原始字节(因为脚本不需要为此知道任何关于字符本身的信息)任务)。给定您描述的输入和输出,下面会产生预期的结果。

use v5.014;
use warnings;
use autodie;

use Carp::Always;
use Tie::File;

my $file_in = 'test_in.txt';
my $file_out = 'test_tie.txt';

unlink $file_out;

tie my @tied, 'Tie::File', $file_out, recsep => "\x0D\x0A" or die 'tie failed';

open my $fh, '<', $file_in;
while (my $line = <$fh>) 
    chomp $line;
    push @tied, $line;

close $fh;

my $i = 0;
say $i++ . ' ' . $_ foreach @tied;

untie @tied;

但是,您可能确实想对中间的文本进行一些处理。在这种情况下,您需要解码字符。在我看来,有两种选择:

    在移交给绑定阵列之前手动编码 找出 Tie::File 的问题

数字 2 可能很重要 - 快速扫描 Tie::File 源,看起来它假设它总是被赋予字节。您似乎可以影响的唯一部分是 https://metacpan.org/source/TODDR/Tie-File-0.98/lib/Tie/File.pm#L111 的 binmode - 您正在做的事情

Tie::File 做了很多 seek 调用,perldoc 在搜索时有这样的说法(http://perldoc.perl.org/functions/seek.html):

注意以字节为单位:即使文件句柄已设置为对字符进行操作(例如通过使用 :encoding(utf8) 开放层),tell() 将返回字节偏移量,而不是字符偏移量(因为实现它会使 seek() 和 tell() 变得相当慢)。

因此,Tie::File 似乎使用字符长度来确定其记录的字节偏移量。因此,它可能会出现在 UTF-8 字符序列的中间。这似乎是您的错误的一个可能原因。

一般来说,当依靠外部模块读取/写入文件句柄时,我会远离binmode - 在这种情况下,我会在推送到@tied 之前对数据调用Encode::encode('UTF-8', ...) 的简单子程序。

例外是模块的文档清楚地说明了解码数据的行为,或者源是否足够简单,我可以验证行为。

【讨论】:

谢谢,这很有启发性,我决定将其标记为已接受。虽然,我最终使用 tie with DB_file 和 DBM_filter,正如 remiah 在这里所建议的那样:perlmonks.org/?node_id=1002394。就可以了。

以上是关于为啥我的 Perl 程序使用 Tie::File 和 Unicode/UTF-8 编码失败?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 Perl 程序在一台机器上得到污染警告,而在另一台机器上却没有?

Perl:为啥在循环中声明(我的)变量会更慢?

当我的 Perl 程序在 cmd.exe 中输出 UTF-8 编码字符串时,为啥我会重复最后一个八位字节?

为啥我的 Perl CGI 脚本找不到 Oracle DBD?

为啥 Perl 的 GD::Graph 抱怨“无效数据集”?

为啥 Perl 在 ClearCase 中找不到我的文件?