Почему моя Perl-программа не работает с кодировкой Tie::File и Unicode/UTF-8?

Я работаю над проектом, который работает с данными на иностранных языках. Мои Perl-скрипты работали нормально.

Затем я захотел использовать Tie:: File, так как это аккуратная концепция (и экономит время и кодирование).

Кажется, что Tie: Файл не работает под 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? Я использую Strawberry Perl 5.14 в системе Windows 7.

Много ТИА - Хелен

Примечание: также размещено на http://perlmonks.org/?node_id=1002104

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;

Тем не менее, вы, вероятно, хотите выполнить некоторую обработку этого текста посередине. В этом случае вы хотите расшифровать символы. На мой взгляд, есть два варианта:

  1. Кодировать вручную перед передачей в связанный массив
  2. Выясните, в чем проблема с Tie::File

Номер 2, вероятно, нетривиален - быстрое сканирование источника Tie::File, и похоже, что предполагается, что ему всегда будут даны байты. Единственная часть, на которую вы можете повлиять, - это binmode на https://metacpan.org/source/TODDR/Tie-File-0.98/lib/Tie/File.pm#L111 который вы делаете.

Tie::File делает много seek звонки, у perldoc есть что сказать при поиске ( http://perldoc.perl.org/functions/seek.html):

Обратите внимание на байты: даже если файловый дескриптор был настроен для работы с символами (например, с помощью открытого слоя:encoding(utf8)), tell() будет возвращать смещения байтов, а не смещения символов (потому что реализация, которая будет выполнять поиск () и сказать () довольно медленно).

Таким образом, похоже, что Tie::File использует длину символов для определения смещения байтов для записей. Поэтому он может оказаться в середине последовательности символов UTF-8. Это кажется вероятной причиной ваших ошибок.

В общем, я держусь подальше от binmode полагаясь на внешний модуль для чтения / записи в дескриптор файла - в этом случае у меня будет простой вызов sub Encode::encode('UTF-8', ...) на данных, прежде чем нажать на @tied.

Исключение составляют случаи, когда в документации модуля четко указано поведение декодированных данных или если источник достаточно прост, чтобы я мог проверить поведение.

Другие вопросы по тегам