【问题标题】:Combining multiple csv files together and also adding a column during concatenation [closed]将多个 csv 文件组合在一起并在连接期间添加一列 [关闭]
【发布时间】:2011-07-01 02:03:30
【问题描述】:

我有一组文件要导入 MySQL。

每个 CSV 文件如下所示:

Header1;Header2;Header3;Header4;Header5
Data1;Data2;Data3;Data4;Data5;
Data1;Data2;Data3;Data4;Data5;
Data1;Data2;Data3;Data4;Data5;
Data1;Data2;Data3;Data4;Data5;

数据可能包含空格、句点或完整的冒号。它们绝对不会包含分号,因此这是一个有效的分隔符。它们也不会包含 \n 或任何其他换行符。

示例数据

2010.08.30 18:34:59
0.7508
String of characters with spaces in them

每个文件都有一个唯一的名称。这些名称都符合以下模式:
Token1_Token2_Token3.csv

我有兴趣将大量这些 CSV 文件(大约数百个)组合成一个 CSV 文件。文件范围从 10KB 到 400MB。最终,我想将它发送到 MySQL。不要担心摆脱单独的标题行;我可以在 MySQL 中轻松做到这一点。

我希望最终的 CSV 文件如下所示:

Header1,Header2,Header3,Header4,Header5,FileName
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1

我不关心任何其他令牌。如果解决方案只是将每个 csv 文件名转储到 Token1 字段中,我也可以生存,因为我可以在 MySQL 中轻松解析它。

请帮帮我!我花了 10 多个小时来解决一个相对简单的问题。

可用技术:

    awk
窗口批处理
linux bash
外壳
perl
蟒蛇
php
mysql导入

这是一个服务器盒子,所以我无法编译任何东西,但如果你给我一个 Java 解决方案,我一定会尝试在盒子上运行它。

【问题讨论】:

  • 您是否尝试过查看 Python csv 模块文档 (docs.python.org/library/csv) 以读取数据文件(并编写组合文件),使用 str.partition 方法获取第一部分文件名?

标签: php python perl powershell awk


【解决方案1】:

使用Text::CSV

程序

#!/usr/bin/env perl

use strict;
use warnings;

use File::Find;
use Text::CSV;

my $semi_colon_csv = Text::CSV->new( { 'sep_char' => ';', } );
my $comma_csv = Text::CSV->new( {
    'sep_char' => ',',
    'eol'      => "\n",
} );

open my $fh_output, '>', 'output.csv' or die $!;

sub convert {
    my $file_name = shift;

    open my $fh_input, '<', $file_name or die $!;

    # header
    my $row = $semi_colon_csv->getline($fh_input);
    $comma_csv->print( $fh_output, [ @$row, $file_name ] );

    while ( $row = $semi_colon_csv->getline($fh_input) ) {
        pop @$row unless $row->[-1];  # remove trailing semi-colon from input
        my ($token) = ( $file_name =~ /^([^_]+)/ );
        $comma_csv->print( $fh_output, [ @$row, $token ] );
    }
}

sub wanted {
    return unless -f;
    convert($_);
}

my $path = 'csv';  # assuming that all your CSVs are in ./csv/
find( \&wanted, $path );

输出(输出.csv)

Header1,Header2,Header3,Header4,Header5,Token1_Token2_Token3.csv
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1
Data1,Data2,Data3,Data4,Data5,Token1

【讨论】:

  • 哇,感谢 perl,我担心这将是一个巨大的项目......我不确定 open
  • jlocke:不客气。 open 将文件与文件句柄相关联。任何时候,内存中都只有一行 CSV。
【解决方案2】:

信不信由你,它可能很简单:

awk 'BEGIN{OFS = FS = ";"} {print $0, FILENAME}' *.csv > newfile.csv

如果要将字段分隔符从分号更改为逗号:

awk 'BEGIN{OFS = ","; FS = ";"} {$1 = $1; print $0, FILENAME}' *.csv > newfile.csv

仅包含第一个标记:

awk 'BEGIN{OFS = ","; FS = ";"} {$1 = $1; split(FILENAME, a, "_"); print $0, a[1]}' *.csv > newfile.csv

【讨论】:

  • Dennis -- 最后一个 awk 语句应该这样做。它在测试数据上运行良好,我现在在 20GB 上运行它。出于某种原因,它在 $1 和拆分之间添加了一个空白的空列,但是一旦数据在数据库中,我可以稍后处理。太感谢了!!你摇滚。
  • @jlocke:从您的示例数据看来,您的行可能以分隔符结尾,这意味着最后一个字段为空。如果是这种情况,您可以将该空字段设置为文件名而不是附加它:awk 'BEGIN{OFS = ","; FS = ";"} {split(FILENAME, a, "_"); $NF = a[1]; print}' *.csv &gt; newfile.csv。顺便说一句,$1first 字段,所以我假设您的意思是文件名标记之前的空白字段(位于$0 和“拆分”之间)。 $1 = $1 强制替换字段分隔符。此评论中的版本不需要,作业可以。
  • @jlocke:请记住,您可以通过a2p 提供一个 awk 程序以获得等效的 Perl - 比其他过度杀伤的答案要短得多!如果您有更多此类任务,我建议您花一些时间学习 AWK,它真的很适合。
  • 正如所写,这将复制每个文件的标题行;我认为BEGIN{OFS = ","; FS = ";"} FNR &gt; 1 || NR == FNR {$1 = $1; split(FILENAME, a, "_"); print $0, a[1]}(未经测试)可以解决这个问题。更好的是,只需去掉所有标题 (FNR &gt; 1) - 无需标题行即可自动处理文件要容易得多。
  • @BeniCherniavsky-Paskin:好建议。
【解决方案3】:

您可能想尝试这个快速而肮脏的 Perl hack 来转换数据:

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

# Open input file
my $inputfile = shift or die("Usage: $0 <filename>\n\n");
open F, $inputfile or die("Could not open input file ($!)\n\n");

# Split filename into an array
my @tokens = split("_", $inputfile);

my $isFirstline = 1;

# Iterate each line in the file
foreach my $line (<F>) {
    my $addition;

    chomp($line);    # Remove newline

    # Add the complete filename to the line at first line
    if ($isFirstline) {
        $isFirstline = 0;
        $addition    = ",$inputfile";
    } else {         # Add first token for the rest of the lines
        $addition = ",$tokens[0]";
    }

    # Split the data into @elements array
    my @elements = split(";", $line);

    # Join it using comma and add filename/token & a new line
    print join(",", @elements) . $addition . "\n";
}

close(F);

【讨论】:

    【解决方案4】:

    Perl 的 DBI 模块可以处理 CSV 文件(需要 DBD::CSV 模块)和 MySQL。只需将所有 csv 文件放在同一个目录中,然后像这样查询它们:

    use DBI;
    my $DBH = DBI->connect ("dbi:CSV:", "", "", { f_dir => "$DATABASEDIR", f_ext => ".csv", csv_sep_char => ";",});
    my $sth = $dbh->prepare ("SELECT * FROM Token1_Token2_Token3");
    $sth->execute;
    while (my $hr = $sth->fetchrow_hashref) {
    
     [...]
    }
    $sth->finish ();
    

    您可以查询 csv 文件(包括 JOIN 语句!)并将数据直接插入 MySQL。

    【讨论】:

      【解决方案5】:

      这是在 PowerShell 中执行此操作的一种方法:

      $res = 'result.csv'
      'Header1,Header2,Header3,Header4,Header5,FileName' > $res
      
      foreach ($file in dir *.csv)
      {
        if ($file -notmatch '(\w+)_\w+_\w+\.csv') { continue }
      
        $csv = Import-Csv $file -Delimiter ';'
        $csv | Foreach {"{0},{1},{2},{3},{4},{5}" -f `
          $_.Header1,$_.Header2,$_.Header3,$_.Header4,$_.Header5,$matches[1]} >> $res
      }
      

      如果文件的大小不是那么大,我建议走这条路:

      $csvAll = @()
      foreach ($file in dir *.csv)
      {
        if ($file -notmatch '(\w+)_\w+_\w+\.csv') { continue }
      
        $csv = Import-Csv $file -Delimiter ';'
        $csv | Add-Member NoteProperty FileName $matches[1]
        $csvAll += $csv
      }
      
      $csvAll | Export-Csv result.csv -NoTypeInformation
      

      但是,这会将所有 CSV 文件的完整内容保存在内存中,直到最后准备好导出。除非您拥有具有大量内存的 64 位 Windows,否则这是不可行的。 :-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-06-04
        • 2017-12-21
        • 2021-07-11
        • 2018-03-30
        • 2020-12-27
        • 2017-09-25
        • 1970-01-01
        相关资源
        最近更新 更多