Как мне прочитать UTF-8 с оператором алмаза (<>)? - PullRequest
41 голосов
/ 06 февраля 2009

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

Так что мой сценарий должен вызываться двумя способами, как обычно, с одинаковым выводом:

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

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

#!/usr/bin/perl -w

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

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

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

РЕДАКТИРОВАТЬ:

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

a
\xCA\xA7
b

Но другой метод дает мне это:

a
\xC3\x8A
\xC2\xA7
b

Ответы [ 4 ]

56 голосов
/ 06 февраля 2009

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

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

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

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

17 голосов
/ 06 февраля 2009

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

#!/usr/bin/perl -w

binmode STDOUT, ':utf8';

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

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

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

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

9 голосов
/ 06 февраля 2009

Вы можете включить 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 голосов
/ 03 февраля 2011

Если вы поместите вызов 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 () с паренами является магическим, так как он проверяет конец файла в дескрипторе псевдо-файла, используемом <>. При необходимости он откроет следующий дескриптор, который должен быть прочитан, что обычно делает * ARGV допустимым, но ничего не читая из него. Это позволяет нам преобразовывать первый файл, с которого производится чтение, до того, как что-либо из него будет прочитано.

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

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

...