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


16

誰かが以下の必要性を持っていると確信しています、巨大な.gzファイルを行ごとにすばやく分割する方法は何ですか?基になるテキストファイルには1億2000万行があります。ファイル全体を一度に圧縮するのに十分なディスク容量がないので、誰かがファイル(.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

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

ありがとう!


1
結果のファイルを再度gzip圧縮しますか?

ipeでgunzipを使用できます。残りは頭と尾で行うことができます
Ingo

@Tichodroma-いいえ、再びgzipで圧縮する必要はありません。しかし、すべての分割テキストファイルを一度に保存することはできませんでした。だから私は最初の分割を取得し、それを使って最初の分割を削除し、次に2番目の分割を取得して、最終的に元のgzを削除し
たいと思います-toop

1
@toop:説明をありがとう。質問をコメントに入れるよりも、明確にしたい場合は一般に質問を編集する方が良いことに注意してください。そうすれば誰もがそれを見るでしょう。
sleske

チャンクの一部のみが必要で、事前にそれらを知らない場合、受け入れられる答えは良いです。すべてのチャンクを一度に生成する場合、分割に基づくソリューションは、O(N²)ではなく、O(N)により高速になります。
b0fh

回答:


11

これをどのように最適に行うかは、何を望むかに依存します。

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

あなたがしたい場合は、ファイルの単一の部分を、あなたのアイデアを使用する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ディスクスペースは必要ありません。メモリ内のすべてを実行してから、stdoutにストリーミングします。


あなたが作成したい場合は一度にすべての部品を、入力ファイルは一度だけ読まれているので、単一のコマンドでそれらすべてを作成する方が効率的です。1つの良い解決策は、使用することsplitです。詳細については、jim mcnamaraの回答を参照してください。


1
パフォーマンスの観点から:gzipは実際にファイル全体を解凍しますか?または、必要な行が4ミリ行のみであることを「魔法のように」知ることができますか?
アロイスマーダル

3
@AloisMahdal:実際には、それは別の質問になります:-)。ショートバージョン:gzip制限(別のプロセスに由来する)については知りません。headが使用された場合、head十分に受信すると終了し、これはgzipSIGPIPE経由で伝播します(Wikipediaを参照)。以下の場合tail、このことはできませんので、はい、gzipすべてを解凍します。
sleske

ただし、興味がある場合は、別の質問として実際に質問する必要があります。
sleske

20

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

gunzip -c bigfile.gz | split -l 400000

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


3
分割されたチャンクの一部のみが必要な場合を除き、これは受け入れられている答えよりもはるかに効率的です。賛成してください。
b0fh 14

1
@ b0fh:はい、あなたは正しいです。賛成し、私の答えで参照されています:-)。
-sleske

間違いなくベストアンサー。
スティーブンブルーム

出力が.gzファイルそのものになるように、出力仕様は何ですか?
ケツァルコアトル

7

(巻き戻し不可能な)ストリームで作業している場合、 '+ N'形式のtailを使用して、行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'

彼はスペースがあまりないので、これがOPの望みだと思います。


2

ディレクトリからファイルのグロブセットを開き、必要に応じてそれらを圧縮し、それらを1行ずつ読み取る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コマンドはすべての行を標準出力に送信するため、ファイルにリダイレクトできます。あるいは、行で何をしたいのかを教えていただければ、それをpythonスクリプトに追加することができ、ファイルの塊を残しておく必要はありません。


2

以下は、標準入力を読み取り、行を分割し、シェル変数$ SPLITを使用して別の宛先にルーティングできる個別のコマンドに各クランプをパイプするために使用できるperlプログラムです。あなたの場合、それは

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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.