unix - розділити величезний .gz файл за рядком


16

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

    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 - ні, я не потребую їх знову gziped. Але я не зміг зберегти всі розбиті текстові файли одразу. Тож я хотів би отримати перший спліт, зробити його з ним, потім видалити перший розділ, а потім дістати другий спліт.etc, нарешті, видаливши оригінальний gz
— toop

1
@toop: Дякую за пояснення. Зауважте, що зазвичай краще відредагувати своє запитання, якщо ви хочете уточнити його, а не ставити його в коментар; таким чином всі це побачать.
— sleske

Прийнята відповідь хороша, якщо ви хочете лише частину шматочків, і не знаєте їх заздалегідь. Якщо ви хочете генерувати всі фрагменти одразу, рішення, засновані на розщепленні, будуть набагато швидшими, 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дисковому простору не потрібно - він робить все в пам'яті, а потім виводить його на stdout.


Якщо ви хочете створити всі частини за один раз , то ефективніше їх створити за допомогою однієї команди, оскільки тоді вхідний файл читається лише один раз. Одне хороше рішення - використовувати split; див. відповідь Джим мcнамара для подробиць.


1
З точки зору продуктивності: чи дійсно gzip розпакує весь файл? Або він здатний "магічно" знати, що потрібно лише 4 мільйони ліній?
— Алоїс Магдал

3
@AloisMahdal: Насправді, це було б добре окреме питання :-). Коротка версія: gzipне знає про межу (яка походить від іншого процесу). Якщо headвін використовується, headвін вийде, коли отримав достатньо, і це пошириться на gzip(через SIGPIPE, див. Вікіпедія). Бо tailце неможливо, так що так, gzipдекомпресуємо все.
— sleske

Але якщо ви зацікавлені, ви дійсно повинні задати це окремим питанням.
— sleske

20

використовуйте або gunzip -c, або zcat, щоб відкрити файл

gunzip -c bigfile.gz | split -l 400000

Додайте вихідні характеристики до розділеної команди.


3
Це масово ефективніше, ніж прийнята відповідь, якщо тільки вам не потрібна лише частина розділених шматочків. Будь ласка, піднесіть заявку.
— b0fh

1
@ b0fh: Так, ви праві. Ухвалено і посилається на мою відповідь :-).
— sleske

Найкраща відповідь напевно.
— Стівен Блум

які специфікації виводу, щоб виходами були самі файли .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'

Я думаю, що цього хотів ОП, тому що у нього мало місця.


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)

Команда рядка друку надсилатиме кожен рядок до std, тому ви можете перенаправити файл. Крім того, якщо ви дасте нам знати, що ви хочете зробити з рядками, я можу додати це до сценарію python, і вам не потрібно буде залишати шматки файлу, що прокладають навколо.


2

Ось програма Perl, яка може бути використана для читання stdin та розділення рядків, переносячи кожну кладку в окрему команду, яка може використовувати змінну оболонки $ 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.