为什么BOM在读取UTF-8文件时会留下来?

时间:2016-01-19 02:52:41

标签: perl encoding utf-8

我试图在Perl中读取一些UTF-8编码的CSV文件(至少我相信它们是什么),并将它们全部写入一个更大的文件中。这是我的剧本:

#!/usr/bin/perl
use strict;
use warnings;

open my $out, '>:encoding(UTF-8)', "output.csv" or die "Cannot open output.csv: $!";

my @files = <*.csv>;
foreach(@files) {
    next if $_ =~ m/^output.csv$/;

    print "Parsing $_\n";

    open my $in, '<:encoding(UTF-8)', $_ or die "Cannot open $_: $!";
    while(<$in>) {
        chomp;
        next if m/^\s*$/;
        print $out "$_\n";
    }
    close $in;
}
close $out;

完成所有操作后,每个文件的内容都以BOM开头,这意味着BOM显示为每个文件数据的前三个字节。不能使用>:encoding(UTF-8)指令打开文件已经摆脱了BOM吗?为什么它会继续显示在我的输出中?

1 个答案:

答案 0 :(得分:4)

UTF-8是基于字节的编码,因此字节顺序无关紧要,并且初始字节顺序标记(BOM)是不必要的,并且通常不鼓励使用UTF-8数据。但它的有效性和功能取决于主流的应用程序,因此Perl不能简单地从数据中删除它

Unicode BOM字符U+FEFF ZERO WIDTH NO-BREAK SPACE 字符共享编码,因此如果布局是唯一的问题,如果留下,则不应该导致问题,即使连接多个源以使其出现在数据流的中间

在大多数文件应用程序中,UTF-8数据源是透明处理的,因此仅包含7位ASCII数据的文件与相同数据的UTF-8编码相同。此类数据不得包含BOM,因为它会干扰透明度。例如,UTF-8编码的shell命令文件开头的 shebang #!行不能以字节顺序标记开头,因为shell根本无法识别它

您可以从解码的 Unicode数据的开头剥离BOM字符,无论来源如何

s/\A\N{BOM}//

当然,通过使用移除了\A锚点的全局替换,或者更整齐地使用

,可以在整个字符串中删除字符
tr/\N{BOM}//d


更新

字符流被读取为字节的序列,在16位或32位编码中,您需要知道它是最不重要的(小端)还是最重要的首先出现的(big-endian)字节,以便您知道如何将这些字节组装成多字节字符

BOM字符始终 U+FEFF。它的全部意义在于它是不变的。因此,如果我从文件中读取前两个字节,并按顺序为FFFE,那么我知道整个文件是UTF-16(或UTF-32)编码的最少 - 显着字节后跟最重要的字节或小端字节,然后我可以正确解释文件的其余部分

但是在基于字节的编码中,字节顺序毫无意义。每个字符由一个或多个字节的序列表示,并且数据是相同的,无论其原始系统的字节顺序如何。 BOM字符U+FEFF以UTF-8编码为该顺序的三个十六进制字节EFBBBF,且不变

File::BOM模块

在我看来,File::BOM使一个简单的概念不必要地复杂化

如果你必须处理来自具有不同字节序的平台的不同编码的许多不同的Unicode文件,我可以看到它是有用的,但在这种情况下,每行文本末尾的记录分隔符的字符序列的变化是可能更像是一个问题

只要您在打开文件之前知道文件的编码,就应该打开文件并按照该标准阅读。如果数据中存在BOM字符是个问题,那么只需使用s///tr///d将其删除即可。但请记住,应在所有符合Unicode的系统上透明地忽略BOM字符