web-dev-qa-db-ja.com

unix-巨大な.gzファイルを行ごとに分割する

誰かが以下の必要性を持っていると確信しています、巨大な.gzファイルを行ごとにすばやく分割する方法は何ですか?基になるテキストファイルには1億2,000万行あります。ファイル全体を一度にgunzipするのに十分なディスク領域がないので、誰かがファイル(.gzまたは内部.txt)を3x 40mn行ファイルに分割できるbash/Perlスクリプトまたはツールを知っているのかどうか疑問に思いました。つまり、次のように呼び出します。

    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 hugefile.txt.gz |頭4000000

注:追加のディスクを取得できません。

ありがとう!

16
toop

これを最適に行う方法は、目的によって異なります。

  • 大きなファイルの一部を抽出しますか?
  • または、すべてのパーツを一度に作成しますか?

ファイルの一部が必要な場合は、gunzipおよびheadを使用するという考えが正しいです。以下を使用できます。

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

これにより、標準出力の最初の4000000行が出力されます。おそらく、実際にデータを処理するために別のパイプを追加する必要があります。

他の部分を取得するには、次のようにheadtailを組み合わせて使用​​します。

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

2番目のブロックを取得します。

おそらくこれらの一連の解決策を実行していますか、それともgunzip -cはファイル全体を解凍するのに十分なスペースを必要としますか

いいえ、gunzip -cはディスク領域を必要としません-メモリ内のすべてを実行してから、標準出力にストリーミングします。


すべてのパーツを一度にを作成する場合は、1つのコマンドですべてを作成する方が効率的です。これは、入力ファイルが一度だけ読み取られるためです。良い解決策の1つは、split;を使用することです。詳細はジム・マクナマラの答えを見てください。

11
sleske

分割するパイプは、gunzip -cまたはzcatを使用してファイルを開きます

gunzip -c bigfile.gz | split -l 400000

Splitコマンドに出力仕様を追加します。

20
jim mcnamara

(巻き戻し不可の)ストリームで作業しているときに、「+ 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
7
zgpmax

split の使用を検討します。

ファイルを分割する

.gzファイルを.gzファイルに直接分割します。

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

OPが欲しかったのはこれだと思います。彼はスペースがあまりないからです。

3
siulkilulki

Stdinを読み取り、行を分割するために使用できる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
Liudvikas Bukys

pythonスクリプトは、ディレクトリからファイルのグロブセットを開き、必要に応じてそれらをgunzipし、1行ずつ読み取ります。これは、ファイル名を保持するためにメモリ内で必要なスペースのみを使用します。現在のラインに加えて、少しオーバーヘッドがあります。

#!/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
Spencer Rathbun