unix - แยกไฟล์. gz ขนาดใหญ่ทีละบรรทัด


16

ฉันแน่ใจว่าบางคนมีความต้องการด้านล่างสิ่งที่เป็นวิธีที่รวดเร็วในการแยกไฟล์. gz ขนาดใหญ่ทีละบรรทัด? ไฟล์ข้อความต้นแบบมี 120 ล้านแถว ฉันมีพื้นที่ว่างในดิสก์ไม่เพียงพอที่จะ gunzip ไฟล์ทั้งหมดในครั้งเดียวดังนั้นฉันจึงสงสัยว่ามีบางคนรู้ว่าสคริปต์หรือเครื่องมือทุบตี / 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
คุณต้องการให้ไฟล์ผลลัพธ์เป็น gziped อีกครั้งหรือไม่

คุณสามารถใช้ gunzip ใน ipe ส่วนที่เหลือสามารถทำได้ด้วยศีรษะและหาง
Ingo

@Tichodroma - ไม่ฉันไม่ต้องการให้ gziped อีกครั้ง แต่ฉันไม่สามารถจัดเก็บไฟล์ข้อความแยกทั้งหมดในครั้งเดียว ดังนั้นฉันต้องการได้รับการแยกแรกทำสิ่งที่มันแล้วลบแยกแรกและจากนั้นได้รับการแยก 2 ในที่สุดก็เอา 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; ดูคำตอบของ jim mcnamara สำหรับรายละเอียด


1
จากมุมมองประสิทธิภาพ: gzip จริงคลายซิปไฟล์ทั้งหมดหรือไม่ หรือว่า "วิเศษ" รู้ได้ว่าต้องการเพียง 4 ล้านบรรทัดเท่านั้น?
Alois Mahdal

3
@AloisMahdal: จริง ๆ แล้วมันจะเป็นคำถามแยกต่างหาก :-) เวอร์ชั่นย่อ: gzipไม่ทราบเกี่ยวกับขีด จำกัด (ซึ่งมาจากกระบวนการอื่น) หากheadมีการใช้งานheadจะออกเมื่อได้รับเพียงพอและสิ่งนี้จะเผยแพร่ไปยังgzip(ผ่าน SIGPIPE ดูที่ Wikipedia) สำหรับtailสิ่งนี้เป็นไปไม่ได้ดังนั้นใช่gzipจะขยายทุกอย่าง
sleske

แต่ถ้าคุณสนใจคุณควรถามคำถามนี้เป็นคำถามแยกต่างหาก
sleske

20

ไปป์ที่จะแยกใช้ gunzip -c หรือ zcat เพื่อเปิดไฟล์

gunzip -c bigfile.gz | split -l 400000

เพิ่มข้อมูลจำเพาะเอาต์พุตไปที่คำสั่ง split


3
สิ่งนี้มีประสิทธิภาพมากกว่าคำตอบที่ยอมรับอย่างหนาแน่นเว้นแต่คุณต้องการเพียงเศษเสี้ยวของส่วนที่แยกออกมาเท่านั้น กรุณาโหวต
b0fh

1
@ b0fh: ใช่คุณพูดถูก โหวตขึ้นและอ้างอิงในคำตอบของฉัน :-)
sleske

คำตอบที่ดีที่สุดแน่นอน
Stephen Blum

ข้อมูลจำเพาะของเอาต์พุตคืออะไรเพื่อให้เอาต์พุตเป็นไฟล์. 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'

ฉันคิดว่านี่เป็นสิ่งที่ OP ต้องการเพราะเขามีพื้นที่ไม่มาก


2

ต่อไปนี้เป็นสคริปต์ไพ ธ อนที่จะเปิดชุดไฟล์แบบวงกลมจากไดเรกทอรีบีบอัดมันหากจำเป็นและอ่านทีละบรรทัด มันใช้พื้นที่ที่จำเป็นในหน่วยความจำสำหรับเก็บชื่อไฟล์และบรรทัดปัจจุบันบวกกับค่าใช้จ่ายเล็กน้อย

#!/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 out ดังนั้นคุณสามารถเปลี่ยนเส้นทางไปยังไฟล์ อีกทางหนึ่งถ้าคุณแจ้งให้เราทราบว่าคุณต้องการทำอะไรกับบรรทัดฉันสามารถเพิ่มลงในสคริปต์ไพ ธ อนได้และคุณไม่จำเป็นต้องทิ้งไฟล์ไว้รอบ ๆ


2

ต่อไปนี้เป็นโปรแกรม perl ที่สามารถใช้อ่าน stdin และแบ่งบรรทัดแล้วไพพ์แต่ละ clump เป็นคำสั่งแยกต่างหากที่สามารถใช้ตัวแปรเชลล์ $ 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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.