【问题标题】:Manipulating huge CSV files with sed使用 sed 处理巨大的 CSV 文件
【发布时间】:2010-11-13 12:35:31
【问题描述】:

我需要修改一组 4 个大型 CSV 文件。我需要做的是匹配这个表达式/^(.*),,/ 复制原子,然后将其添加到每个后续行,直到原子再次匹配。然后我需要冲洗并重复直到文件结束(每个文件大约有 25k 行)。最后我需要返回并移除第一个原子。

如果可能的话,我想为此使用 sed。我尝试用 vim 做,但无法正确使用正则表达式。任何帮助将不胜感激。下面举例说明:

之前:

0917,,
,882-1273,1
,95F 9475,1
,276-080,1
,40K 0080,1
,275-690A,1
,TX-2311,3
,TX-3351,4
,B-07432,1
,B-6901,1
,23-753,1
,02F 4307,1
,5.1K QBK-ND,1
,0944-026,1
,0944-027,1
,0944-004,1
,0944-056,1
,0944-057,1
,0944-082,1
,0944-024,1
,0944-025,1
,0944-102,4
,LOR 102,1
0918,,
,CJ1085,1
,1352-152,4
,DMS3102A-18-,1
,6-32 KEP,7
,6-32 X 3/4,4
,6-32X1/2,4
,1251-102,8
,Oct-32,4
,10-32 SAE,8

之后:

0917,882-1273,1
0917,95F 9475,1
0917,276-080,1
0917,40K 0080,1
0917,275-690A,1
0917,TX-2311,3
0917,TX-3351,4
0917,B-07432,1
0917,B-6901,1
0917,23-753,1
0917,02F 4307,1
0917,5.1K QBK-ND,1
0917,0944-026,1
0917,0944-027,1
0917,0944-004,1
0917,0944-056,1
0917,0944-057,1
0917,0944-082,1
0917,0944-024,1
0917,0944-025,1
0917,0944-102,4
0917,LOR 102,1
0918,CJ1085,1
0918,1352-152,4
0918,DMS3102A-18-,1
0918,6-32 KEP,7
0918,6-32 X 3/4,4
0918,6-32X1/2,4
0918,1251-102,8
0918,Oct-32,4
0918,10-32 SAE,8

【问题讨论】:

  • 好问题!问题并不过分宽泛,您对您正在尝试做的事情有一个相当清晰的描述,并且您有一个输入和所需输出的详细示例。向你致敬!我希望更多的 SO 用户能够根据这个标准制定他们的查询。
  • 这个数据集是否曾经通过 Excel? 0918,Oct-32,4 行在我看来很可疑。
  • @bsisco 我会仔细检查0918,Oct-32,4 行。
  • @Sinan 不错。它应该是 10-32,就像它下面的行 +1 给你先生
  • @bsisco 谢谢。请记住:除非绝对必要,否则不要让 Excel 触及您的数据:abovethelaw.com/2008/10/the_case_for_sleep_what_happen.phpnetworkworld.com/news/2008/…

标签: regex vim csv sed


【解决方案1】:

这是一个 sed 解决方案。这不是最干净的,我确信有更好的方法来音译这两行,但我发现这很有趣。 (实际上,我会使用 Perl 解决方案,但我发布这个是为了新颖,并希望看到改进。)

sed -e '/,,/{s/,,//; H; d;}' -e 'G' -e 's/\(.*\)\n\(.*\)/\2\1/'

【讨论】:

  • 因为 CSV 文件具有不同的数据结构,例如固定分隔符。因此,使用字段而不是正则表达式来操作数据会更容易。
  • @ghostdog74:这是对我的回答投反对票吗?否决票应该针对问题,而不是我的回答!
【解决方案2】:

这是一个完整的例子,用 Perl 编写,使用 Perl 5.10 的新特性;

#!/usr/bin/perl
use strict;
use warnings;

use feature qw'switch say';

my $append;

while( <> ){
  given( $_ ){

    when( /^$/ ){
      # handle empty line
      say STDERR '#';
    }

    # handle lines that start with "#"
    when( /^\s*[#](.*)/s ){
      print STDERR '# comment:', $1;
    }

    # handle lines that end with two commas
    when( /(.+),,\s*$/ ){
      $append = $1;
    }

    # handle lines that start with a comma
    when( /^,/ ){
      die unless defined $append;
      print $append, $_;
    }
  }
}

输入

0917,,
,882-1273,1
,95F 9475,1
,276-080,1

,40K 0080,1
,275-690A,1
,TX-2311,3
# ignore
 # ignore this too
,TX-3351,4
,B-07432,1
,B-6901,1
,23-753,1
,02F 4307,1
,5.1K QBK-ND,1
,0944-026,1
,0944-027,1
,0944-004,1
,0944-056,1
,0944-057,1
,0944-082,1
,0944-024,1
,0944-025,1
,0944-102,4
,LOR 102,1
0918,,
,CJ1085,1
,1352-152,4
,DMS3102A-18-,1
,6-32 KEP,7
,6-32 X 3/4,4
,6-32X1/2,4
,1251-102,8
,Oct-32,4
,10-32 SAE,8

输出

#
# comment: ignore
# comment: ignore this too
0917,882-1273,1
0917,95F 9475,1
0917,276-080,1
0917,40K 0080,1
0917,275-690A,1
0917,TX-2311,3
0917,TX-3351,4
0917,B-07432,1
0917,B-6901,1
0917,23-753,1
0917,02F 4307,1
0917,5.1K QBK-ND,1
0917,0944-026,1
0917,0944-027,1
0917,0944-004,1
0917,0944-056,1
0917,0944-057,1
0917,0944-082,1
0917,0944-024,1
0917,0944-025,1
0917,0944-102,4
0917,LOR 102,1
0918,CJ1085,1
0918,1352-152,4
0918,DMS3102A-18-,1
0918,6-32 KEP,7
0918,6-32 X 3/4,4
0918,6-32X1/2,4
0918,1251-102,8
0918,Oct-32,4
0918,10-32 SAE,8

【讨论】:

    【解决方案3】:

    正如我在您之前关于同一主题的问题中指出的那样,我发现 Perl 更容易:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $prefix = q{};
    
    while ( <> ) {
        last unless /\S/;
        if ( /^(.+),,$/ ) {
            $prefix = $1;
            next;
        }
        print $prefix, $_;
    }
    

    【讨论】:

    • 想如果没有“社区 Wiki”,我会再试一次。再次感谢您指出错误。
    • -1:您假设 CSV 文件格式很简单,但事实并非如此
    • @Stefano 不,CSV 的所有现有变体都不简单,但 OP 给出的格式是。该程序的目的似乎是处理根据某些规范生成的数据,而不是处理由无数用户生成的任意电子表格。这完全是虚假的反对票。
    • 我将捕获的部分更改为.+ 以避免匹配空字符串。您可能希望选择更符合您的规范的模式,例如[A-Za-z0-9]。前缀的长度总是4吗?
    • 我同意你的观点。事实上,我更详细地阅读了这篇文章以及 bsisco 给出的规格,你是对的。我在 +1 投票中转换了我的反对票。你的解决方案和我的一样好用,而且你只关注规格。实际上,我邀请 bsisco 为您提供正确答案标记,因为从技术上讲,您正在按照要求做,不多也不少(我们知道它在软件开发中的重要性)。
    【解决方案4】:

    这是一个使用 awk 的解决方案:

    awk -F, '{ if ($1 != "") prefix=$1; else printf "%s%s\n", prefix,$0 }' myfile.csv
    

    【讨论】:

      【解决方案5】:

      Perl 可能更简单:

      #!/usr/bin/perl -w
      
      $filename = $ARGV[0];
      open FILE, "<", $filename or die $!;
      
      while (<FILE>) {
          if(/^(.*),,/) {
              $prefix = $_;
              $prefix =~ s/,//g;
              $prefix =~ s/\s+//g;
              next;   
          }
          s/^,/$prefix,/g;
          print $_;
      }
      
      close FILE;
      

      【讨论】:

      • 别忘了use strict;更喜欢词法文件句柄;为什么要捕获而不使用捕获的子字符串; .* 将匹配空字符串等
      • 好的。我对 perl 有点陌生。谢谢你的建议。我看到你的似乎更简单。我会看看它。再次感谢
      【解决方案6】:

      程序(python)

      import csv
      infile=file("in","r")
      outfile=file("out","w")
      reader = csv.reader(infile , dialect='excel')
      writer = csv.writer(outfile , dialect='excel')
      current_header=""
      for inrow in reader:
          if len(inrow[0].strip()) != 0:
              current_header = inrow[0]
              continue
      
          writer.writerow([current_header]+inrow[1:])
      
      infile.close()
      outfile.close()
      print "done"
      

      输入

      0917,,
      ,882-1273,1
      ,95F 9475,1
      ,276-080,1
      ,40K 0080,1
      ,275-690A,1
      ,TX-2311,3
      ,TX-3351,4
      ,B-07432,1
      ,B-6901,1
      ,23-753,1
      ,02F 4307,1
      ,5.1K QBK-ND,1
      ,0944-026,1
      ,0944-027,1
      ,0944-004,1
      ,0944-056,1
      ,0944-057,1
      ,0944-082,1
      ,0944-024,1
      ,0944-025,1
      ,0944-102,4
      ,LOR 102,1
      0918,,
      ,CJ1085,1
      ,1352-152,4
      ,DMS3102A-18-,1
      ,6-32 KEP,7
      ,6-32 X 3/4,4
      ,6-32X1/2,4
      ,1251-102,8
      ,Oct-32,4
      ,10-32 SAE,8
      

      输出

      0917,882-1273,1
      0917,95F 9475,1
      0917,276-080,1
      0917,40K 0080,1
      0917,275-690A,1
      0917,TX-2311,3
      0917,TX-3351,4
      0917,B-07432,1
      0917,B-6901,1
      0917,23-753,1
      0917,02F 4307,1
      0917,5.1K QBK-ND,1
      0917,0944-026,1
      0917,0944-027,1
      0917,0944-004,1
      0917,0944-056,1
      0917,0944-057,1
      0917,0944-082,1
      0917,0944-024,1
      0917,0944-025,1
      0917,0944-102,4
      0917,LOR 102,1
      0918,CJ1085,1
      0918,1352-152,4
      0918,DMS3102A-18-,1
      0918,6-32 KEP,7
      0918,6-32 X 3/4,4
      0918,6-32X1/2,4
      0918,1251-102,8
      0918,Oct-32,4
      0918,10-32 SAE,8
      

      玩得开心

      【讨论】:

      • 我不认为它更短。 perl 更短,所以我想 awk 也很短。思南指出,你的规格非常明确。我的解决方案更通用,因为 python csv 模块考虑了 csv 的不同方言。如果您的开发人员更习惯于使用 python 并且您期望奇怪的 csv 数据集,请使用 python 解决方案。如果您的开发人员更习惯于 perl 并且您的格式将始终保持您指定的格式,请使用 perl 解决方案。
      • 对于 Perl,使用 search.cpan.org/perldoc/Text::CSVsearch.cpan.org/perldoc/Text::XSV 任意字符分隔的文件格式
      • @Stefano...好点。我几乎是这里唯一一个得到这样的东西掉在我身上的人。我正在学习(并且喜欢)python,所以这是我倾向于学习的方向。再次感谢。
      【解决方案7】:

      由于 sed 的语法晦涩难懂,而且你不是每天都在使用,更不用说你的同事了,这段代码将很难维护。首选 Perl/awk 解决方案。

      无论如何,here 是我见过的最好的 sed 手册。

      祝你好运 迪玛

      【讨论】:

      • 不回答问题。 “RTFM”不是答案。 “使用另一种语言”不是答案。 “使用另一种语言,你可以这样做”是一个答案。如果您建议使用 Perl,请举一个 Perl 示例说明如何使用。
      • 如您所见,亲爱的 A. Levy,所有答案都使用高级语言。猜猜为什么? sed 可能是很酷的工具,但是如果您不每天积极地使用它,那么比搜索和替换 (s///) 更复杂的东西是不受欢迎的。当被问到问题时,提出“你为什么需要它?”、“也许还有另一种解决方法?”这样的问题是合理的。等等。这是我的个人意见,如果你不接受它而不是你自己的问题。
      猜你喜欢
      • 2017-02-18
      • 1970-01-01
      • 1970-01-01
      • 2017-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多