unix-按行分割巨大的.gz文件


16

我确定有人具有以下需求,按行分割大.gz文件的快速方法是什么?基础文本文件具有1.2亿行。我没有足够的磁盘空间来立即压缩整个文件,所以我想知道是否有人知道可以将文件(.gz或内部.txt)分割为3x 4000万行文件的bash / perl脚本或工具。即调用它像:

    bash splitter.sh hugefile.txt.gz 4000000 1
 would get lines 1 to 40 mn    
    bash splitter.sh hugefile.txt.gz 4000000 2
would get lines 40mn to 80 mn
    bash splitter.sh hugefile.txt.gz 4000000 3
would get lines 80mn to 120 mn

也许正在做一系列这样的解决方案,或者gunzip -c需要足够的空间来解压缩整个文件(即原始问题):gunzip -c hugefile.txt.gz | 头4000000

注意:我无法获得额外的磁盘。

谢谢!


1
是否要再次将生成的文件压缩为gzip?

您可以在ipe中使用gunzip。剩下的事情可以用头和尾完成
Ingo 2012年

@Tichodroma-不,我不需要再次将它们压缩。但是我无法一次存储所有拆分文本文件。因此,我想进行第一个拆分,对其进行处理,然后删除第一个拆分,然后获取第二个split.etc,最后删除原始的gz
toop 2012年

1
@toop:感谢您的澄清。请注意,如果您想澄清问题,通常最好编辑它,而不要在评论中添加;这样每个人都会看到它。
sleske 2012年

如果您只想要一部分块,并且事先不知道它们,那么可以接受的答案很好。如果要一次生成所有块,则基于拆分的解决方案将比O(N²)更快,O(N)。
b0fh

Answers:


11

如何做到最好取决于您想要的:

  • 您是否要提取大文件的单个部分?
  • 还是要一次性创建所有零件?

如果您只需要文件的一部分,则可以使用您的想法gunzip,这head是正确的。您可以使用:

gunzip -c hugefile.txt.gz | head -n 4000000

这将在标准输出上输出前4000000行-您可能希望附加另一个管道以对数据进行实际处理。

要获得其他部分,你会使用的组合headtail,如:

gunzip -c hugefile.txt.gz | head -n 8000000 |tail -n 4000000

得到第二块。

也许正在做一系列这样的解决方案,或者gunzip -c需要足够的空间来解压缩整个文件

不,gunzip -c不需要任何磁盘空间-它在内存中执行所有操作,然后将其流式传输到stdout。


如果要一次性创建所有零件,则使用单个命令将它们全部创建会更有效,因为这样输入文件仅被读取一次。一种好的解决方案是使用split; 有关详细信息,请参见吉姆·麦克纳马拉的答案。


1
从性能角度来看:gzip实际上能解压缩整个文件吗?还是能够“神奇”地知道只需要400万行?
Alois Mahdal 2012年

3
@AloisMahdal:实际上,这将是一个很好的单独问题:-)。简短版本:gzip不知道限制(来自不同的过程)。如果head使用,head则会在收到足够的信息时退出,并将传播到gzip(通过SIGPIPE,请参见Wikipedia)。因为tail这是不可能的,所以是的,gzip将解压缩所有内容。
sleske 2012年

但是,如果您有兴趣,则应该将其作为一个单独的问题进行询问。
sleske 2012年

20

使用gunzip -c或zcat打开文件进行拆分的管道

gunzip -c bigfile.gz | split -l 400000

将输出规范添加到split命令。


3
除非您只需要一小部分拆分块,否则这比接受的答案要高效得多。请投票。
b0fh 2014年

1
@ b0fh:是的,您是对的。赞成,并在我的回答中引用:-)。
sleske '16

肯定的最佳答案。
斯蒂芬·布鲁姆

输出规范是什么,以便输出本身是.gz文件?
Quetzalcoatl

7

在处理(不可倒带)流时,您将需要使用“ + N”形式的尾部来获取从N行开始的行。

zcat hugefile.txt.gz | head -n 40000000
zcat hugefile.txt.gz | tail -n +40000001 | head -n 40000000
zcat hugefile.txt.gz | tail -n +80000001 | head -n 40000000


3

将.gz文件直接拆分为.gz文件:

zcat bigfile.gz | split -l 400000 --filter='gzip > $FILE.gz'

我认为这是OP想要的,因为他没有太多空间。


2

这是一个python脚本,用于从目录中打开文件集,并在必要时将其压缩,然后逐行阅读。它仅使用内存中用于保存文件名和当前行所需的空间,以及少许开销。

#!/usr/bin/env python
import gzip, bz2
import os
import fnmatch

def gen_find(filepat,top):
    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist,filepat):
            yield os.path.join(path,name)

def gen_open(filenames):
    for name in filenames:
        if name.endswith(".gz"):
            yield gzip.open(name)
        elif name.endswith(".bz2"):
            yield bz2.BZ2File(name)
        else:
            yield open(name)

def gen_cat(sources):
    for s in sources:
        for item in s:
            yield item

def main(regex, searchDir):
    fileNames = gen_find(regex,searchDir)
    fileHandles = gen_open(fileNames)
    fileLines = gen_cat(fileHandles)
    for line in fileLines:
        print line

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Search globbed files line by line', version='%(prog)s 1.0')
    parser.add_argument('regex', type=str, default='*', help='Regular expression')
    parser.add_argument('searchDir', , type=str, default='.', help='list of input files')
    args = parser.parse_args()
    main(args.regex, args.searchDir)

print line命令将把每一行发送到标准输出,因此您可以重定向到文件。另外,如果您让我们知道您要对这些行进行什么操作,则可以将其添加到python脚本中,而无需保留文件的大部分内容。


2

这是一个perl程序,可用于读取stdin并拆分行,将每个簇用管道传递到单独的命令,该命令可以使用shell变量$ SPLIT将其路由到其他目的地。对于您的情况,将使用

zcat hugefile.txt.gz | perl xsplit.pl 40000000 'cat > tmp$SPLIT.txt; do_something tmp$SPLIT.txt; rm tmp$SPLIT.txt'

抱歉,命令行处理有些麻烦,但是您知道了。

#!/usr/bin/perl -w
#####
# xsplit.pl: like xargs but instead of clumping input into each command's args, clumps it into each command's input.
# Usage: perl xsplit.pl LINES 'COMMAND'
# where: 'COMMAND' can include shell variable expansions and can use $SPLIT, e.g.
#   'cat > tmp$SPLIT.txt'
# or:
#   'gzip > tmp$SPLIT.gz'
#####
use strict;

sub pipeHandler {
    my $sig = shift @_;
    print " Caught SIGPIPE: $sig\n";
    exit(1);
}
$SIG{PIPE} = \&pipeHandler;

my $LINES = shift;
die "LINES must be a positive number\n" if ($LINES <= 0);
my $COMMAND = shift || die "second argument should be COMMAND\n";

my $line_number = 0;

while (<STDIN>) {
    if ($line_number%$LINES == 0) {
        close OUTFILE;
        my $split = $ENV{SPLIT} = sprintf("%05d", $line_number/$LINES+1);
        print "$split\n";
        my $command = $COMMAND;
        open (OUTFILE, "| $command") or die "failed to write to command '$command'\n";
    }
    print OUTFILE $_;
    $line_number++;
}

exit 0;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.