读取文件时自动检测字符编码

时间:2018-07-06 11:01:58

标签: perl io character-encoding

有时我必须从外部源读取文本文件,这些文件可以使用各种字符编码。通常是UTF-8,Latin-1或Windows CP-1252。

是否可以方便地读取这些文件,像Vim这样的编辑器来自动检测编码?

我希望有一些简单的东西:

open(my $f, '<:encoding(autodetect)', 'foo.txt') or die 'Oops: $!';

请注意,Encode::Guess并不能解决问题:只有在可以明确检测到编码的情况下,它才起作用,否则会崩溃。大多数UTF-8数据名义上都是有效的latin-1数据,因此在UTF-8文件上失败。

示例:

#!/usr/bin/env perl

use 5.020;
use warnings;

use Encode;
use Encode::Guess qw(utf-8 cp1252);

binmode STDOUT => 'utf8';

my $utf8 = "H\x{C3}\x{A9}llo, W\x{C3}\x{B8}rld!"; # "Héllo, Wørld!" in UTF-8
my $latin = "H\x{E9}llo, W\x{F8}rld!";            # "Héllo, Wørld!" in CP-1252

# Version 1
my $enc1 = Encode::Guess->guess($latin);
if (ref($enc1)) {
    say $enc1->name, ': ', $enc1->decode($latin);
}
else {
    say "Oops: $enc1";
}
my $enc2 = Encode::Guess->guess($utf8);
if (ref($enc2)) {
    say $enc2->name, ': ', $enc2->decode($utf8);
}
else {
    say "Oops: $enc2";
}

# Version 2
say decode("Guess", $latin);
say decode("Guess", $utf8);

输出:

cp1252: Héllo, Wørld!
Oops: utf-8-strict or utf8 or cp1252
Héllo, Wørld!
cp1252 or utf-8-strict or utf8 at ./guesstest line 32.

Borodin答案中“更新”下的版本仅适用于UTF-8数据,但不适用于Latin-1数据。 如果您需要同时处理UTF-8和Latin-1文件,则无法使用Encode::Guess

这个问题与this one并非同一问题:我正在寻找一种打开文件时自动检测的方法。

2 个答案:

答案 0 :(得分:2)

这取决于您可能要处理的编码。 看看 Encode::Guess 模块

通常,很容易判断您是否 是否有ASCII文件,因为代码点是7位,所以超过127的值表示它不是ASCII 。还可以可靠地判断您的文件 不是 ,因为多字节字符的最高有效位具有特定的顺序。其他任何事情都不那么可靠,但是可能

我不知道您可能会使用哪种编码,但这是一般的想法。 Encode::Guess是核心Encode模块的一部分,因此不需要安装

use Encode::Guess;

my $enc = guess_encoding($data, qw/ ascii cp1252 iso-8859-1 utf-8 /);
say ref $enc? $enc->name : $enc, "\n";

或者您可以执行最佳猜测解码,而无需检查模块选择了什么

  use Encode::Guess qw/ ascii cp1252 iso-8859-1 utf-8 /;

  my $chars = decode("Guess", $data);

请记住,您提供的编码可能 更少 ,猜测的准确性就越高。您应该仔细阅读模块文档


更新

这是OP的一线示范,证明Encode::Guess “没有成功” 写为正确的程序

请注意,如文档所述,guess_encoding有时可能返回类似utf-8 or iso-8859-1的字符串,在这种情况下,程序员必须处理歧义。 *在OP的示例中情况并非如此:数据被标识为UTF-8编码,并且guess_encodingdecode('guess', ...)均返回正确的结果

您可以使用此代码对您选择的任何字节字符串测试Encode::Guess:只需修改$raw的内容

use strict;
use warnings 'all';
use feature 'say';
use open qw/ :std encoding(UTF-8) /;

use Encode;
use Encode::Guess;
use Data::Dump;

my $raw = qq/H\x{C3}\x{A9}llo, W\x{C3}\x{B8}rld!/;

my $enc = guess_encoding($raw);

if ( my $class = ref $enc ) {
    printf qq{Guessed encoding \$enc is an %s object "%s"\n}, $class, $enc->name
}
else {
    printf qq{Guessed encoding \$enc is a scalar "%s"\n}, $enc;
}

my $chars = decode('guess', $raw);

printf "Decoded characters: %s\n", $chars;
dd $chars;

输出

Guessed encoding $enc is an Encode::utf8 object "utf8"
Decoded characters: Héllo, Wørld!
"H\xE9llo, W\xF8rld!"

答案 1 :(得分:1)

这是我当前的解决方法。至少对UTF-8和Latin-1(或Windows-1252)文件有用。

use 5.024;
use experimental 'signatures';
use Encode qw(decode);

sub slurp($file)
{
    # Read the raw bytes
    local $/;
    open (my $fh, '<:raw', $file) or return undef();
    my $raw = <$fh>;
    close($fh);

    my $content;

    # Try to interpret the content as UTF-8
    eval { my $text = decode('utf-8', $raw, Encode::FB_CROAK); $content = $text };

    # If this failed, interpret as windows-1252 (a superset of iso-8859-1 and ascii)
    if (!$content) {
        eval { my $text = decode('windows-1252', $raw, Encode::FB_CROAK); $content = $text };
    }

    # If this failed, give up and use the raw bytes
    if (!$content) {
        $content = $raw;
    }

    return $content;
}