IO::Handle для получения и удаления символов Юникода

Я думаю, что столкнулся с проблемой с Unicode и IO::Handle. Очень вероятно, что я делаю что-то не так. Я хочу получать и удалять отдельные символы Юникода (не байты) из IO::Handle. Но я получаю удивительную ошибку.

#!/usr/local/bin/perl

use 5.016;
use utf8;
use strict;
use warnings;

binmode(STDIN,  ':encoding(utf-8)');
binmode(STDOUT, ':encoding(utf-8)');
binmode(STDERR, ':encoding(utf-8)');

my $string = qq[a Å];
my $fh = IO::File->new();

$fh->open(\$string, '<:encoding(UTF-8)');

say $fh->getc(); # a
say $fh->getc(); # SPACE
say $fh->getc(); # Å LATIN CAPITAL LETTER A WITH RING ABOVE (U+00C5)
$fh->ungetc(ord("Å"));
say $fh->getc(); # should be A RING again.

Сообщение об ошибке из строки ungetc(): «Неверный формат символа UTF-8 (неожиданный конец строки), скажем, в строке 21 unicode.pl. «\x{00c5}» не соответствует utf8 в строке 21 unicode.pl». Но это правильный гекс для персонажа, и он должен соответствовать персонажу.

Я использовал шестнадцатеричный редактор, чтобы убедиться, что байты для A-RING верны для UTF-8.

Кажется, это проблема для любого двухбайтового символа.

Последнее слово выводит '\xC5' (буквально четыре символа: обратная косая черта, x, C, 5).

И я проверил это, читая из файлов вместо скалярных переменных. Результат тот же.

Это Perl 5, версия 16, Subversion 2 (v5.16.2), созданный для darwin-2level.

И скрипт сохраняется в UTF-8. Это было первое, что я проверил.


person Michael    schedule 06.01.2013    source источник
comment
Совет: binmode(STDIN, ':encoding(utf-8)'); binmode(STDOUT, ':encoding(utf-8)'); binmode(STDERR, ':encoding(utf-8)'); можно записать как use open ':std', ':encoding(utf-8)';. (Бонус, это происходит во время компиляции.)   -  person ikegami    schedule 06.01.2013
comment
Совет: my $fh = IO::File->new(); $fh->open(\$string, '<:encoding(UTF-8)'); можно записать как open(my $fh, '<:encoding(UTF-8)', \$string). Да, вы все еще можете использовать методы.   -  person ikegami    schedule 06.01.2013


Ответы (2)


Я почти уверен, что это доказывает, что существует серьезная ошибка обработки Unicode, учитывая, что этот вывод:

perl5.16.0 ungettest
ungettest 98896 @ Sun Jan  6 16:01:08 2013: sending normal line to kid
ungettest 98896 @ Sun Jan  6 16:01:08 2013: await()ing kid
ungettest 98897 @ Sun Jan  6 16:01:08 2013: ungetting litte z
ungettest 98897 @ Sun Jan  6 16:01:08 2013: ungetting big sigma
ungettest 98897 @ Sun Jan  6 16:01:08 2013: kid looping on parental input
98897: Unexpected fatalized warning: utf8 "\xA3" does not map to Unicode at ungettest line 40, <STDIN> line 1.
 at ungettest line 10, <STDIN> line 1.
    main::__ANON__('utf8 "\xA3" does not map to Unicode at ungettest line 40, <ST...') called at ungettest line 40
98896: parent pclose failed: 65280,  at ungettest line 28.
Exit 255

производится этой программой:

#!/usr/bin/env perl

use v5.16;
use strict;
use warnings;
use open qw( :utf8    :std );

use Carp;

$SIG{__WARN__} = sub {  confess "$$: Unexpected fatalized warning: @_" };

sub ungetchar($) {
    my $char = shift();
    confess "$$: expected single character pushback, not <$char>" if length($char) != 1;
    STDIN->ungetc(ord $char);
}

sub debug {
    my $now = localtime(time());
    print STDERR "$0 $$ \@ $now: @_\n";
}

if (open(STDOUT, "|-")                          // confess "$$: cannot fork: $!") {
    $| = 1;
    debug("sending normal line to kid");
    say "From \N{greek:alpha} to \N{greek:omega}.";
    debug("await()ing kid");
    close(STDOUT)                               || confess "$$: parent pclose failed: $?, $!";
    debug("child finished, parent exiting normally");
    exit(0);
}

debug("ungetting litte z");
ungetchar("z")                                  || confess "$$: ASCII ungetchar failed: $!";

debug("ungetting big sigma");
ungetchar("\N{greek:Sigma}")                    || confess "$$: Unicode ungetchar failed: $!";

debug("kid looping on parental input");
while (<STDIN>) {
    chomp;
    debug("kid got $_");
}
close(STDIN)                                    || confess "$$: child pclose failed: $?, $!";
debug("parent closed pipe, child exiting normally");
exit 0;
person tchrist    schedule 06.01.2013
comment
Итак, похоже, что ord() возвращает правильную кодовую точку Unicode, но ungetc() интерпретирует ее не как кодовую точку, а необработанную последовательность байтов. Это звучит правильно? Если это так, то это существенная ошибка. - person Michael; 07.01.2013
comment
@Michael Да, в perlio.c, в PerlIO_ungetc(PerlIO *f, int ch) происходит то, что по сути происходит преобразование int в char через STDCHAR buf = ch, где STDCHAR - это просто тип C char. Это большая проблема. Пожалуйста, отправьте отчет об ошибке. Спасибо. - person tchrist; 07.01.2013
comment
Отчет об ошибке прочь (через perlbug). Сколько времени потребуется для подтверждения по электронной почте, что они его получили? - person Michael; 07.01.2013
comment
@Michael Вы должны были получить билет автоответчика практически сразу. Однако, похоже, он еще не прошел через всю систему. По крайней мере, я еще не получил копию p5p. - person tchrist; 07.01.2013
comment
На этот раз отчет об ошибке был сделан: rt.perl.org/rt3 /Public/Bug/Display.html?id=116322 - person Michael; 07.01.2013

ungetc добавляет байт к базовому входному потоку. Чтобы вернуть U+00C5, поток должен содержать C3 A5 (кодировка UTF-8 этого символа), а не C5 (ord("Å")). Вместо этого используйте unread IO::Unread.

person ikegami    schedule 06.01.2013
comment
В документации для ungetc() в IO::Handle сказано, что он помещает символ обратно в поток. А поскольку поток закодирован в UTF-8, он должен работать, не так ли? - person Michael; 06.01.2013
comment
Характер - расплывчатый термин. В данном случае используется определение C. ‹повторить ответ здесь› - person ikegami; 06.01.2013
comment
За исключением того, что он не использует определение C при чтении символов. - person Michael; 06.01.2013
comment
Было бы разумно, если бы ваш файл содержал 20 20 20 (три пробела в UTF-8). Имеет ли смысл, чтобы ваш файл содержал C5 20 20 20? Нет, добавление C5 не имеет смысла. - person ikegami; 06.01.2013
comment
Но я нигде не прошу его добавлять C5. - person Michael; 06.01.2013