유닉스-거대한 .gz 파일을 한 줄씩 나누기


15

누군가가 아래의 요구를 가지고 있다고 확신합니다. 거대한 .gz 파일을 한 줄씩 나누는 빠른 방법은 무엇입니까? 기본 텍스트 파일에는 1 억 2 천만 개의 행이 있습니다. 한 번에 전체 파일을 압축 할 수있는 디스크 공간이 충분하지 않아서 누군가가 파일 (.gz 또는 내부 .txt)을 3x 4 천만 줄 파일로 분할 할 수있는 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-아니요 다시 압축하지 않아도됩니다. 그러나 모든 분할 텍스트 파일을 한 번에 저장할 수는 없습니다. 그래서 내가 첫 번째 분할을 얻을 그것으로 물건을 수행 한 후 첫 번째 분할을 삭제 한 다음 2 split.etc 마침내 원래 GZ을 제거 좀하고 싶습니다
toop

1
@toop : 설명해 주셔서 감사합니다. 의견을 제시하기보다는 명확하게 설명하려면 질문을 편집하는 것이 일반적으로 좋습니다. 그렇게하면 모두가 그것을 볼 수 있습니다.
sleske 2012 년

청크의 일부만 원하고 미리 알지 못하는 경우 허용되는 대답이 좋습니다. 한 번에 모든 청크를 생성하려면 분할 기반 솔루션이 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

두 번째 블록을 얻을 수 있습니다.

이러한 일련의 솔루션을 수행하거나 gunzip -c에서 전체 파일을 압축 해제하기에 충분한 공간이 필요합니다.

아니요, gunzip -c디스크 공간이 필요하지 않습니다. 메모리의 모든 작업을 수행 한 다음 stdout으로 스트리밍합니다.


한 번에 모든 부품 을 작성하려면 단일 명령으로 모든 부품 을 작성 하는 것이 더 효율적입니다. 입력 파일은 한 번만 읽기 때문입니다. 좋은 해결책 중 하나는 사용하는 것입니다 split. 자세한 내용은 jim mcnamara의 답변을 참조하십시오.


1
성능 관점에서 : gzip은 실제로 전체 파일을 압축 해제합니까? 아니면 4 백만 줄만 필요하다는 것을 "마술로"알 수 있습니까?
Alois Mahdal

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

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


2

다음은 디렉토리에서 globbed 파일 세트를 열고 필요한 경우 파일을 압축하여 한 줄씩 읽는 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 명령은 모든 행을 표준 출력으로 보내므로 파일로 리디렉션 할 수 있습니다. 또는 라인으로 원하는 것을 알려 주면 파이썬 스크립트에 추가 할 수 있으며 파일 덩어리를 남겨 둘 필요가 없습니다.


2

다음은 stdin을 읽고 라인을 분할하고 쉘 변수 $ 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;

2

.gz 파일을 .gz 파일로 직접 분할 :

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

공간이 많지 않기 때문에 이것이 OP가 원하는 것이라고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.