unix - разбить огромный .gz файл построчно


16

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

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

Благодарность!


1
Хотите ли вы, чтобы полученные файлы снова распаковывались?

Вы можете использовать gunzip в ipe. Остальное можно сделать с головой и хвостом
— Инго

@Tichodroma - нет, они мне больше не нужны. Но я не смог сохранить все разделенные текстовые файлы одновременно. Поэтому я хотел бы получить первый раскол, делать вещи с ним, а затем удалите первый раскол, а затем получить второе split.etc окончательно удалим оригинальный GZ
— Toop

1
@toop: Спасибо за разъяснения. Обратите внимание, что, как правило, лучше отредактировать свой вопрос, если вы хотите уточнить его, чем помещать его в комментарий; Таким образом, все увидят это.
— слеске

Принятый ответ хорош, если вы хотите только часть кусков и не знаете их заранее. Если вы хотите сгенерировать все чанки одновременно, решения, основанные на разбиении, будут намного быстрее, O (N) вместо O (N²).
— b0fh

Ответы:


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; подробности смотрите в ответе Джима Макнамара.


1
С точки зрения производительности: распаковывает ли gzip весь файл? Или он может «волшебным образом» знать, что нужны только 4 млн строк?
— Алоис Махдал

3
@AloisMahdal: На самом деле, это был бы хороший отдельный вопрос :-). Краткая версия: gzipне знает о пределе (который происходит от другого процесса). Если headиспользуется, headвыйдет, когда получит достаточно, и это будет распространяться на gzip(через SIGPIPE, см. Википедию). Для tailэтого не возможно, так что да, gzipбудет распаковывать все.
— слеське

Но если вам интересно, вы должны задать этот вопрос как отдельный вопрос.
— слеське

20

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

gunzip -c bigfile.gz | split -l 400000

Добавьте выходные спецификации в команду split.


3
Это значительно эффективнее, чем принятый ответ, если только вам не нужна только часть разделенных кусков. Пожалуйста, проголосуйте.
— b0fh

1
@ b0fh: Да, вы правы. Голосовал и ссылался в моем ответе :-).
— Слёске

Лучший ответ наверняка.
— Стивен Блюм

Каковы выходные спецификации, так что выходные данные сами файлы .gz?
— Кецалькоатль

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'

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


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, и вам не нужно будет оставлять куски файла лежащими вокруг.


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;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.