我有一些非常大的gzip压缩的csv文件(mysqldump
的压缩输出) - 每个大约65 GB。
我需要将它们分成压缩块,每个压缩块小于4 GB(压缩后),请记住每个csv文件中都存在引用的新行字符。
在'nix命令行上(例如在Debian中)执行此操作的最有效方法是什么?
与此SO类似,但回复未正确解释引用的换行符。
答案 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)
由于gzip是具有历史表的压缩,因此您不能简单地拆分压缩流。你需要解码它。 将输出传递给拆分器程序时,可能会节省一些磁盘空间。
接下来的任务是获得4GB的块。如果您无法安全地估算压缩率,唯一安全的方法是削减每4GB 原始 CSV数据。这可能会导致比4GB小得多的块。
由于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的块中至少有一个有效的记录分隔符。
gzip -d -c sourcefile | perlscript
这将完成整个任务。它不会使用超过几MB的内存,主要用于Perl解释器。
在磁盘上,您需要两倍的存储空间来保存源文件以及目标文件。