Недетерминированность в кодировании при использовании open() со скалярными и I/O слоями в Perl

Уже несколько часов я борюсь с ошибкой в ​​моей программе Perl. Я не уверен, что я делаю что-то не так или интерпретатор делает, но код не является детерминированным, хотя он должен быть детерминированным, IMO. Также он демонстрирует то же поведение на древнем Debian Lenny (Perl 5.10.0) и сервере, только что обновленном до Debian Wheezy (Perl 5.14.2). Это сводилось к этому куску кода Perl:

#!/usr/bin/perl
use warnings;
use strict;
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";
my $c = "";
open C, ">:utf8", \$c;
print C "š";
close C;
die "Does not happen\n" if utf8::is_utf8($c);
print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";

Он инициализирует интерпретатор Perl 5 в строгом режиме с включенными предупреждениями, с символьными строками (в отличие от байтовых строк) и именованными стандартными потоками, закодированными в UTF8 (внутреннее понятие UTF-8, но довольно близкое; переход на полный UTF-8 не имеет значения). Затем он открывает дескриптор файла в "файле в памяти" (скалярная переменная), печатает в нем один двухбайтовый символ UTF-8 и проверяет переменную при закрытии.

Скалярная переменная теперь всегда имеет бит UTF8. Однако иногда он содержит байтовую строку (преобразуется в символьную строку через utf8::decode()) и иногда символьная строка, которая просто должна перевернуть свой бит UTF8 (Encode::_utf8_on()).

Когда я выполняю свой код несколько раз (1000 раз, через Bash), он печатает Undecoded а также Decoded с примерно одинаковыми частотами. Когда я изменяю строку, я пишу в "файл", например, добавляю новую строку в конце, Undecoded исчезает. когда utf8::decode успешно, и я пробую это для той же самой исходной строки в цикле, это продолжает успешно в том же экземпляре интерпретатора; однако, если это терпит неудачу, это продолжает терпеть неудачу.

Чем объясняется наблюдаемое поведение? Как я могу использовать дескриптор файла для скалярной переменной вместе со строками символов?

Детская площадка Bash:

for i in {1..1000}; do perl -we 'use strict; use utf8; binmode STDOUT, ":utf8"; binmode STDERR, ":utf8"; my $c = ""; open C, ">:utf8", \$c; print C "š"; close C; die "Does not happen\n" if utf8::is_utf8($c); print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";'; done | grep Undecoded | wc -l

Для справки и, чтобы быть абсолютно уверенным, я также сделал версию с педантичной обработкой ошибок - те же результаты.

#!/usr/bin/perl
use warnings;
use strict;
use utf8;
binmode STDOUT, ":utf8" or die "Cannot binmode STDOUT\n";
binmode STDERR, ":utf8" or die "Cannot binmode STDERR\n";
my $c = "";
open C, ">:utf8", \$c or die "Cannot open: $!\n";
print C "š" or die "Cannot print: $!\n";
close C or die "Cannot close: $!\n";
die "Does not happen\n" if utf8::is_utf8($c);
print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";

1 ответ

Решение

Изучение $c в деталях показывает, что это не имеет ничего общего с содержанием $c или его внутренности, и результат decode точно представляет то, что он сделал или не сделал.

$ for i in {1..2}; do
     perl -MDevel::Peek -we'
        use strict; use utf8;
        binmode STDOUT, ":utf8";
        binmode STDERR, ":utf8";
        my $c = "";
        open C, ">:utf8", \$c;
        print C "š";
        close C;
        die "Does not happen\n" if utf8::is_utf8($c);
        Dump($c);
        print utf8::decode($c) ? "Decoded\n" : "Undecoded\n";
        Dump($c)
     '
     echo
  done

SV = PV(0x17c8470) at 0x17de990
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0x17d7a40 "\305\241"
  CUR = 2
  LEN = 16
Decoded
SV = PV(0x17c8470) at 0x17de990
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK,UTF8)
  PV = 0x17d7a40 "\305\241" [UTF8 "\x{161}"]
  CUR = 2
  LEN = 16

SV = PV(0x2d0fee0) at 0x2d26400
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0x2d1f4b0 "\305\241"
  CUR = 2
  LEN = 16
Undecoded
SV = PV(0x2d0fee0) at 0x2d26400
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0x2d1f4b0 "\305\241"
  CUR = 2
  LEN = 16

Это была ошибка в utf8::decode, но это было исправлено в 5.16.3 или более ранней версии, вероятно, 5.16.0, поскольку она все еще присутствовала в 5.14.2.

Подходящий обходной путь, чтобы использовать Encode's decode_utf8 вместо.

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