여러 줄 레코드를 분할하지 않고 큰 텍스트 파일을 효율적으로 분할하는 방법은 무엇입니까?


9

큰 텍스트 파일이 있습니다 (gz 's ~ 50Gb). 파일은 4*N행이나 N레코드를 포함 합니다. 즉, 모든 레코드는 4 줄로 구성됩니다. 이 파일을 입력 파일의 대략 25 % 크기 인 4 개의 작은 파일로 나누고 싶습니다. 레코드 경계에서 파일을 어떻게 분할 할 수 있습니까?

순진한 접근 방식은 zcat file | wc -l줄 수를 가져 와서 그 수를 4로 나눈 다음을 사용하는 것 split -l <number> file입니다. 그러나 이것은 파일을 두 번 통과하고 행 수는 매우 느립니다 (36 분). 더 좋은 방법이 있습니까?

이것은 가깝지만 내가 찾고있는 것이 아닙니다. 허용 된 답변도 줄 수를 계산합니다.

편집하다:

파일은 fastq 형식의 시퀀싱 데이터를 포함합니다. 두 개의 레코드는 다음과 같습니다 (익명).

@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF

각 레코드의 첫 번째 줄은로 시작합니다 @.

EDIT2 :

zcat file > /dev/null 31 분이 걸립니다.

EDIT3 : 첫 번째 줄만으로 시작합니다 @. 다른 사람은 절대로 없습니다. 여기를 참조 하십시오 . 기록은 순서대로 유지되어야합니다. 결과 파일에 아무것도 추가하지 않아도됩니다.


싱글 zcat file > /dev/null은 얼마나 걸립 니까?
choroba

문제가되는 파일의 작은 샘플을 제공 할 수 있습니까?
FloHimself

모든 레코드가 시작되고 레코드 @당 4 줄이 있다고 말합니다 . 둘 다 절대인가요? -2,3,4 행은 @? 파일에 바닥 글 줄의 레코드가 아닌 헤더가 있습니까?
Peter.O

1
압축 입력을 처리하거나 압축 출력을 생성하는 솔루션을 찾고 있습니까? 동일한 크기의 압축 파일 4 개를 찾고 있습니까?
Stephen Kitt 2016 년

답변:


4

나는 당신이 이것을 할 수 있다고 생각하지 않습니다-확실하지 않고 당신이 요구하는 방식이 아닙니다. 문제는 아카이브의 압축 비율이 머리에서 꼬리까지 고르게 분포되지 않을 것입니다. 압축 알고리즘은 다른 부분보다 일부에 더 잘 적용됩니다. 그것이 작동하는 방식입니다. 따라서 압축 파일의 크기에 대한 분할을 고려할 수 없습니다.

무엇보다, gzip단지 크기 만 4GB보다 큰 압축 파일의 원래 크기를 저장하는 지원하지 않습니다 - 그것을 처리 할 수 없습니다. 따라서 신뢰할 수있는 크기를 얻기 위해 아카이브를 쿼리 할 수 ​​없습니다.

4 줄은 정말 쉽습니다. 4 파일-압축하지 않은 크기를 얻기 위해 먼저 아카이브를 추출하지 않고 안정적으로 고르게 배포 할 수있는 방법을 모르겠습니다. 내가 시도했기 때문에 당신이 할 수 있다고 생각하지 않습니다.

그러나 할 수있는 일은 분할 출력 파일의 최대 크기를 설정하고 파일이 항상 레코드 장벽에서 깨지는 지 확인하는 것입니다. 쉽게 할 수 있습니다. 다음은 gzip아카이브 를 추출하고 각 인수 를 압축 해제 / 재 압축 하기 위해 전달하기 전에 dd특정 count=$rpt인수를 사용 하여 명시 적 파이프 버퍼를 통해 내용을 파이핑하여 수행하는 작은 스크립트입니다 lz4. 또한 tee각 세그먼트의 마지막 네 줄을 stderr에 인쇄 하는 몇 가지 작은 파이프 트릭을 던졌습니다 .

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)

모든 입력을 처리 할 때까지 계속 진행됩니다. 그것은 그것을 얻을 수없는 백분율로 나누려고하지 않지만 대신 스플릿 당 최대 원시 바이트 수로 나눕니다. 어쨌든, 문제의 큰 부분은 아카이브가 너무 커서 아카이브에서 신뢰할 수있는 크기를 얻을 수 없다는 것입니다-당신이 무엇이든간에 다시하지 마십시오-4gbs 미만의 조각을 조각으로 만듭니다. , 아마도. 최소한이 작은 스크립트를 사용하면 압축되지 않은 바이트를 디스크에 쓰지 않고도이 작업을 수행 할 수 있습니다.

다음은 필수 사항을 제거한 짧은 버전입니다. 모든 보고서에 추가되지는 않습니다.

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz

그것은 첫 번째와 똑같은 일을합니다. 대부분 그것에 대해 할 말이 없습니다. 또한 혼란이 적기 때문에 진행 상황을 쉽게 볼 수 있습니다.

IFS=것은 단지 하나 개 처리하는 것입니다 read반복 당 라인. 우리는 read하나 때문에 우리는 때 입력이 종료 종료 우리의 루프가 필요합니다. 이것은 레코드 크기 에 따라 다릅니다. 예를 들어 354 바이트입니다. gzip테스트하기 위해 임의의 데이터로 4 + gb 아카이브를 만들었 습니다.

무작위 데이터는 다음과 같이 얻었습니다.

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

...하지만 이미 데이터와 모든 것을 가지고 있기 때문에 그것에 대해 너무 걱정할 필요가 없습니다. 솔루션으로 돌아 가기 ...

기본적으로 pigz는 압축보다 약간 빠른 속도로 zcat압축되지 않은 것처럼 압축되지 않은 스트림과 dd출력 블록으로 출력되는 버퍼를 354 바이트의 배수로 출력합니다. 루프 것 는 것이다 입력이 아직 도착하는지 테스트로 각 반복 번 이후 에 서로가 이전 블록을 읽어라고 354 바이트의 배수에서 구체적으로 크기 - 버퍼링과 동기화 프로세스 - 기간 동안. 초기 단계로 인해 반복마다 하나의 짧은 읽기가 발생 하지만 수집기 프로세스 에서 인쇄하기 때문에 중요하지 않습니다 .read$lineprintfprintflz4ddddread $linelz4

각 반복에서 약 1GB의 압축되지 않은 데이터를 읽고 인스 트림에서 약 650Mb 정도로 압축하도록 설정했습니다. lz4다른 유용한 압축 방법보다 훨씬 빠릅니다. 대기하지 않기 때문에 여기에서 선택한 이유입니다. xz그래도 실제 압축에서 훨씬 더 나은 작업을 수행 할 것입니다. lz4그러나 한 가지 중요한 점은 RAM 속도에 가깝게 압축을 풀 lz4수 있다는 것입니다. 즉, 어쨌든 메모리에 기록 할 수있는 것처럼 빨리 압축을 풀 수 있습니다 .

큰 것은 반복마다 몇 가지 보고서를 수행합니다. 두 루프 모두 dd전송 된 원시 바이트 수와 속도 등에 대한 보고서를 인쇄 합니다. 큰 루프는 사이클 당 마지막 4 줄의 입력과 동일한 바이트 수를 인쇄 한 다음 아카이브를 ls작성하는 디렉토리를 인쇄합니다 lz4. 다음은 두 단계의 출력입니다.

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2

gzip -l<2GiB 비 압축 파일 IIRC (어쨌든 OP 파일보다 작은 것)에 대해서만 작동합니다.
Stéphane Chazelas

@ StéphaneChazelas-젠장. 이것이 압축되지 않은 크기를 얻는 유일한 방법입니다. 그렇지 않으면 전혀 작동하지 않습니다.
mikeserv

4

레코드 경계에서 파일을 분할하는 것은 실제로 코드없이 매우 쉽습니다.

zcat your_file.gz | split -l 10000 - output_name_

출력 이름은 output_name_aa, output_name_ab, output_name_ac 등으로 각각 10000 줄의 출력 파일을 생성합니다. 입력이 최대 인 경우 많은 출력 파일이 제공됩니다. 100004의 배수로 바꾸면 원하는대로 출력 파일을 크거나 작게 만들 수 있습니다. 불행히도 다른 답변과 마찬가지로 입력에 대해 추측하지 않고 원하는 수의 동일한 크기의 출력 파일을 얻을 수있는 좋은 방법은 없습니다. (또는 실제로 전체를 통해 파이핑합니다 wc.) 레코드의 크기가 대략 같은 크기 (또는 적어도 거의 고르게 분포 된 경우)라면 다음과 같은 추정치를 시도해 볼 수 있습니다.

zcat your_file.gz | head -n4000 | gzip | wc -c

파일의 처음 1000 개 레코드의 압축 된 크기를 알려줍니다. 이를 바탕으로 각 파일에서 4 개의 파일로 끝나는 행 수를 추정 할 수 있습니다. (축소 된 다섯 번째 파일을 남기지 않으려면, 추정값을 약간 채우거나 다섯 번째 파일을 네 번째 꼬리에 붙일 준비를하십시오.)

편집 : 압축 된 출력 파일을 원한다고 가정하면 한 가지 더 트릭이 있습니다.

#!/bin/sh

base=$(basename $1 .gz)
unpigz -c $1 | split -l 100000 --filter='pigz -c > _$FILE.gz' - ${base}_

batch=$((`ls _*.gz | wc -l` / 4 + 1))
for i in `seq 1 4`; do
  files=`ls _*.gz | head -$batch`
  cat $files > ${base}_$i.gz && rm $files
done

이렇게하면 더 작은 파일이 많이 생성 된 다음 빠르게 다시 정리됩니다. (파일의 줄 길이에 따라 -l 매개 변수를 조정해야 할 수도 있습니다.) 상대적으로 최신 버전의 GNU coreutils (분할 필터) 및 입력 파일 크기의 약 130 %가 있다고 가정합니다. 디스크 여유 공간. gzip / zcat을 pigz / unpigz로 대체하십시오 (없는 경우). 일부 소프트웨어 라이브러리 (Java?)가 이런 방식으로 연결된 gzip 파일을 처리 할 수 ​​없다고 들었지만 지금까지 아무런 문제가 없었습니다. (pigz는 동일한 트릭을 사용하여 압축을 병렬화합니다.)


pigz를 설치 한 경우 'zcat'대신 'pigz -cd'를 사용하여 속도를 조금 높일 수 있습니다.
Drew

2
아, 방금 당신이 이미 그 질문에 대해 언급했음을 알았습니다. 그러나 실제로는 거의 모든 솔루션이 후드에서 분리 된 것과 똑같은 일을 할 것입니다. 어려운 부분은 각 파일에 얼마나 많은 행을 넣어야하는지 알아내는 것입니다.
Drew

3

google-sphere를 확인한 후 7.8 GiB .gz파일을 추가로 테스트 한 후 수집 한 내용 에서 원래 압축되지 않은 파일 크기의 메타 데이터는 큰 파일 (4GiB 이상 (일부 경우 2GiB 이상 )의 경우 정확하지 않습니다 (예 : 잘못된 ) .gz버전 gzip.)
다시 GZIP의 메타 데이터의 내 테스트. :

* The compressed.gz file is  7.8 GiB ( 8353115038 bytes) 
* The uncompressed  file is 18.1 GiB (19436487168 bytes)
* The metadata says file is  2.1 GiB ( 2256623616 bytes) uncompressed

따라서 실제로 압축을 풀지 않고 압축되지 않은 크기를 결정하는 것은 불가능한 것 같습니다 (최소한 말하면 약간 거칠습니다!)

어쨌든, 압축되지 않은 파일을 레코드 경계에서 분할하는 방법이 있습니다 . 각 레코드에는 4 개의 행이 있습니다.

파일 크기를 바이트 단위 (via stat)로 awk계산하고 바이트 수 (문자 아님)를 사용합니다. 줄 끝이 있는지 여부 LF| CR| CRLF이 스크립트는 내장 변수를 통해 줄 끝 길이를 처리합니다 RT).

LC_ALL=C gawk 'BEGIN{"stat -c %s "ARGV[1] | getline inSize
                      segSiz=int(inSize/4)+((inSize%4)==0?0:1)
                      ouSplit=segSiz; segNb=0 }
               { lnb++; bytCt+=(length+length(RT))
                 print $0 > ARGV[1]"."segNb
                 if( lnb!=4 ) next
                 lnb=0
                 if( bytCt>=ouSplit ){ segNb++; ouSplit+=segSiz }
               }' myfile

아래는 각 파일의 줄 수가 다음과 같은지 확인하는 데 사용한 테스트입니다. mod 4 == 0

for i in myfile  myfile.{0..3}; do
    lc=$(<"$i" wc -l)
    printf '%s\t%s\t' "$i" $lc; 
    (( $(echo $lc"%4" | bc) )) && echo "Error: mod 4 remainder !" || echo 'mod 4 ok'  
done | column -ts$'\t' ;echo

테스트 출력 :

myfile    1827904  mod 4 ok
myfile.0  456976   mod 4 ok
myfile.1  456976   mod 4 ok
myfile.2  456976   mod 4 ok
myfile.3  456976   mod 4 ok

myfile 에 의해 생성되었습니다 :

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile

2

이것은 진지한 대답이 아닙니다! 나는 그냥 flex놀았는데 ~ 50Gb 인 입력 파일 (아직 테스트 파일보다 큰 입력 데이터)에서는 작동하지 않을 것입니다.

~ 1Gb 파일 input.txt 에서 작동합니다 .

flex입력 파일 splitter.l이 주어지면 :

%{
#include <stdio.h>
extern FILE* yyin;
extern FILE* yyout;

int input_size = 0;

int part_num;
int part_num_max;
char **part_names;
%}

%%
@.+ {
        if (ftell(yyout) >= input_size / part_num_max) {
            fclose(yyout);
            if ((yyout = fopen(part_names[++part_num], "w")) == 0) {
                exit(1);
            }
        }
        fprintf(yyout, "%s", yytext);
    }
%%

int main(int argc, char *argv[]) {

    if (argc < 2) {
        return 1;
    } else if ((yyin = fopen(argv[1], "r")) == 0) {
        return 1;
    } else if ((yyout = fopen(argv[2], "w")) == 0) {
        fclose(yyin);
        return 1;
    } else {

        fseek(yyin, 0L, SEEK_END);
        input_size = ftell(yyin);
        rewind(yyin);

        part_num = 0;
        part_num_max = argc - 2;
        part_names = argv + 2;

        yylex();

        fclose(yyin);
        fclose(yyout);
        return 0;
    }
}

lex.yy.c를 생성 하고 다음을 사용하여 splitter바이너리로 컴파일하십시오 .

$ flex splitter.l && gcc lex.yy.c -ll -o splitter

용법:

$ ./splitter input.txt output.part1 output.part2 output.part3 output.part4

1Gb input.txt의 실행 시간 :

$ time ./splitter input.txt output.part1 output.part2 output.part3 output.part4

real    2m43.640s
user    0m48.100s
sys     0m1.084s

여기 실제 렉싱은 너무 간단합니다. 렉스의 이점은 없습니다. getc(stream)간단한 논리를 호출 하고 적용하십시오. 또한. (f) lex의 정규 표현식 문자는 개행을 제외한 모든 문자와 일치합니다 . 이러한 레코드는 여러 줄로되어 있습니다.
Kaz

@Kaz 당신의 진술은 일반적으로 현명하지만, 이것은 실제로 Q에 제공된 데이터와 함께 작동합니다.
FloHimself

일치하지 않는 경우 기본 규칙이 있기 때문에 우연히 만 : 문자를 소비하고 출력으로 인쇄하십시오! 다른 말로하면 @문자 를 인식하는 규칙을 사용하여 파일 전환을 수행 한 다음 기본 규칙이 데이터를 복사하게 할 수 있습니다. 이제 규칙의 일부를 하나의 큰 토큰으로 복사 한 다음 기본 규칙이 한 번에 한 문자 씩 두 번째 줄을 가져옵니다.
Kaz

설명해 주셔서 감사합니다. 나는이 작업을 어떻게 해결할 수 있을지 궁금합니다 txr.
FloHimself 2016 년

작업이 가능한 한 많은 양의 데이터로 매우 간단한 작업을 수행하는 것이기 때문에 확실하지 않습니다.
Kaz

1

다음은 출력 파일을 작성하는 입력 파일을 한 번에 전달하는 Python 솔루션입니다.

사용에 대한 기능은 wc -l여기에서 각 레코드의 크기가 같다고 가정한다는 것입니다. 여기에는 해당 될 수 있지만, 그렇지 않은 경우에도 아래 솔루션이 작동합니다. wc -c파일에서 기본적으로 또는 바이트 수를 사용 하고 있습니다. 파이썬에서 이것은 os.stat () 를 통해 이루어집니다

프로그램이 작동하는 방식은 다음과 같습니다. 먼저 이상적인 분리 점을 바이트 오프셋으로 계산합니다. 그런 다음 해당 출력 파일에 쓰는 입력 파일의 행을 읽습니다. 당신은 당신이 최적의 다음 분리 점을 초과 한 것을 볼 때 는 가까운 마지막 출력 파일의 기록 경계하고 다음을 엽니 다.

프로그램은 이런 의미에서 최적이며 입력 파일의 바이트를 한 번 읽습니다. 파일 크기를 가져 오기 위해 파일 데이터를 읽을 필요는 없습니다. 필요한 저장 공간은 선의 크기에 비례합니다. 그러나 파이썬이나 시스템은 아마도 I / O 속도를 높이기 위해 합리적인 파일 버퍼를 가지고있을 것입니다.

분할 할 파일 수와 나중에이를 조정하려는 경우 레코드 크기에 대한 매개 변수를 추가했습니다.

그리고 이것은 분명히 다른 프로그래밍 언어로도 번역 될 수 있습니다.

다른 한 가지, crlf가있는 Windows가 Unix-y 시스템에서와 같이 줄 길이를 올바르게 처리하는지 확실하지 않습니다. len ()이 여기서 하나 떨어져 있으면 프로그램을 조정하는 방법이 분명하기를 바랍니다.

#!/usr/bin/env python
import os

# Adjust these
filename = 'file.txt'
rec_size = 4
file_splits = 4

size = os.stat(filename).st_size
splits = [(i+1)*size/file_splits for i in range(file_splits)]
with open(filename, 'r') as fd:
    linecount = 0
    i = 0 # File split number
    out = open('file%d.txt' % i, 'w')
    offset = 0  # byte offset of where we are in the file: 0..size
    r = 0 # where we are in the record: 0..rec_size-1
    for line in fd:
        linecount += 1
        r = (r+1) % rec_size
        if offset + len(line) > splits[i] and r == 1 :
            out.close()
            i += 1
            out = open('file%d.txt' % i, 'w')
        out.write(line)
        offset += len(line)
    out.close()
    print("file %s has %d lines" % (filename, linecount))

레코드 경계에서 분할되지 않습니다. 예. 첫 번째 서브 파일 분할은이 입력으로 3 번째 줄 이후에 발생합니다printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O

1

사용자 FloHimself는 TXR 솔루션 에 대해 궁금해 보였습니다 . 다음은 내장 TXR Lisp를 사용하는 것입니다 .

(defvar splits 4)
(defvar name "data")

(let* ((fi (open-file name "r"))                 ;; input stream
       (rc (tuples 4 (get-lines fi)))            ;; lazy list of 4-tuples
       (sz (/ (prop (stat name) :size) splits))  ;; split size
       (i 1)                                     ;; split enumerator
       (n 0)                                     ;; tuplecounter within split
       (no `@name.@i`)                           ;; output split file name
       (fo (open-file no "w")))                  ;; output stream
  (whilet ((r (pop rc)))  ;; pop each 4-tuple
    (put-lines r fo) ;; send 4-tuple into output file
    ;; if not on the last split, every 1000 tuples, check the output file
    ;; size with stat and switch to next split if necessary.
    (when (and (< i splits)
               (> (inc n) 1000)
               (>= (seek-stream fo 0 :from-current) sz))
      (close-stream fo)
      (set fo (open-file (set no `@name.@(inc i)`) "w")
           n 0)))
  (close-stream fo))

노트:

  1. 같은 이유로 pop게으른 목록에서 각 튜플을 ping하는 것이 중요하므로 게으른 목록이 사용됩니다. 우리는 파일의 행진에 따라 메모리가 커지므로 해당 목록의 시작에 대한 참조를 유지해서는 안됩니다.

  2. (seek-stream fo 0 :from-current)의 no-op 경우이며 seek-stream현재 위치를 반환하여 유용합니다.

  3. 성능 : 언급하지 마십시오. 사용할 수는 있지만 트로피를 가져 오지는 않습니다.

  4. 우리는 1000 튜플마다 크기 검사를 수행하기 때문에 튜플 크기를 4000 줄로 만들 수 있습니다.


0

새 파일이 원본 파일의 연속 청크 일 필요가없는 경우 sed다음과 같은 방법 으로이 작업을 완전히 수행 할 수 있습니다 .

sed -n -e '1~16,+3w1.txt' -e '5~16,+3w2.txt' -e '9~16,+3w3.txt' -e '13~16,+3w4.txt'

-n각 행을 인쇄에서 중지하고, 각 -e스크립트는 본질적으로 같은 일을하고있다. 1~16첫 번째 줄과 16 번째 줄마다 일치합니다. ,+3각 행 다음에 다음 세 줄을 일치시키는 것을 의미합니다. w1.txt그 줄을 모두 파일에 쓰라고 말합니다 1.txt. 이것은 4 줄의 4 번째 그룹마다 첫 번째 4 줄 그룹부터 시작하여 파일에 기록합니다. 다른 세 명령은 동일한 작업을 수행하지만 각각 4 행씩 앞으로 이동하여 다른 파일에 씁니다.

파일이 지정한 사양과 정확히 일치하지 않으면 끔찍하게 중단되지만 그렇지 않으면 의도 한대로 작동합니다. 프로파일 링하지 않았으므로 얼마나 효율적인지 알지 못하지만 sed스트림 편집에서 합리적으로 효율적입니다.

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