不带BOM的文件出现“未识别的字符 \xEF”错误

3
我目前正在使用Perl和模块Text::CSV从CSV文件中提取文本。每个CSV文件都有引号将每个字段分隔开。文本被保存到独立的文本文件中,以制表符分隔成列。我可以调用并打印文本文件中的每一列,但是当我尝试在循环中使用这些值时,出现错误Unrecognized character \xEF
以下是我的代码示例:
#!/usr/bin/perl

use strict;
use warnings;
use Text::CSV;

#### Match ligand data with GPCR interaction data ####
my $csv = Text::CSV->new();
my $file = $ARGV[0];
open (FILE, "<$file");
open (OUT, ">new_$file");
while (my $line2 = <FILE>)
{
    binmode(STDOUT, ":utf8");
    if ($line2 =~ /^(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)$/)
    {
        #### Data from filtered1.txt ####
        my $up_fil = $1;
        my $ligid_fil = $2;
        my $units_fil = $3;
        my $low_fil = $4;
        my $median_fil = $5;
        my $upper_fil = $6;
        my $ref = $7;

        #### Convert negative log affinity values to normal ####
        my $activity = $units_fil;
        $activity =~ s/p//;
        my $value;

        if ($median_fil ne "")
        { 
            $value = $median_fil;
            $value = (10**-$median_fil)/(10**-9);
        }
        elsif ($low_fil ne "" and $upper_fil ne "") 
        {
            my $lower = $low_fil;
            $lower = (10**-$low_fil)/(10**-9);
            my $upper = $upper_fil;
            $upper = (10**-$upper_fil)/(10**-9);
            $value = "$upper - $lower";
        }
        else
        {
            $value = "n/a";
        }


        #### Match entries from filtered1.txt with ligands.csv ####
        open (LIG, "<ligands.csv");
        while (my $line3 = <LIG>)
        {
            $csv->parse($line3);
            my @ligand_fields = $csv->fields();
            if (!$ligand_fields[14]) { next; }
            if ($ligand_fields[0] eq $ligid_fil)
            {
                #print OUT "$ligand_fields[14]\t$ligand_fields[13]\t$up_fil\t$ligid_fil\t$activity\t$value\t$ref\n";
                print "$ligand_fields[14]\t$ligand_fields[13]\t$up_fil\t$ligid_fil\t$activity\t$value\t$ref\n";
                next;
            }
        }
            close LIG;
        }
    }
    close FILE;
    close OUT;

我也尝试过使用类似以下的正则表达式,但未成功。
# remove BOM
${$self->{CODE}} =~ s/^(?:
    \xef\xbb\xbf     |
    \xfe\xff         |
    \xff\xfe         |
    \x00\x00\xfe\xff |
    \xff\xfe\x00\x00
)//x;

原始的CSV文件似乎没有BOM,因此我怀疑在解析和返回值时Text::CSV可能会创建它。希望这已经足够清楚地解释了问题,如果需要,我可以提供更多细节。非常感谢您提供的任何建议。

2
你读取编码字节,然后告诉STDOUT它们是Unicode代码点,需要转换为UTF-8。解码输入文件以获取Unicode代码点! - ikegami
谢谢编辑,Miller。另外,我找出了问题所在。原来是在if和($median_fil ne "")之间有一个奇怪的类似空格的字符,导致了错误。我删除了这个空格,添加了一个新的,并且问题就解决了。不管怎样,还是感谢你的帮助! - Wally
1个回答

2
< p > Text::CSV 的文档说明,你几乎肯定应该使用二进制模式。

 my $csv = Text::CSV->new ( { binary => 1 } )  # should set binary attribute.
                 or die "Cannot use CSV: ".Text::CSV->error_diag ();

来自https://metacpan.org/pod/Text::CSV#SYNOPSIS
你可能还想看一下Text::CSV::Encoded
我还看到你在STDOUT上设置了一个:utf8的binmode。但这样有几个问题:
  1. 你每次循环都不必要地设置它
  2. :utf8 binmode没有很好的错误检查,你应该使用:encoding(UTF-8)
字节0xEF可以出现在UTF-8字节序列中,但只有在非常特定的情况下才会出现,它太高(> 0x7F)以至于无法表示单个字符。然而,在Perl中,\xEF\x{ef}并不是指字节0xEF,而是Unicode代码点U+00EF,其在UTF-8中表示为0xC3 0xAF。您可以在Unicode / UTF-8字符表中看到这一点,例如http://www.utf8-chartable.de/
$ perl -E 'binmode STDOUT, ":encoding(UTF-8)"; say "\xEF";'
ï

因此,我认为您的正则表达式去除BOM失败的原因就在这里。我建议使用 三个参数的open函数 之一,同时使用 '<:encoding(UTF-8)' 或者 '>:encoding(UTF-8)' 打开所有输入和输出文件,并以二进制模式使用 Text::CSV,以获得最佳结果。

1
一个字节\xEF可以出现在有效的UTF-8中,但只有在后面跟着范围为\x80..\xBF的两个连续字节时才能出现。(后面的字节可能还有其他限制,但这是最基本的限制之一。) - Jonathan Leffler
1
啊,我明白我的错误了。在我的 Perl 一行代码中(perl -E 'binmode STDOUT, ":encoding(UTF-8)"; say "\xEF";'),"\xEF" 是指 Unicode 码点 U+00EF(即 "ï"),而不是字节 EF,正如你所说的那样,它不是一个有效的 UTF-8 字符。我会更新我的答案,谢谢。 - Kaoru

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接