Недетерминированность в кодировании при использовании 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
вместо.