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

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

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

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

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

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

shell csv command-line
2个回答
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文件的头部预先添加到每个分割文件的输出流中。


0
投票

1 The gzip stream

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

2 The chunk size

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

3 The CSV stream

由于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 Invoke the whole pipeline

gzip -d -c sourcefile | perlscript

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

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

© www.soinside.com 2019 - 2024. All rights reserved.