'chomp'在Perl中逐行处理文件的奇怪行为

时间:2011-10-20 19:17:08

标签: perl

我使用以下Perl脚本进行一些简单的处理:

use strict;
my $file = "data-text";
open(FILE, "<$file") or die "Can't open $file: $!\n";
my @lines = <FILE>;
close FILE;
my @arrayA = (); my @arrayB=();
my $i = 0;
while($i < @lines) {
    print $lines[$i], "\t", $lines[$i+1], "\n";
    chomp($lines[$i]); chomp($lines[$i+1]); #The problem is here...
    push @arrayA, \$lines[$i];
    push @arrayB, \$lines[$i+1];
    print $lines[$i], "\t", $lines[$i+1], "\n";
    $i+=2;
}

正如我在脚本中指出的那样,问题出在chomp($lines[$i]); chomp($lines[$i+1]);行。看来如果我使用这条线,线条就会搞砸了。

有什么问题?这是为什么?

2 个答案:

答案 0 :(得分:7)

chomp从字符串末尾删除单个\n字符。

如果字符串以\r\n结尾(Windows样式的行结尾),chomp将保留\r。这可能会导致类似于您所看到的症状。

修改

一些背景知识。类Unix系统(包括Linux)使用单个换行符('\n')来标记文本文件中每行的结尾。 Windows(及其前身MS-DOS)使用两个字符,回车符和换行符(\r\n)。

Perl的许多功能都是为了处理文本而设计的。这意味着,相当合理的是,Perl默认假定它正在读取的任何文本文件都使用底层操作系统的本机行尾表示。

从C继承的Perl特性是,当读取一行文本时,本机行尾序列(无论它是什么)被转换为单个'\n'字符。 (反向转换在输出上完成)。这使得大多数程序不必担心文本的表示方式;它被转换为输入和输出的规范内部形式。 (由于历史原因,这种形式恰好符合Unix格式。)

但是,如果您需要处理非本机文本文件,这没有多大帮助。如果您在类似Unix的环境中运行,但是在阅读Windows格式的文本文件时,\r字符将看起来像是该行的一部分。特别是,chomp不会对它们做任何特殊处理。当您打印\r字符时,它通常会使光标移动到当前行的开头而不会前进到下一行。一团糟。 (Cygwin是这种混乱的丰富来源;它是一个类似Unix的环境,默认情况下使用Unix风格的文本文件,但它在Windows下运行,可以完全看到Windows文件系统。你使用的是Cygwin吗?)

参见@ BillRupert的评论;他在Windows下使用Perl的Windows本机实现运行,所以他没有看到你遇到的问题。

如果您想处理非原生文本文件,则需要做一些额外的工作。例如,在阅读一行文字时,而不仅仅是

chomp $line;

你可以写:

chomp $line;
$line =~ s/\r$//;

在撰写文字时,你可以这样做:

$line =~ s/$/\r/;

但首先,您需要决定是否要使用Windows样式或Unix样式的行结尾编写输出。这很棘手。

(可能有一个Perl模块使这更容易;任何知道一个的人,请在评论中提及。)

顺便提一下,您看到的输出不是您的程序产生的输出。如果您通过显示可打印格式的不可打印字符的内容过滤输出,则会在输出中看到\r^M。如果您的系统具有... | cat -A命令,请使用... | cat -vcat

如果可能,您可以考虑在尝试阅读之前翻译输入。

答案 1 :(得分:0)

由于我没有你的数据文件,我无法确定,但首先,让我们切换到现代打开和处理,让我们使用警告,也许只是扼杀整个数组:

use strict;
use warnings;

## If line endings are the problem, try for example:
#local $/ = "\r\n";

my $file="data-text";

my @lines;
{
    open(my $fh, "<", $file) or die "Can't open $file: $!\n";
    @lines = <$fh>;
}

chomp @lines;

my @arrayA;
my @arrayB;
my $i = 0;
while ($i < @lines) {
    print $lines[$i],"\t",$lines[$i+1],"\n";
    push @arrayA, \$lines[$i];
    push @arrayB, \$lines[$i+1];

    ## The following line is now no different from the above, commented out
    #print $lines[$i],"\t",$lines[$i+1],"\n";
    $i+=2;
}

看看这是否符合您的预期。如果你给我们(一部分)文件,我们可能会注意到更多。

此外,如果您正在尝试将所有其他行拆分为两个阵列,则可以执行以下操作:

while (@lines) {
    my $line1 = shift @lines;
    my $line2 = shift(@lines) || '';
    print $line1,"\t",$line2,"\n";
    push @arrayA, $line1;
    push @arrayB, $line2;
}

内存使用量较少。