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

msc*_*cha 2 io perl character-encoding

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

\n\n

有没有一种方法可以方便地读取这些文件,像 Vim 等编辑器一样自动检测编码?

\n\n

我希望有这样简单的事情:

\n\n
open(my $f, \'<:encoding(autodetect)\', \'foo.txt\') or die \'Oops: $!\';\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

例子:

\n\n
#!/usr/bin/env perl\n\nuse 5.020;\nuse warnings;\n\nuse Encode;\nuse Encode::Guess qw(utf-8 cp1252);\n\nbinmode STDOUT => \'utf8\';\n\nmy $utf8 = "H\\x{C3}\\x{A9}llo, W\\x{C3}\\x{B8}rld!"; # "H\xc3\xa9llo, W\xc3\xb8rld!" in UTF-8\nmy $latin = "H\\x{E9}llo, W\\x{F8}rld!";            # "H\xc3\xa9llo, W\xc3\xb8rld!" in CP-1252\n\n# Version 1\nmy $enc1 = Encode::Guess->guess($latin);\nif (ref($enc1)) {\n    say $enc1->name, \': \', $enc1->decode($latin);\n}\nelse {\n    say "Oops: $enc1";\n}\nmy $enc2 = Encode::Guess->guess($utf8);\nif (ref($enc2)) {\n    say $enc2->name, \': \', $enc2->decode($utf8);\n}\nelse {\n    say "Oops: $enc2";\n}\n\n# Version 2\nsay decode("Guess", $latin);\nsay decode("Guess", $utf8);\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
cp1252: H\xc3\xa9llo, W\xc3\xb8rld!\nOops: utf-8-strict or utf8 or cp1252\nH\xc3\xa9llo, W\xc3\xb8rld!\ncp1252 or utf-8-strict or utf8 at ./guesstest line 32.\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n

msc*_*cha 5

这是我当前的解决方法。至少对于 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;
}
Run Code Online (Sandbox Code Playgroud)