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

问题描述 投票:0回答:2

我需要处理许多看起来像这样的文件(带有

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
    ,它们都会导致文件没有空尾随列)。

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

csv perl text newline
2个回答
3
投票

更新:这个答案是为问题的前两个版本写的。我只是取消删除它,因为 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);
}

更新:

由于您编辑了问题并添加了一行代码来更改everything并使您自己声称的输出“错误”,我发现以下内容:

如果您只有尾随的空字段,

split
将默认删除这些空字段。这可以修复,如 documentation for split 中所指定:

如果 LIMIT 为负,则将其视为任意值 大的;产生尽可能多的字段。

如果 LIMIT 被省略(或等效地为零),那么它通常是 被视为负面的,但除了 尾随空字段被剥离(空前导字段总是 保留);如果所有字段都为空,则所有字段都被认为是 尾随(因此在这种情况下被剥离)。

换句话说,你可以改变

split /\$/;

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

修复丢失的尾随空白字段。

唯一的问题是你还没有报告有这个问题(还)。所以,我想我们需要等你更新你的问题。


0
投票

注意:此答案的目的是为未来的读者澄清问题和一些解决方案(由于我对问题的不合时宜的编辑)。信用应该转到@TLP answer。此外,正如他所建议的,正确的解决方案可能是使用

Text::CSV
模块,但破译这个问题是有教育意义的。


代码中有两个问题:

  1. 当使用
    \$\r\n
    \$\R
    匹配最后一个
    $
    符号时,它按预期工作。但是,由于最后一列是空的(即没有
    CRLF
    包含在最后一列中),
    split
    默认删除它们
    .
  2. 当使用
    \$$
    匹配最后一个
    $
    符号时,一个
    CRLF
    被包含在最后一列中(我不明白为什么);

在 Linux 上,一个可能的解决方法是:

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

在 Windows 上,需要进行一些调整。

我机器上的一些时间使用

file1.txt
file2.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
© www.soinside.com 2019 - 2024. All rights reserved.