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.