使用 Perl 从大型 CSV 文件中删除特定的 CSV 列和重复行

使用 Perl 从大型 CSV 文件中删除特定的 CSV 列和重复行

我有一个大的 CSV 文件 (300MB+),我想使用 Perl 仅删除第 2,3 和 6-8 列并删除重复的行:

注 1:所有列均以,(逗号)分隔,但有时我的单元格值包含一个,或多个,并以(参见最后一行,第 9 列和第 10 列),分隔;"因此我希望仍然能够处理 input.csv 文件,即使它位于,单元格内:

注 2:我添加了 input.csv 和 output.csv 文件的链接:

输入.csv

Col1,Col2,Col3,Col4,Col5,Col6,Col7,Col8,Col9,Col10
info 1,info 2,info 3,...,info 10
address 1,address 2,....,address 10
city 1,city 2,city 3,city 4,city 5,city 6,city 7,city 8,"city 9, extra","city 10, new"

输出.csv

Col1,Col4,Col5,Col9,Col10
info 1,info 4,info 5,info 9,info 10
address 1,address 4,address 5,address 9,address 10
city 1,city 4,city 5,"city 9, extra","city 10, new"

我找到了一个 Perl 命令,可以使用正则表达式删除最后一列,但不知道它是否足够好,也不知道如何调整它以适合我的情况(任何其他建议都非常受欢迎!):

perl -pe 's/.*\K,.*//'

是否可以使用 Perl 仅删除第 2,3 和 6-8 列并删除任何重复的行?

PS:更新了 input.csv 文件以包含重复的行

谢谢你!

答案1

最简单的方法是使用磨坊主aka mlr,这是一个处理 CSV、json 和其他一些输入或输出格式数据的出色工具。例如:

$ mlr --csv --implicit-csv-header --headerless-csv-output \
    cut -x -f 2,3,6,7,8 \
    then uniq -a input.csv  
Col1,Col4,Col5,Col9,Col10
info 1,info 4,5,9,info 10
address 1,4,5,9,address 10
city 1,4,5,9,city 10

同时使用--implicit-csv-header--headerless-csv-output选项可以有效地忽略标题行(即将其与其他数据行相同),并允许我指定要按数字而不是按名称剪切的字段。

我必须编辑您的示例 input.csv 文件以在缺少的字段中添加一些垃圾数据。 mlr否则会抱怨的。我还添加了一个重复的输入行来测试重复消除是否有效。

$ cat input.csv 
Col1,Col2,Col3,Col4,Col5,Col6,Col7,Col8,Col9,Col10
info 1,info 2,info 3,info 4,5,6,7,8,9,info 10
info 1,info 2,info 3,info 4,5,6,7,8,9,info 10
address 1,address 2,3,4,5,6,7,8,9,address 10
city 1, city 2,3,4,5,6,7,8,9,city 10

如果你想用 perl 来做:

  1. 如果您只需要处理简单的逗号分隔输入:
$ perl -F, -lane '
  next if $seen{$_}++;
  splice @F,5,3;
  splice @F,1,2;
  print join ",", @F' input.csv
Col1,Col4,Col5,Col9,Col10
info 1,info 4,5,9,info 10
address 1,4,5,9,address 10
city 1,4,5,9,city 10

这使用 perl 的-a选项自动将每个输入行分割成一个名为 的数组@F。该-F选项告诉它使用什么分隔符。

注 1:perl 数组从零开始,而不是从一开始...所以数组元素 5 是第 6 列。 splice @$row, 5, 3从元素 5 开始从数组中删除三个元素(即第 6、7、8 列)。perldoc -f splice详情请参阅。

注2:我在这里以相反的顺序删除列(即编号较高的列编号较低)。否则,如果我在删除第 5、6、7 列之前删除了第 2 列和第 3 列,则第一次删除将导致这些列重新编号(变为 3、4、5)

  1. 使用文本::CSV处理任何有效的 CSV(包括诸如包含逗号的多行引号列之类的内容):
$ perl -MText::CSV -e '
  my $csv = Text::CSV->new();
  while (my $row = $csv->getline(*ARGV)) {
    next if $seen{join ",", @$row}++;
    splice @$row, 5, 3;
    splice @$row, 1, 2;
    $csv->say(*STDOUT, $row);
  }' input.csv
Col1,Col4,Col5,Col9,Col10
"info 1","info 4",5,9,"info 10"
"address 1",4,5,9,"address 10"
"city 1",4,5,9,"city 10"

这里有四件事值得注意:

  1. Text::CSV不是核心 perl 模块,因此需要安装。它适用于大多数(如果不是全部)Linux 发行版。例如在 Debian 上,您可以使用 安装它sudo apt-get install libtext-csv-perl。否则,您可以使用cpanperl 附带的命令安装它。

  2. Text::CSV 的getline()方法(如上所示$row = $csv->getline(*ARGV))返回对数组或 arrayref 的引用。这是一个指向整个数组的标量值(请参阅man perlrefman perldata了解更多信息)。

  3. $row上面的代码中包含 arrayref。使用/操作 $row 适用于引用本身,而不适用于它引用的数据。因此,例如,$row2 = $row复制引用,而不是数据。两个参考文献都指向相同的数据。@$row将 arrayref 作为数组“取消引用”,以便可以像任何其他数组一样使用它。

  4. in是一个特殊的文件句柄*ARGVgetline(*ARGV)它从命令行上给出的所有文件名参数读取输入(这些参数存储在 perl 中名为 @ARGV 的数组中)。假定非文件名参数(例如选项,如果您的脚本具有处理选项的代码)已被处理并从@ARGV 中删除。不存在或无法打开的文件名(例如由于权限)将产生错误消息。简而言之,它会读取您指定的一个或多个文件名。的参数-被视为标准输入,因此它可以从文件、标准输入或两者中读取输入。

这是一个非常简单和原始的示例,说明了 Text::CSV 的功能以及如何使用它。请阅读手册页以获取更多详细信息和示例。

正如您在上面的示例输出中看到的,默认情况下,如果文本字段包含空格,Text::CSV 将引用它们。如果您不希望它这样做,您可以通过将属性设置quote_space为零来覆盖它......或者当您使用以下new方法创建 $csv 对象时:

my $csv = Text::CSV->new({ quote_space => 0 });

或之后:

my $csv = Text::CSV->new();
$csv->quote_space(0);

那么输出将是这样的:

Col1,Col4,Col5,Col9,Col10
info 1,info 4,5,9,info 10
address 1,4,5,9,address 10
city 1,4,5,9,city 10

答案2

将其转换为数组,思考一下,将其重新创建为 csv:

perl -pe '@c = split(","); splice(@c, 1, 2); splice(@c, 3, 3); $_ = join(", ", @c)

如果您的字段被引用,您可以使用Text::CSV

$ cat in.csv 
Col1,Col2,Col3,Col4,Col5
one,two,three,four,five
six,"se,ven","ei,ght",nine,ten
$ perl -MText::CSV -e 'Text::CSV::csv( in => "in.csv", headers => false, on_in => sub { splice( @{@_[1]}, 1, 2) } )'
Col1,Col4,Col5
one,four,five
six,nine,ten

您询问有关 perl 的问题,但为了提高认识,还请考虑剪切工具: cut -f '1,4,5,9,10' -d ,

相关内容