在Perl中如何正确检测文件的行尾?

5

问题:我拥有在Windows和\* nix上生成的数据(大多数为CSV格式),并且主要在\* nix上进行处理。 Windows使用CRLF作为行结尾,而Unix使用LF。 对于任何特定的文件,我都不知道它具有Windows还是\* nix行结尾。 目前为止,我一直在编写类似于以下内容来处理差异:

while (<$fh>){
    tr/\r\n//d;
    my @fields = split /,/, $_;
    # ...
}

在*nix上,\n部分相当于chomping,此外,如果它是由Windows生成的文件,则还会去掉\r(CR)。

但现在我想使用Text::CSV_XS,因为我开始得到更奇怪的带引号数据文件,可能包含嵌入式换行符等。为了使该模块读取此类文件,Text::CSV_XS::getline()要求您指定行尾字符。 (我不能像以上那样逐行阅读,tr/ \ n \ r // d,然后使用Text::CSV解析它,因为这样无法正确处理嵌入的换行符)。 我如何“正确”检测任意文件是否使用Windows或*nix样式的行结尾,以便告诉Text::CSV_XS ::eol()如何chomp()? 我在CPAN上找不到一个简单检测行结尾的模块。 我不想通过dos2unix先转换所有数据文件,因为这些文件很大(数百GB),并且每个文件花费超过10分钟来处理如此简单的问题似乎很愚蠢。 我考虑编写一个函数,该函数读取文件的前几百个字节并计算LF和CRLF的数量,但我拒绝相信这没有更好的解决方案。 有什么帮助吗?
注意:所有文件都具有完全的Windows行尾或*nix结束,即它们不是在单个文件中混合的。
5个回答

10

您可以使用:crlf PerlIO层打开文件,然后告诉Text::CSV_XS\n用作行结束符。这将自动将任何CR/LF对映射为单个换行符,但这可能是您想要的。

use Text::CSV_XS;
my $csv = Text::CSV_XS->new( { binary => 1, eol => "\n" } );

open( $fh, '<:crlf', 'data.csv' ) or die $!;

while ( my $row = $csv->getline( $fh ) ) {
     # do something with $row
}

谢谢,我以前不知道 PerlIO。这正是我所需要的。 - user1481

6
自Perl 5.10以来,您可以使用此功能检查一般的行尾符。
s/\R//g;

它应该在所有情况下都能工作,包括*nix和Windows。


3

读取每个文件的第一行,查看其倒数第二个字符。如果是\r,则该文件来自Windows,否则为*nix。然后使用seek到开头并开始处理。

如果文件可能具有混合的行尾(例如嵌入式换行符的不同类型),则只能猜测。


1
你可以使用PERLIO变量。这样做的好处是不需要根据平台修改脚本源代码。
如果你正在处理DOS文本文件,请将环境变量PERLIO设置为:unix:crlf
$ PERLIO=:unix:crlf my-script.pl dos-text-file.txt

如果您主要处理DOS文本文件(例如在Cygwin上),您可以将以下内容放入您的.bashrc文件中:

export PERLIO=:unix:crlf

我认为在Cygwin上,PERLIO的默认值应该是这个,但显然不是。


1

理论上,无法可靠地确定行尾:这个文件是一个带有嵌入式\n的DOS行结尾的单行文件,还是一堆带有一些杂乱的\r字符的行结尾?

foo\n
ba\r\n

对比

foo\nba\r\n

如果统计分析不是一个选项,因为它太不准确和昂贵(扫描这样巨大的文件需要时间),那么你必须实际上知道编码是什么。

如果您可以控制生成应用程序,则最好指定确切的文件格式或使用某种元数据来跟踪生成数据的平台。

在Perl中,字符\n表示与语言环境有关:\n/\012在*nix机器上,\r/\015在旧版Mac上,序列\r\n/\015\012在DOS后代即Windows上。因此,为了进行可靠的处理,您应该使用八进制值。


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