unix - pisahkan file .gz besar dengan baris

16

Saya yakin seseorang memiliki kebutuhan di bawah ini, apa cara cepat untuk memecah file .gz besar demi baris? File teks yang mendasarinya memiliki 120 juta baris. Saya tidak punya cukup ruang disk untuk mem-gunzip seluruh file sekaligus jadi saya bertanya-tanya apakah ada yang tahu tentang skrip bash / perl atau alat yang dapat membagi file (baik .gz atau inner .txt) menjadi file baris 3x 40mn . yaitu menyebutnya seperti:

    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

Mungkin melakukan serangkaian solusi ini atau apakah gunzip -c membutuhkan ruang yang cukup untuk seluruh file untuk di-unzip (yaitu masalah asli): gunzip -c hugefile.txt.gz | kepala 4000000

Catatan: Saya tidak bisa mendapatkan disk tambahan.

Terima kasih!

toop
sumber
1
Apakah Anda ingin file yang dihasilkan di-gzip lagi?
Anda dapat menggunakan gunzip di ipe. Sisanya dapat dilakukan dengan kepala dan ekor
Ingo
@Tichodroma - tidak, saya tidak butuh mereka di-zz lagi. Tapi saya tidak bisa menyimpan semua file teks sekaligus. Jadi saya ingin mendapatkan split pertama, melakukan hal-hal dengan itu, lalu hapus split pertama, dan kemudian mendapatkan split.etc 2 akhirnya menghapus gz asli
toop
1
@toop: Terima kasih atas klarifikasi. Perhatikan bahwa umumnya lebih baik mengedit pertanyaan Anda jika Anda ingin menjelaskannya, daripada memasukkannya ke dalam komentar; dengan begitu semua orang akan melihatnya.
sleske
Jawaban yang diterima baik jika Anda hanya menginginkan sebagian kecil dari potongan, dan tidak mengetahuinya terlebih dahulu. Jika Anda ingin membuat semua chunks sekaligus, solusi berdasarkan split akan jauh lebih cepat, O (N) daripada O (N²).
b0fh

Jawaban:

11

Cara melakukan ini terbaik tergantung pada apa yang Anda inginkan:

  • Apakah Anda ingin mengekstrak satu bagian dari file besar?
  • Atau Anda ingin membuat semua bagian sekaligus?

Jika Anda ingin satu bagian file , ide Anda digunakan gunzipdan headbenar. Kamu bisa memakai:

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

Itu akan menampilkan 4000000 baris pertama pada out standar - Anda mungkin ingin menambahkan pipa lain untuk benar-benar melakukan sesuatu dengan data.

Untuk mendapatkan bagian lain, Anda akan menggunakan kombinasi headdan tail, seperti:

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

untuk mendapatkan blok kedua.

Mungkin melakukan serangkaian solusi ini atau apakah gunzip -c membutuhkan cukup ruang untuk seluruh file untuk di-unzip

Tidak, gunzip -ctidak memerlukan ruang disk apa pun - ia melakukan segalanya dalam memori, lalu mengalirkannya ke stdout.


Jika Anda ingin membuat semua bagian dalam sekali jalan , lebih efisien untuk membuat semuanya dengan satu perintah, karena file input hanya dibaca sekali. Salah satu solusi yang baik adalah menggunakan split; lihat jawaban jim mcnamara untuk lebih jelasnya.

sleske
sumber
1
Dari tampilan kinerja: apakah gzip sebenarnya meng-unzip seluruh file? Atau mungkinkah "secara ajaib" tahu bahwa hanya 4 juta baris yang diperlukan?
Alois Mahdal
3
@AloisMahdal: Sebenarnya, itu akan menjadi pertanyaan terpisah yang bagus :-). Versi singkat: gziptidak tahu tentang batas (yang berasal dari proses yang berbeda). Jika headdigunakan, headakan keluar ketika sudah cukup menerima, dan ini akan menyebar ke gzip(melalui SIGPIPE, lihat Wikipedia). Karena tailini tidak mungkin, jadi ya, gzipakan mendekompresi semuanya.
sleske
Tetapi jika Anda tertarik, Anda harus benar-benar menanyakan hal ini sebagai pertanyaan terpisah.
sleske
20

pipa untuk dibagi menggunakan gunzip -c atau zcat untuk membuka file

gunzip -c bigfile.gz | split -l 400000

Tambahkan spesifikasi output ke perintah split.

jim mcnamara
sumber
3
Ini secara besar-besaran lebih efisien daripada jawaban yang diterima, kecuali Anda hanya membutuhkan sebagian kecil dari potongan yang terpecah. Harap menang.
b0fh
1
@ b0fh: Ya, Anda benar. Diperbaharui, dan direferensikan dalam jawaban saya :-).
sleske
Jawaban terbaik pasti.
Stephen Blum
apa spesifikasi output sehingga outputnya adalah file .gz sendiri?
Quetzalcoatl
7

Saat Anda mengerjakan aliran (tidak dapat mundur), Anda ingin menggunakan bentuk '+ N' ekor untuk mendapatkan garis mulai dari garis N dan seterusnya.

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
zgpmax
sumber
4

Saya akan mempertimbangkan menggunakan split .

pisahkan file menjadi beberapa bagian

Michael Krelin - hacker
sumber
3

Pisahkan file .gz menjadi file .gz:

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

Saya pikir ini yang diinginkan OP, karena dia tidak punya banyak ruang.

siulkilulki
sumber
2

Berikut ini adalah skrip python untuk membuka kumpulan file globbed dari direktori, gunzip jika perlu, dan bacalah secara baris demi baris. Ini hanya menggunakan ruang yang diperlukan dalam memori untuk memegang nama file, dan baris saat ini, ditambah sedikit overhead.

#!/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)

Perintah baris cetak akan mengirim setiap baris ke std, sehingga Anda dapat mengarahkan ulang ke file. Atau, jika Anda memberi tahu kami apa yang ingin Anda lakukan dengan baris, saya dapat menambahkannya ke skrip python dan Anda tidak perlu meninggalkan potongan file yang tergeletak di sekitar.

Spencer Rathbun
sumber
2

Berikut adalah program perl yang dapat digunakan untuk membaca stdin, dan membagi baris, memipakan setiap rumpun ke perintah terpisah yang dapat menggunakan variabel shell $ SPLIT untuk merutekannya ke tujuan yang berbeda. Untuk kasus Anda, itu akan diminta

zcat hugefile.txt.gz | perl xsplit.pl 40000000 'cat > tmp$SPLIT.txt; do_something tmp$SPLIT.txt; rm tmp$SPLIT.txt'

Maaf pemrosesan command-line agak kotor tetapi Anda mendapatkan ide.

#!/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;
Liudvikas Bukys
sumber