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.