csv 在Perl中处理`CRLF`行结尾的替换和`split`

dtcbnfnu  于 2023-05-04  发布在  Perl
关注(0)|答案(2)|浏览(181)

我需要处理许多文件(以CRLF行结尾),它们看起来像这样:

$ cat -v file1.txt
1$XXX$ZZZ$$$$$$$$^M
2$AAA$BBB$$$$$$$$^M

$ cat -v file2.txt
1$4668$$$^M
2$46$$$^M

我需要:

  • 删除最后一个$符号,
  • 将所有$更改为,
  • 用双引号将每个字段括起来,
  • 重命名文件。

所需输出(无论行尾是CRLF还是LF):

$ cat newname1.csv
"1","XXX","ZZZ","","","","","","",""
"2","AAA","BBB","","","","","","",""

$ cat newname2.csv
"1","4668","",""
"2","46","",""

以下是我的尝试:

#!/usr/bin/perl

use strict;
use warnings;

my %inputs = qw(
  file1 file1.txt
  file2 file2.txt
);

my %outputs = qw(
  file1 newname1.csv
  file2 newname2.csv
);

for my $key (keys %inputs) {
  
  open my $in, '<', $inputs{$key} or die $!;
  open my $out, '>', $outputs{$key} or die $!;
  
  while(<$in>) {
    local $, = ',';
    local $\ = "\n";
    s/\$$//;
    my @row = split /\$/;
    print $out map qq("$_"), @row;
  }
  
  close $in or die $!;
  close $out or die $!;
  
}

在Linux上,它给出了最后一列包含CRLF的文件和LF行结尾:

$ cat -v newname1.csv
"1","XXX","ZZZ","","","","","","","","^M
"
"2","AAA","BBB","","","","","","","","^M
"

$ cat -v newname2.csv
"1","4668","","","^M
"
"2","46","","","^M
"

我猜这个问题是由于CRLF行结束。因此,我尝试:

  • '<'更改为'<:crlf'以打开我的文件,结果相同;
  • 使用其他正则表达式来匹配最后一个$符号(例如\$\r\n\$\R,这两个文件都没有空的尾部列)。

我如何修复我的代码以获得我想要的输出?

ki1q1bka

ki1q1bka1#

更新:这个答案是为前两个版本的问题写的。我只是因为OP要求我取消删除它。它可能不适合当前版本的问题。有些事情可能是完全错误的。
这与行尾是否为CRLF无关。这只是一个split的问题。
如果我在代码中添加一个Dumper打印,其中您已拆分为变量@row

my @row = split /\$/;
use Data::Dumper;
print Dumper \@row;

我得到(对于第一个字段):

$VAR1 = [
          '1',
          '4668',
          '',
          '',
          '
'
        ];

在分割的最后一个字段中可以看到尾随的换行符。
然后,当您将这些拆分结果视为数据中的真正列值时,将为换行符添加1个字段。
我看不出你在哪里删除最后一个$。也许这是你误解了什么?

建议方案:

如果这是csv数据,您应该使用csv模块来处理它。Text::CSV模块很好地做到了这一点。下面是一个处理输入的示例代码:

use strict;
use warnings;
use Text::CSV qw(csv);

my %inputs = qw(
  file1 file1.txt
  file2 file2.txt
);

my %outputs = qw(
  file1 newname1.csv
  file2 newname2.csv
);

for my $key (keys %inputs) {
    my $aoa = csv (in => $inputs{$key}, sep_char => '$');
    csv (in => $aoa, out => $outputs{$key}, sep_char => ',', always_quote => 1);
}

更新:
自从你编辑了你的问题并添加了一行代码,改变了 * 一切 * 并使你自己声称的输出“错误”,我发现了以下内容:
如果只有尾随的空字段,默认情况下split将删除这些空字段。这是可以修复的,如documentation for split
如果LIMIT是负的,则它被视为任意大的;产生尽可能多的场。
如果LIMIT被省略(或者,等价地,零),则它通常被视为好像它是负的,但是例外的是尾部空字段被剥离(空的前导字段总是被保留);如果所有字段都是空的,则所有字段都被认为是拖尾的(并且因此在这种情况下被剥离)。
换句话说,你可以改变

split /\$/;

split /\$/, $_, -1;

以修复丢失的尾随空字段。
唯一的问题是你还没有报告有这个问题(还没有)。所以,我想我们需要等待你更新你的问题。

brjng4g3

brjng4g32#

  • 注:这个答案的目的是澄清问题和一些解决方案,为未来的读者(由于我的问题不合时宜的编辑)。信用应该去@TLP答案。此外,正如他所建议的,正确的解决方案可能是使用Text::CSV模块,但破译这个问题是有教育意义的。

代码中有两个问题:
1.当使用\$\r\n\$\R来匹配最后一个$符号时,它按预期工作。然而,由于最后一列是空的(即,没有CRLF包含在最后一列中),split drop them by default
1.当使用\$$来匹配最后一个$符号时,CRLF被包含在最后一列中(我不明白为什么);
在Linux上,一个可能的修复方法是:

  • 通过将split /\$/;更改为split /\$/, $_, -1;来指定splitLIMIT参数(以修复问题1);
    • 和 *(解决问题2):
  • 将正则表达式更改为匹配最后一个$符号,从\$$更改为\$\r\n(或\$\R);(以下简称myscript_fix1.pl
  • 或者在while循环的开始添加local $/ = "\r\n"; chomp;,并保持正则表达式\$$与最后一个$匹配;(以下简称myscript_fix2.pl
  • 或者保留最后的$符号(即,删除s/\$$//;),并在split之后添加pop @row;。(以下简称myscript_fix3.pl

在Windows上,需要进行一些调整。
在我的机器上使用file1.txtfile2.txt的一些计时重复了10000次:

$ time myscript_fix1.pl
real    0m0,199s
user    0m0,179s
sys     0m0,018s

$ time myscript_fix2.pl
real    0m0,234s
user    0m0,215s
sys     0m0,017s

$ time myscript_fix3.pl
real    0m0,176s
user    0m0,159s
sys     0m0,016s

相关问题