14

Я уверен, что у кого-то возникла следующая потребность, как быстро разбить огромный файл .gz по строкам? Базовый текстовый файл имеет 120 миллионов строк. У меня недостаточно места на диске, чтобы разархивировать весь файл сразу, поэтому мне было интересно, знает ли кто-нибудь сценарий bash/perl или инструмент, который может разбить файл (либо .gz, либо внутренний .txt) на файлы строк размером 3х 40 мин , то есть называя это так:

    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 greatfile.txt.gz | голова 4000000

Примечание: я не могу получить дополнительный диск.

Спасибо!

7 ответов7

19

канал для разделения используйте либо gunzip -c или zcat, чтобы открыть файл

gunzip -c bigfile.gz | split -l 400000

Добавьте выходные данные в команду split.

11

Как это сделать лучше всего зависит от того, что вы хотите:

  • Вы хотите извлечь одну часть большого файла?
  • Или вы хотите создать все детали за один раз?

Если вам нужна отдельная часть файла, ваша идея использовать gunzip и head верна . Ты можешь использовать:

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

Это вывело бы первые 4000000 строк на стандартном выходе - вы, вероятно, захотите добавить другой канал, чтобы фактически что-то делать с данными.

Чтобы получить другие части, вы должны использовать сочетание head и tail , например:

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

чтобы получить второй блок.

Возможно, для решения этой серии или же для gunzip -c потребуется достаточно места для распаковки всего файла

Нет, gunzip -c не требует места на диске - он все делает в памяти, а затем выводит его на стандартный вывод.


Если вы хотите создать все детали за один раз, более эффективно создать их все одной командой, потому что тогда входной файл читается только один раз. Хорошее решение - использовать split ; подробности смотрите в ответе Джима Макнамара.

6

Поскольку вы работаете с потоком (без перемотки), вам нужно использовать форму хвоста '+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
4

Я хотел бы рассмотреть возможность использования сплит.

разбить файл на части

2

Вот Perl-программа, которая может использоваться для чтения стандартного ввода и разбиения строк, передавая каждую группу в отдельную команду, которая может использовать переменную оболочки $ 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;
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 отправит каждую строку в std out, чтобы вы могли перенаправить ее в файл. В качестве альтернативы, если вы дадите нам знать, что вы хотите сделать со строками, я могу добавить это в скрипт python, и вам не нужно будет оставлять куски файла лежащими вокруг.

1

Непосредственно разделить файл .gz на файлы .gz:

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

Я думаю, что именно этого хотел ОП, потому что у него не так много места.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .