Làm thế nào để phân chia hiệu quả một tệp văn bản lớn khi chia tách các bản ghi đa dòng?


9

Tôi có một tệp văn bản lớn (~ 50Gb khi gz'ed). Các tập tin có chứa 4*Ndòng hoặc Nhồ sơ; đó là mỗi bản ghi gồm 4 dòng. Tôi muốn chia tệp này thành 4 tệp nhỏ hơn, mỗi tệp có kích thước khoảng 25% tệp đầu vào. Làm thế nào tôi có thể tách các tập tin tại ranh giới hồ sơ?

Một cách tiếp cận ngây thơ sẽ là zcat file | wc -llấy số đếm dòng, chia số đó cho 4 và sau đó sử dụng split -l <number> file. Tuy nhiên, điều này đi qua tập tin hai lần và việc đếm dòng cực kỳ chậm (36 phút). Có cách nào tốt hơn?

Điều này đến gần nhưng không phải là những gì tôi đang tìm kiếm. Câu trả lời được chấp nhận cũng làm một số dòng.

BIÊN TẬP:

Các tập tin chứa dữ liệu tuần tự ở định dạng fastq. Hai bản ghi trông như thế này (ẩn danh):

@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

Mỗi dòng đầu tiên của bản ghi bắt đầu bằng a @.

EDIT2:

zcat file > /dev/null mất 31 phút.

EDIT3: Chỉ dòng đầu tiên bắt đầu bằng @. Không ai trong số những người khác sẽ bao giờ. Xem ở đây . Hồ sơ cần được giữ theo thứ tự. Không thể thêm bất cứ điều gì vào tập tin kết quả.


Mất bao lâu zcat file > /dev/null?
choroba

Bạn có thể cung cấp một mẫu nhỏ của tập tin trong câu hỏi?
FloHimelf

Bạn nói rằng mọi bản ghi bắt đầu bằng @và cũng có 4 dòng trên mỗi bản ghi. Là cả hai tuyệt đối? - và dòng 2,3,4 có thể bắt đầu bằng @? và có bất kỳ tiêu đề không ghi của dòng chân trang trong tệp không?
Peter.O

1
Bạn đang tìm kiếm một giải pháp xử lý đầu vào nén và / hoặc tạo đầu ra nén? Bạn đang tìm kiếm bốn tập tin nén có kích thước bằng nhau?
Stephen Kitt

Câu trả lời:


4

Tôi không nghĩ bạn có thể làm điều này - không đáng tin cậy, và không phải theo cách bạn yêu cầu. Vấn đề là, tỷ lệ nén của kho lưu trữ có thể sẽ không được phân bổ đều từ đầu đến đuôi - thuật toán nén sẽ áp dụng tốt hơn cho một số phần so với các phần khác. Đó chỉ là cách nó hoạt động. Và do đó, bạn không thể phân chia sự phân chia của mình theo kích thước của tệp nén.

Hơn nữa, gzipkhông hỗ trợ lưu trữ kích thước gốc của các tệp nén có kích thước lớn hơn 4gbs - nó không thể xử lý được. Và vì vậy, bạn không thể truy vấn kho lưu trữ để có được kích thước đáng tin cậy - bởi vì nó sẽ đánh lừa bạn.

Điều 4 dòng - thực sự khá dễ dàng. Điều gồm 4 tệp - Tôi chỉ không biết làm thế nào bạn có thể làm điều đó một cách đáng tin cậy và với một bản phân phối đồng đều mà không cần trích xuất kho lưu trữ để có được kích thước không nén của nó. Tôi không nghĩ bạn có thể bởi vì tôi đã cố gắng.

Tuy nhiên, những gì bạn có thể làm là đặt kích thước tối đa cho các tệp đầu ra được phân chia và đảm bảo rằng các tệp đó luôn bị phá vỡ ở các rào cản kỷ lục. Điều đó bạn có thể dễ dàng làm. Đây là một đoạn script nhỏ sẽ thực hiện điều đó bằng cách trích xuất gzipkho lưu trữ và dẫn nội dung qua một vài ddbộ đệm ống rõ ràng với các count=$rptđối số cụ thể , trước khi chuyển qua đó lz4để giải nén / giải nén từng tệp một cách nhanh chóng. Tôi cũng đã ném vào một vài teethủ thuật đường ống nhỏ để in bốn dòng cuối cùng cho mỗi phân đoạn để stderr là tốt.

(       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
)

Điều đó sẽ chỉ tiếp tục cho đến khi nó đã xử lý tất cả các đầu vào. Nó không cố gắng phân tách nó theo một tỷ lệ phần trăm - mà nó không thể có được - nhưng thay vào đó, nó chia nó theo số byte thô tối đa trên mỗi lần phân chia. Và dù sao, một vấn đề lớn của bạn là bạn không thể có được kích thước đáng tin cậy trong kho lưu trữ của mình vì nó quá lớn - dù bạn có làm gì đi nữa, đừng làm điều đó một lần nữa - hãy chia nhỏ hơn 4gbs một vòng , có lẽ. Kịch bản nhỏ này, ít nhất, cho phép bạn làm điều này mà không cần phải ghi một byte không nén vào đĩa.

Đây là một phiên bản ngắn hơn được loại bỏ các yếu tố cần thiết - nó không thêm vào tất cả các nội dung báo cáo:

(       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

Nó thực hiện tất cả những điều tương tự như lần đầu tiên, chủ yếu, nó chỉ không có nhiều điều để nói về nó. Ngoài ra, có thể ít lộn xộn hơn để có thể dễ dàng nhìn thấy những gì đang xảy ra, có thể.

Các IFS=điều là chỉ để xử lý một readdòng cho mỗi lần lặp. Chúng tôi readmột vì chúng tôi cần vòng lặp của chúng tôi kết thúc khi đầu vào kết thúc. Điều này phụ thuộc vào kích thước bản ghi của bạn - mà, theo ví dụ của bạn, là 354 byte mỗi. Tôi đã tạo một gzipkho lưu trữ 4 + gb với một số dữ liệu ngẫu nhiên để kiểm tra nó.

Dữ liệu ngẫu nhiên có được theo cách này:

(       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

... nhưng có lẽ bạn không cần phải lo lắng nhiều về điều đó, vì bạn đã có sẵn dữ liệu. Quay lại giải pháp ...

Về cơ bản pigz- dường như giải nén nhanh hơn một chút zcat- loại bỏ luồng không nén và ddbộ đệm tạo ra các khối ghi có kích thước cụ thể ở bội số 354 byte. Các vòng lặp sẽ readmột $linelần mỗi lần lặp để kiểm tra rằng đầu vào vẫn đến, mà nó sẽ printfsau đó printftại lz4trước khác ddđược gọi là để đọc các khối có kích thước đặc biệt tại một bội số của 354-byte - để đồng bộ hóa với đệm ddquá trình - trong suốt thời gian. Sẽ có một lần đọc ngắn cho mỗi lần lặp vì lần đầu tiên read $line- nhưng điều đó không thành vấn đề, bởi vì chúng tôi đang in nó tại lz4- quy trình thu thập của chúng tôi - dù sao đi nữa.

Tôi đã thiết lập nó để mỗi lần lặp sẽ đọc khoảng 1gb dữ liệu không nén và nén luồng đó vào khoảng 650Mb hoặc hơn. lz4nhanh hơn nhiều so với bất kỳ phương pháp nén hữu ích nào khác - đó là lý do tôi chọn nó ở đây vì tôi không muốn chờ đợi. xzMặc dù vậy, sẽ làm một công việc tốt hơn nhiều ở việc nén thực tế. lz4Mặc dù vậy, một điều về nó là nó thường có thể giải nén ở gần tốc độ RAM - điều đó có nghĩa là rất nhiều lần bạn có thể giải nén một lz4kho lưu trữ nhanh như bạn có thể ghi nó vào bộ nhớ.

Cái lớn làm một vài báo cáo mỗi lần lặp. Cả hai vòng lặp sẽ in ddbáo cáo về số lượng byte thô được truyền và tốc độ, v.v. Vòng lặp lớn cũng sẽ in 4 dòng đầu vào cuối cùng cho mỗi chu kỳ và số byte tương tự, theo sau là một lsthư mục mà tôi viết lz4tài liệu lưu trữ. Dưới đây là một vài vòng đầu ra:

/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 -lchỉ hoạt động đối với <2GiB tệp không nén IIRC (dù sao nhỏ hơn tệp OP).
Stéphane Chazelas

@ StéphaneChazelas - chết tiệt. Đó là cách duy nhất tôi có thể nghĩ đến để có được kích thước không nén. Không có điều đó, điều này hoàn toàn không hoạt động.
mikeerv

4

Việc chia nhỏ tệp trên ranh giới bản ghi thực sự rất dễ dàng, không cần bất kỳ mã nào:

zcat your_file.gz | split -l 10000 - output_name_

Điều này sẽ tạo ra các tệp đầu ra gồm 10000 dòng mỗi dòng, với tên output_name_aa, output_name_ab, output_name_ac, ... Với đầu vào lớn như của bạn, điều này sẽ cung cấp cho bạn rất nhiều tệp đầu ra. Thay thế 10000bằng bất kỳ bội số nào trong bốn và bạn có thể làm cho các tệp đầu ra lớn hoặc nhỏ tùy thích. Thật không may, như với các câu trả lời khác, không có cách nào tốt để đảm bảo bạn sẽ có được số lượng (xấp xỉ) kích thước tệp đầu ra mong muốn mà không cần đoán trước về đầu vào. (Hoặc thực sự dẫn toàn bộ thông qua wc.) Nếu hồ sơ của bạn có kích thước xấp xỉ bằng nhau (hoặc ít nhất, được phân bổ đều), bạn có thể thử đưa ra một ước tính như thế này:

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

Điều đó sẽ cho bạn biết kích thước nén của 1000 bản ghi đầu tiên của tệp của bạn. Dựa vào đó, bạn có thể đưa ra ước tính có bao nhiêu hàng bạn muốn trong mỗi tệp để kết thúc với bốn tệp. .

Chỉnh sửa: Đây là một mẹo nữa, giả sử bạn muốn các tệp đầu ra được nén:

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

Điều này sẽ tạo ra rất nhiều tệp nhỏ hơn và sau đó nhanh chóng đưa chúng trở lại với nhau. . Không gian trống của đĩa. Thay thế gzip / zcat cho pigz / unpigz nếu bạn không có chúng. Tôi đã nghe nói rằng một số thư viện phần mềm (Java?) Không thể xử lý các tệp gzip được nối theo cách này, nhưng cho đến nay tôi chưa gặp vấn đề gì với nó. (pigz sử dụng thủ thuật tương tự để song song nén.)


Nếu bạn đã cài đặt pigz, bạn có thể tăng tốc mọi thứ lên một chút bằng cách thay thế 'pigz -cd' cho 'zcat'.
vẽ

2
Ah, tôi chỉ nhận thấy rằng bạn đã đề cập đến sự phân chia trong câu hỏi. Nhưng thực sự, bất kỳ giải pháp nào cũng sẽ được thực hiện về điều tương tự như phân chia dưới mui xe. Phần khó là tìm ra có bao nhiêu hàng bạn cần đặt trong mỗi tệp.
vẽ

3

Từ những gì tôi thu thập được sau khi kiểm tra google-sphere và kiểm tra thêm .gztệp 7.8 GiB , có vẻ như siêu dữ liệu của kích thước tệp không nén ban đầu là không chính xác (nghĩa là sai ) đối với các .gztệp lớn (lớn hơn 4GiB (có thể là 2GiB đối với một số tệp phiên bản của gzip).
Re. thử nghiệm siêu dữ liệu của gzip của tôi:

* 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

Vì vậy, có vẻ như không thể xác định kích thước không nén mà không thực sự giải nén nó (điều này hơi thô, để nói rằng ít nhất!)

Dù sao đi nữa, đây là một cách để phân chia một tệp không nén tại các ranh giới bản ghi, trong đó mỗi bản ghi chứa 4 dòng .

Nó sử dụng kích thước của tệp theo byte (thông qua stat) và awkđếm byte (không phải ký tự). Có hay không kết thúc dòng là LF| CR| CRLF, tập lệnh này xử lý độ dài kết thúc dòng thông qua biến dựng sẵn 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

Dưới đây là thử nghiệm tôi đã sử dụng để kiểm tra xem số lượng dòng của mỗi tệp là 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

Đầu ra thử nghiệm:

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 được tạo bởi:

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

2

Đây không phải là câu trả lời nghiêm túc! Tôi chỉ đang đùa giỡn flexvà điều này rất có thể sẽ không hoạt động trên một tệp đầu vào với ~ 50Gb (nếu có, trên dữ liệu đầu vào lớn hơn tệp thử nghiệm của tôi):

Điều này hoạt động với tôi trên tệp input.txt ~ 1Gb :

Cho bộ chiaflex tệp đầu vào.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;
    }
}

tạo lex.yy.c và biên dịch nó thành splitternhị phân với:

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

Sử dụng:

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

Thời gian chạy cho 1Gb input.txt :

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

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

Việc lexing thực tế ở đây rất đơn giản, bạn thực sự không được hưởng lợi từ lex. Chỉ cần gọi getc(stream)và áp dụng một số logic đơn giản. Ngoài ra, bạn có biết rằng. (dấu chấm) ký tự regex trong (f) lex khớp với bất kỳ ký tự nào ngoại trừ dòng mới , phải không? Trong khi những hồ sơ này là nhiều dòng.
Kaz

@Kaz Mặc dù các tuyên bố của bạn nói chung là đúng, nhưng điều này thực sự hoạt động với dữ liệu được cung cấp trong Q.
FloHimelf

Chỉ vô tình, bởi vì có một quy tắc mặc định khi không có gì phù hợp: tiêu thụ một ký tự và in nó ra đầu ra! Trong các từ khóa khác, bạn có thể thực hiện chuyển đổi tệp của mình chỉ bằng một quy tắc nhận dạng @ký tự và sau đó để quy tắc mặc định sao chép dữ liệu. Bây giờ bạn có quy tắc sao chép một phần dữ liệu dưới dạng một mã thông báo lớn và sau đó quy tắc mặc định sẽ nhận được một ký tự dòng thứ hai tại một thời điểm.
Kaz

Cảm ơn đã làm rõ. Tôi tự hỏi, làm thế nào bạn sẽ giải quyết nhiệm vụ này với txr.
FloHimelf

Tôi không chắc chắn rằng tôi sẽ làm vì nhiệm vụ là làm một việc rất đơn giản với một lượng lớn dữ liệu, càng nhanh càng tốt.
Kaz

1

Đây là một giải pháp trong Python giúp người ta vượt qua tệp đầu vào ghi các tệp đầu ra khi nó đi cùng.

Một tính năng về việc sử dụng wc -llà bạn giả sử mỗi bản ghi ở đây có cùng kích thước. Điều đó có thể đúng ở đây, nhưng giải pháp dưới đây hoạt động ngay cả khi đó không phải là trường hợp. Về cơ bản, nó là sử dụng wc -choặc số byte trong tệp. Trong Python, điều này được thực hiện thông qua os.stat ()

Vì vậy, đây là cách chương trình hoạt động. Trước tiên, chúng tôi tính toán các điểm phân chia lý tưởng là độ lệch byte. Sau đó, bạn đọc các dòng ghi tệp đầu vào vào tệp đầu ra thích hợp. Khi bạn thấy rằng bạn đã vượt quá điểm phân chia tiếp theo tối ưu bạn đang ở ranh giới bản ghi, hãy đóng tệp đầu ra cuối cùng và mở tiếp theo.

Chương trình này là tối ưu theo nghĩa này, nó đọc các byte của tệp đầu vào một lần; Lấy kích thước tệp không yêu cầu đọc dữ liệu tệp. Lưu trữ cần thiết tỷ lệ thuận với kích thước của một dòng. Nhưng Python hoặc hệ thống có lẽ có bộ đệm tệp hợp lý để tăng tốc I / O.

Tôi đã thêm các tham số cho số lượng tệp cần chia và kích thước bản ghi trong trường hợp bạn muốn điều chỉnh giá trị này trong tương lai.

Và rõ ràng điều này có thể được dịch sang các ngôn ngữ lập trình khác là tốt.

Một điều khác, tôi không chắc chắn nếu Windows với crlf của nó xử lý đúng độ dài của dòng như trên các hệ thống Unix-y. Nếu len () bị tắt bởi một người ở đây, tôi hy vọng cách điều chỉnh chương trình rõ ràng.

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

Nó không tách ra ở một ranh giới kỷ lục. ví dụ. Việc phân chia tệp phụ đầu tiên xảy ra sau dòng thứ 3 với đầu vào nàyprintf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O

1

Người dùng FloHimelf có vẻ tò mò về một giải pháp TXR . Đây là một cái sử dụng TXR Lisp nhúng :

(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))

Ghi chú:

  1. Vì lý do tương tự - việc poplấy từng bộ dữ liệu từ danh sách bộ dữ liệu lười biếng là rất quan trọng, để danh sách lười được tiêu thụ. Chúng tôi không được giữ lại một tham chiếu đến đầu danh sách đó vì khi đó bộ nhớ sẽ tăng lên khi chúng tôi di chuyển qua tệp.

  2. (seek-stream fo 0 :from-current)là trường hợp không có seek-stream, điều này làm cho nó hữu ích bằng cách trả về vị trí hiện tại.

  3. Hiệu suất: không đề cập đến nó. Có thể sử dụng được, nhưng sẽ không mang bất kỳ danh hiệu nào về nhà.

  4. Vì chúng tôi chỉ thực hiện kiểm tra kích thước sau mỗi 1000 tuple, nên chúng tôi chỉ có thể tạo kích thước tuple 4000 dòng.


0

Nếu bạn không cần các tệp mới là các phần liền kề của tệp gốc, bạn có thể thực hiện việc này hoàn toàn sedtheo cách sau:

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

Việc -nngăn chặn nó in mỗi dòng và mỗi -etập lệnh về cơ bản là làm cùng một việc. 1~16phù hợp với dòng đầu tiên, và mỗi dòng thứ 16 sau. ,+3có nghĩa là khớp ba dòng tiếp theo sau mỗi dòng. w1.txtnói viết tất cả những dòng đó vào tập tin 1.txt. Điều này là lấy mỗi nhóm thứ 4 gồm 4 dòng và ghi nó vào một tệp, bắt đầu với nhóm 4 dòng đầu tiên. Ba lệnh còn lại làm điều tương tự, nhưng mỗi lệnh được chuyển tiếp 4 dòng và ghi vào một tệp khác.

Điều này sẽ phá vỡ khủng khiếp nếu tệp không khớp chính xác với đặc điểm kỹ thuật bạn đặt ra, nhưng nếu không thì nó sẽ hoạt động như bạn dự định. Tôi chưa định hình được nó, vì vậy tôi không biết nó sẽ hiệu quả đến mức nào, nhưng sedhiệu quả một cách hợp lý khi chỉnh sửa luồng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.