Как читать UTF-8 с алмазным оператором (<>)?

Я хочу читать вход UTF-8 в Perl, независимо от того, поступает он от стандартного ввода или из файла, используя оператор алмаза: while(<>){...}.

Итак, мой script должен быть вызван этими двумя способами, как обычно, с тем же выходом:

./script.pl utf8.txt
cat utf8.txt | ./script.pl

Но выходы отличаются! Только второй вызов (с использованием cat), по-видимому, работает так, как было разработано, правильно прочитав UTF-8. Вот script:

#!/usr/bin/perl -w

binmode STDIN, ':utf8';
binmode STDOUT, ':utf8';

while(<>){
    my @chars = split //, $_;
    print "$_\n" foreach(@chars);
}

Как я могу заставить его правильно читать UTF-8 в обоих случаях? Я хотел бы продолжать использовать алмазный оператор <> для чтения, если это возможно.

EDIT:

Я понял, что должен, вероятно, описать разные результаты. Мой входной файл содержит следующую последовательность: a\xCA\xA7b. Метод с cat корректно выводит:

a
\xCA\xA7
b

Но другой метод дает мне следующее:

a
\xC3\x8A
\xC2\xA7
b

Ответ 1

Попробуйте вместо этого использовать прагму:

use strict;
use warnings;
use open qw(:std :utf8);

while(<>){
    my @chars = split //, $_;
    print "$_" foreach(@chars);
}

Вам нужно сделать это, потому что оператор < > волшебный. Как вы знаете, это будет читать из STDIN или из файлов в @ARGV. Чтение из STDIN не вызывает проблем, поскольку STDIN уже открыт, поэтому binmode хорошо работает на нем. Проблема заключается в чтении из файлов в @ARGV, когда ваш script запускает и вызывает binmode, файлы не открыты. Это приводит к тому, что STDIN устанавливается в UTF-8, но этот IO-канал не используется, когда @ARGV имеет файлы. В этом случае оператор < > открывает новый дескриптор файла для каждого файла в @ARGV. Каждый дескриптор файла получает reset и теряет его атрибут UTF-8. Используя открытую прагму, вы заставляете каждый новый STDIN находиться в UTF-8.

Ответ 2

Ваш script работает, если вы это сделаете:

#!/usr/bin/perl -w

binmode STDOUT, ':utf8';

while(<>){
    binmode ARGV, ':utf8';

    my @chars = split //, $_;
    print "$_\n" foreach(@chars);
}

Магический дескриптор файла, который < > читает, называется *ARGV, и это открывается при вызове readline.

Но на самом деле я поклонник явного использования Encode::decode и Encode::encode, если это необходимо.

Ответ 3

Вы можете включить UTF8 по умолчанию с помощью флага -C:

perl -CSD -ne 'print join("\n",split //);' utf8.txt

Переключатель -CSD безоговорочно включает UTF8; если вы просто используете -C, он включит UTF8, только если соответствующие переменные среды (LC_ALL, LC_TYPE и LANG) указывают на это. Подробнее см. perlrun.

Это не рекомендуется, если вы не вызываете perl напрямую (в частности, он может не работать надежно, если вы передадите параметры perl из строки shebang). В этом случае см. Другие ответы.

Ответ 4

Если вы поместите вызов binmode внутри цикла while, тогда он переключит дескриптор в режим utf8 ПОСЛЕ первой строки, в которую будет считываться. Вероятно, это не то, что вы хотите сделать.

Что-то вроде следующего может работать лучше:

#!/usr/bin/env perl -w
binmode STDOUT, ':utf8';
eof() ? exit : binmode ARGV, ':utf8';
while( <> ) {
    my @chars = split //, $_;
    print "$_\n" foreach(@chars);
} continue {
    binmode ARGV, ':utf8' if eof && !eof();
}

Вызов eof() с помощью parens волшебный, поскольку он проверяет конец файла на псевдо файловой манипуляции, используемой < > . Он, при необходимости, откроет следующий дескриптор, который должен быть прочитан, что, как правило, приводит к действию * ARGV, но не читая ничего из него. Это позволяет нам binmode первый файл, который читается, прежде чем что-либо будет прочитано из него.

Позже используется eof (без парнеров); это проверяет последний дескриптор, который был прочитан для конца файла. Это будет верно после обработки последней строки каждого файла из командной строки (или когда stdin достигает ее конца).

Очевидно, что если мы только что обработали последнюю строку одного файла, вызов eof() (с помощью parens) открывает следующий файл (если он есть), делает * ARGV действительным (если это возможно), и тесты для конец файла в следующем файле. Если этот следующий файл присутствует и не находится в конце файла, тогда мы можем безопасно использовать binmode в ARGV.