【问题标题】:Perl - File Encoding and Word ComparisonPerl - 文件编码和单词比较
【发布时间】:2011-08-19 14:05:36
【问题描述】:

我有一个文件,每行包含一个短语/术语,我从 STDIN 读取到 perl。我有一个停用词列表(如“á”、“são”、“é”),我想将它们中的每一个与每个术语进行比较,如果它们相等则删除。问题是我不确定文件的编码格式。

我从file 命令得到这个:

words.txt: Non-ISO extended-ASCII English text

我的 linux 终端是 UTF-8 格式的,它显示了一些单词的正确内容,而另一些则没有。以下是其中一些的输出:

condi<E3>
conte<FA>dos
ajuda, mas não resolve
mo<E7>ambique
pedagógico são fenómenos

您可以看到,第 3 行和第 5 行正确识别带有重音符号和特殊字符的单词,而其他行则没有。其他行的正确输出应该是:condiã、conteúdos 和 moçambique。

如果我使用binmode(STDOUT, utf8),“不正确”行现在可以正确输出,而其他行则不能。例如第 3 行:

ajuda, mas não resolve

我该怎么办?

【问题讨论】:

    标签: perl unicode character-encoding


    【解决方案1】:

    它是这样工作的:

    C:\Dev\Perl :: chcp
    Aktive Codepage: 1252.
    
    C:\Dev\Perl :: type mixed-encoding.txt
    eins zwei drei Käse vier fünf Wurst
    eins zwei drei Käse vier fünf Wurst
    
    C:\Dev\Perl :: perl mixed-encoding.pl < mixed-encoding.txt
    eins zwei drei vier fünf
    eins zwei drei vier fünf
    

    mixed-encoding.pl 的去向如下:

    use strict;
    use warnings;
    use utf8; # source in UTF-8
    use Encode 'decode_utf8';
    use List::MoreUtils 'any';
    
    my @stopwords = qw( Käse Wurst );
    
    while ( <> ) { # read octets
        chomp;
        my @tokens;
        for ( split /\s+/ ) {
            # Try UTF-8 first. If that fails, assume legacy Latin-1.
            my $token = eval { decode_utf8 $_, Encode::FB_CROAK };
            $token = $_ if $@;
            push @tokens, $token unless any { $token eq $_ } @stopwords;
        }
        print "@tokens\n";
    }
    

    请注意,脚本不必以 UTF-8 编码。只是如果您的脚本中有时髦的字符数据,您必须确保编码匹配,所以use utf8 如果您的编码是 UTF-8,则不要。

    根据 tchrist 的合理建议进行更新:

    use strict;
    use warnings;
    # source in Latin1
    use Encode 'decode';
    use List::MoreUtils 'any';
    
    my @stopwords = qw( Käse Wurst );
    
    while ( <> ) { # read octets
            chomp;
            my @tokens;
            for ( split /\s+/ ) {
                    # Try UTF-8 first. If that fails, assume 8-bit encoding.
                    my $token = eval { decode utf8 => $_, Encode::FB_CROAK };
                    $token    = decode Windows1252 => $_, Encode::FB_CROAK if $@;
                    push @tokens, uc $token unless any { $token eq $_ } @stopwords;
            }
            print "@tokens\n";
    }
    

    【讨论】:

    • @michael 谢谢它现在输出正确;)我意识到文件的大部分是 ISO-8859-1 和 utf-8 的一些部分(这就是为什么其中一些输出正确)一个更多的东西。我必须使用 lc 函数,因为我的停用词都是小写的,而且当短语不是 utf-8 时我遇到了问题。在这种情况下,如果我有一个带重音的大写字母,它就不会是小写的。
    • @Barata:如果您希望 uc 等处理非 UTF8 字符串,您仍然需要对其进行解码。 Perl 5.12(及更高版本)unicode_strings 功能也可能有所帮助,因为它将假定 ISO 8859-1 用于字节字符串。比较:perl -e 'print uc("\xB5\xE9\xDF")' => µéß这是错误的,perl -M5.012 -e 'print uc("\xB5\xE9\xDF")' => ΜÉSS 这是正确的。 最后一个字符串确实是"\x{39C}\x{C9}SS""\N{GREEK CAPITAL LETTER MU}\N{LATIN CAPITAL LETTER E WITH ACUTE}SS"。原始字符串是"\N{MICRO SIGN}\N{LATIN SMALL LETTER E WITH ACUTE}\N{LATIN SMALL LETTER SHARP S}"
    • @tchrist 使用 Michael 代码,检查 if $@ 并将字符串解码为 iso-8859-1 就足够了吗?
    • @Barata:是的,可能。但是,如果您正在处理来自 Microsoft 系统的文件,您可能应该假设 CP1252,它是 ISO-8859-1 的超集。查看我的解决方案。
    • 对于混合编码的一般文件,您实际上不能这样做。那是因为如果您不知道编码,您甚至无法正确找到空格,更不用说一次读取一行了。例如,想象一下如果部件采用 UTF-16 会发生什么。
    【解决方案2】:

    我强烈建议您创建一个过滤器,该过滤器采用混合编码行的文件并将它们转换为纯 UTF-8。然后改为

    open(INPUT, "< badstuff.txt") || die "open failed: $!";
    

    您可以打开固定版本,或者来自修复程序的管道,例如:

    open(INPUT, "fixit < badstuff.txt |") || die "open failed: $!"
    

    在任何一种情况下,你都会

    binmode(INPUT, ":encoding(UTF-8)") || die "binmode failed";
    

    那么fixit 程序可以这样做:

    use strict;
    use warnings;
    use Encode qw(decode FB_CROAK);
    
    binmode(STDIN,  ":raw")  || die "can't binmode STDIN";
    binmode(STDOUT, ":utf8") || die "can't binmode STDOUT";
    
    while (my $line = <STDIN>) {
        $line = eval { decode("UTF-8", $line, FB_CROAK() };
        if ($@) { 
            $line = decode("CP1252", $line, FB_CROAK()); # no eval{}!
        }
        $line =~ s/\R\z/\n/;  # fix raw mode reads
        print STDOUT $line;    
    }
    
    close(STDIN)  || die "can't close STDIN: $!";
    close(STDOUT) || die "can't close STDOUT: $!";
    exit 0;
    

    看看它是如何工作的?当然,您可以将其更改为默认为其他编码,或者有多个后备。可能最好在@ARGV 中列出它们。

    【讨论】:

    • 当从 UTF-8 解码失败时,从特定编码解码非常好。因此,您最终不会混合使用 Unicode 和遗留字符串,而是将所有内容均质化为 Unicode。
    猜你喜欢
    • 2019-11-07
    • 1970-01-01
    • 1970-01-01
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 2022-11-03
    • 2019-12-20
    • 2014-06-14
    相关资源
    最近更新 更多