将压缩的csv拆分为块的最有效方法

时间:2017-11-01 20:00:02

标签: shell csv command-line

我有一些非常大的gzip压缩的csv文件(mysqldump的压缩输出) - 每个大约65 GB。

我需要将它们分成压缩块,每个压缩块小于4 GB(压缩后),请记住每个csv文件中都存在引用的新行字符。

在'nix命令行上(例如在Debian中)执行此操作的最有效方法是什么?

与此SO类似,但回复未正确解释引用的换行符。

2 个答案:

答案 0 :(得分:3)

不使用临时磁盘空间的方法。

块大小估计

首先,由于每行的压缩比根据其内容而变化,因此除非运行2遍编码,否则很难获得精确的压缩后大小目标。但是,您可以使用

粗略估计压缩前块的大小
gzip -l file.csv.gz | tail -1 | awk '{print int(4*$2/$1)}'

此命令中的4(GB)是压缩后每个块的目标大小,因此如果结果显示21,则表示您可以使用大约一个块大小拆分未压缩文件21(GB),假设文件具有统一的熵分布。

解压缩,拆分和压缩

我们可以使用上面获得的块大小来分割文件

gzip -dc file.csv.gz | split -C 21G -d - split_ --filter='gzip > $FILE.csv.gz'
  • gzip -dc将文件解压缩为stdout
  • split -C 21G每个输出文件最多放置21G记录
  • split --filter='gzip > $FILE.csv.gz'为每个拆分文件启用直接压缩

额外提示:将上面的所有gzip替换为pigz以启用更快的多线程压缩

<强>更新

要保留每个拆分的gzip压缩文件的标头,我们可以使用

gzip -dc file.csv.gz | tail -n+2 | split - --filter='{ gzip -dc file.csv.gz | head -1; cat; } | gzip > $FILE.csv.gz'

为简单起见,此处忽略了split的某些选项,但您明白了。诀窍是修改split中的过滤器选项,以便它将原始csv文件的头部预先添加到每个分割文件的输出流中。

答案 1 :(得分:0)

1 gzip流

由于gzip是具有历史表的压缩,因此您不能简单地拆分压缩流。你需要解码它。 将输出传递给拆分器程序时,可能会节省一些磁盘空间。

2块大小

接下来的任务是获得4GB的块。如果您无法安全地估算压缩率,唯一安全的方法是削减每4GB 原始 CSV数据。这可能会导致比4GB小得多的块。

3 CSV流

由于CSV格式(带换行符)不允许重新同步,因此还必须解析整个CVS流 - 假设您不是只想在任意位置拆分CSV,而是在记录的末尾,即逻辑CVS线。

事实上后一种程序可以稍微简化一下。假设CSV数据中的潜在双引号像往常一样使用双引号进行转义,则在偶数双引号后出现的每个换行符都可以假定为记录分隔符。因此,解析器任务减少到计算流中的双引号。

我很确定这仍然无法通过shell脚本合理解决。我会推荐一些Perl脚本或类似的东西来进行解析和拆分。

脚本需要从stdin读取,计算字节数和双引号数,并将结果传递给gzip > targetfile。每次字节数达到任务2的限制时,它应该在当前缓冲区中寻找流中偶数个双引号之后的换行符。然后将到此时为止的字节发送到当前的gzip实例,并关闭输出流。现在递增目标文件名并打开一个新的gzip输出,重置字节计数器并将当前缓冲区的剩余部分传递给新的gzip输出流。

以下脚本演示了解决方案:

#!/usr/bin/perl
use strict;

my $targetfile = "target";
my $limit = 1 << 32; # 4GB

my $filenum = 0;
open F, "|-", "gzip >$targetfile-$filenum.gz" or die;
my ($buffer, $bytes, $quotes);
while (read STDIN, $buffer, 1024*1024)
{ $bytes += length $buffer;
  if ($bytes > $limit)
  { my $pos;
    do
    { $pos = 1 + index $buffer, "\n", $pos;
      $pos or die "no valid delimiter found: $bytes";
    } while (((substr($buffer, 0, $pos) =~ tr/"//) + $quotes) & 1);
    print F substr $buffer, 0, $pos or die;
    close F;
    ++$filenum;
    open F, "|-", "gzip >$targetfile-$filenum.gz" or die;
    $buffer = substr $buffer, $pos;
    $bytes = length $buffer;
  }
  $quotes += $buffer =~ tr/"//;
  print F $buffer or die;
}
close F;

抄写员假设在1MB的块中至少有一个有效的记录分隔符。

4调用整个管道

gzip -d -c sourcefile | perlscript

这将完成整个任务。它不会使用超过几MB的内存,主要用于Perl解释器。

在磁盘上,您需要两倍的存储空间来保存源文件以及目标文件。