Bagaimana cara membagi file teks besar secara efisien tanpa membagi catatan multiline?

9

Saya punya file teks besar (~ 50Gb saat gz'ed). File tersebut berisi 4*Ngaris atau Ncatatan; yaitu setiap record terdiri dari 4 baris. Saya ingin membagi file ini menjadi 4 file lebih kecil yang masing-masing berukuran sekitar 25% dari file input. Bagaimana saya bisa membagi file pada batas catatan?

Pendekatan naif adalah zcat file | wc -luntuk mendapatkan jumlah baris, bagi angka itu dengan 4 dan kemudian gunakan split -l <number> file. Namun, ini melewati file dua kali dan line-counte sangat lambat (36 menit). Apakah ada cara yang lebih baik?

Ini mendekati tetapi bukan apa yang saya cari. Jawaban yang diterima juga menghitung jumlah baris.

EDIT:

File tersebut berisi urutan data dalam format fastq. Dua catatan terlihat seperti ini (dianonimkan):

@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

Baris pertama setiap record dimulai dengan a @.

EDIT2:

zcat file > /dev/null membutuhkan waktu 31 menit.

EDIT3 : Onlye baris pertama dimulai dengan @. Tak satu pun dari yang lain akan pernah. Lihat di sini . Catatan harus tetap tertib. Tidak apa-apa menambahkan sesuatu ke file yang dihasilkan.

Rolf
sumber
Berapa lama satu zcat file > /dev/nullwaktu?
choroba
Bisakah Anda memberikan sampel kecil file yang dimaksud?
FloHimself
Anda mengatakan setiap record dimulai dengan @dan juga bahwa ada 4 baris per record. Apakah keduanya mutlak? - dan bisakah garis 2,3,4 dimulai dengan @? dan apakah ada header non-record dari baris footer dalam file?
Peter.O
1
Apakah Anda mencari solusi yang menangani input terkompresi dan / atau menghasilkan output terkompresi? Apakah Anda mencari empat file terkompresi berukuran sama?
Stephen Kitt

Jawaban:

4

Saya tidak berpikir Anda bisa melakukan ini - tidak andal, dan bukan cara Anda bertanya. Masalahnya, rasio kompresi arsip mungkin tidak akan didistribusikan secara merata dari kepala ke ekor - algoritma kompresi akan berlaku lebih baik untuk beberapa bagian daripada yang lain. Begitulah cara kerjanya. Jadi Anda tidak dapat memfaktorkan pemisahan Anda pada ukuran file terkompresi.

Terlebih lagi, gziptidak mendukung penyimpanan ukuran asli file terkompresi yang berukuran lebih dari 4gb - tidak dapat menanganinya. Jadi Anda tidak dapat meminta arsip untuk mendapatkan ukuran yang andal - karena itu akan menipu Anda.

Masalah 4 baris - itu cukup mudah, sungguh. Masalah 4-file - Saya hanya tidak tahu bagaimana Anda bisa melakukannya dengan andal dan dengan distribusi yang merata tanpa terlebih dahulu mengekstraksi arsip untuk mendapatkan ukurannya yang tidak terkompresi. Saya tidak berpikir Anda bisa karena saya sudah mencoba.

Namun, apa yang dapat Anda lakukan, adalah mengatur ukuran maksimum untuk file output split, dan pastikan mereka selalu rusak pada batasan rekor. Itu bisa Anda lakukan dengan mudah. Berikut ini adalah skrip kecil yang akan melakukannya dengan mengekstraksi gziparsip, dan menyaring konten melalui beberapa ddpenyangga pipa eksplisit dengan count=$rptargumen tertentu , sebelum meneruskannya lz4untuk mendekompresi / mengkompres ulang setiap file dengan cepat. Saya juga melemparkan beberapa teetrik pipa kecil untuk mencetak empat baris terakhir untuk setiap segmen ke stderr juga.

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

Itu hanya akan terus berjalan sampai semua input ditangani. Itu tidak mencoba untuk membaginya dengan beberapa persentase - yang tidak bisa didapat - tetapi sebaliknya membaginya per jumlah byte mentah maksimum per split. Dan lagi pula, sebagian besar masalah Anda adalah bahwa Anda tidak bisa mendapatkan ukuran yang dapat diandalkan pada arsip Anda karena terlalu besar - apa pun yang Anda lakukan, jangan lakukan itu lagi - buat perpecahan kurang dari 4gb sepotong ini berputar , mungkin. Skrip kecil ini, setidaknya, memungkinkan Anda untuk melakukan ini tanpa harus menulis byte yang tidak dikompresi ke disk.

Berikut ini adalah versi yang lebih pendek yang dilucuti untuk hal yang penting - ini tidak menambahkan semua hal laporan:

(       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

Itu melakukan semua hal yang sama seperti yang pertama, kebanyakan, hanya saja tidak banyak bicara tentang itu. Juga, ada lebih sedikit kekacauan sehingga lebih mudah untuk melihat apa yang terjadi, mungkin.

The IFS=hal yang hanya menangani satu readbaris per iterasi. Kami readsatu karena kami membutuhkan loop kami untuk mengakhiri ketika input berakhir. Ini tergantung pada ukuran rekaman Anda - yang, per contoh Anda, adalah 354 byte per. Saya membuat gziparsip 4+ gb dengan beberapa data acak untuk mengujinya.

Data acak didapat seperti ini:

(       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

... tapi mungkin Anda tidak perlu terlalu khawatir tentang itu, karena Anda sudah memiliki data dan semuanya. Kembali ke solusinya ...

Pada dasarnya pigz- yang tampaknya melakukan dekompresi sedikit lebih cepat daripada yang dilakukan zcat- menyalurkan aliran yang tidak terkompresi, dan ddbuffer yang menghasilkan blok-blok tulis yang berukuran khusus pada kelipatan 354-byte. Loop akan readmenjadi $linesekali setiap iterasi untuk menguji bahwa masukan masih tiba, yang akan printfkemudian printfdi lz4sebelum lain dddipanggil untuk membaca blok berukuran khusus di kelipatan dari 354-byte - untuk sinkronisasi dengan penyangga ddproses - untuk durasi. Akan ada satu bacaan pendek per iterasi karena inisial read $line- tapi itu tidak masalah, karena kami mencetaknya di lz4- proses kolektor kami -.

Saya telah mengaturnya sehingga setiap iterasi akan membaca sekitar 1gb data yang tidak terkompresi dan kompres sela itu menjadi sekitar 650MB. lz4jauh lebih cepat daripada hampir semua metode kompresi berguna lainnya - itulah alasan saya memilihnya di sini karena saya tidak suka menunggu. xzakan melakukan pekerjaan yang jauh lebih baik di kompresi yang sebenarnya, mungkin. Namun, satu hal yang lz4sering terjadi adalah dekompres pada kecepatan mendekati RAM - yang berarti banyak kali Anda dapat mendekompres lz4arsip dengan cepat karena Anda tetap dapat menuliskannya ke dalam memori.

Yang besar melakukan beberapa laporan per iterasi. Kedua loop akan mencetak ddlaporan tentang jumlah byte mentah yang ditransfer dan kecepatan dan sebagainya. Loop besar juga akan mencetak 4 baris input terakhir per siklus, dan jumlah byte untuk yang sama, diikuti oleh lsdirektori yang saya tulis lz4arsipnya. Berikut adalah beberapa putaran output:

/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
mikeserv
sumber
gzip -lhanya berfungsi untuk <Rp2GiB file terkompresi IIRC (sesuatu yang lebih kecil dari file OP).
Stéphane Chazelas
@ StéphaneChazelas - sial. Itulah satu-satunya cara saya bisa mendapatkan ukuran yang tidak terkompresi. Tanpa itu, ini tidak akan berhasil sama sekali.
mikeserv
4

Memisahkan file pada batas rekaman sebenarnya sangat mudah, tanpa kode apa pun:

zcat your_file.gz | split -l 10000 - output_name_

Ini akan membuat file output masing-masing 10.000 baris, dengan nama output_name_aa, output_name_ab, output_name_ac, ... Dengan input sebesar milik Anda, ini akan memberi Anda banyak file output. Ganti 10000dengan kelipatan empat, dan Anda dapat membuat file output sebesar atau sekecil yang Anda suka. Sayangnya, seperti jawaban yang lain, tidak ada cara yang baik untuk menjamin Anda akan mendapatkan jumlah yang diinginkan (kira-kira) ukuran yang sama dari file output tanpa membuat beberapa tebakan tentang input. (Atau benar-benar menyalurkan semuanya wc.) Jika rekaman Anda berukuran kira-kira sama (atau setidaknya, didistribusikan secara kasar), Anda dapat mencoba membuat perkiraan seperti ini:

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

Itu akan memberi tahu Anda ukuran terkompresi dari 1000 catatan pertama file Anda. Berdasarkan itu, Anda mungkin dapat membuat perkiraan berapa banyak baris yang Anda inginkan di setiap file berakhir dengan empat file. (Jika Anda tidak ingin file kelima yang merosot tersisa, pastikan untuk menambah perkiraan Anda sedikit, atau bersiaplah untuk menempelkan file kelima ke ekor keempat.)

Sunting: Ini satu trik lagi, dengan asumsi Anda ingin file output terkompresi:

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

Ini akan membuat banyak file yang lebih kecil dan kemudian dengan cepat menyatukannya kembali. (Anda mungkin harus men-tweak parameter -l tergantung pada berapa lama baris dalam file Anda.) Ini mengasumsikan Anda memiliki versi relatif baru GNU coreutils (untuk split --filter) dan sekitar 130% dari ukuran file input Anda di ruang disk kosong. Gzip / zcat pengganti untuk pigz / unpigz jika Anda tidak memilikinya. Saya pernah mendengar bahwa beberapa pustaka perangkat lunak (Java?) Tidak dapat menangani file gzip yang disatukan dengan cara ini, tetapi sejauh ini saya tidak memiliki masalah dengan itu. (Pigz menggunakan trik yang sama untuk memparalelkan kompresi.)

Drew
sumber
Jika Anda telah menginstal pigz, Anda dapat mempercepat sedikit dengan mengganti 'pigz -cd' untuk 'zcat'.
Drew
2
Ah, saya baru tahu sekarang bahwa Anda sudah menyebutkan perpecahan dalam pertanyaan. Tapi sungguh, hampir semua solusi akan melakukan hal yang sama seperti split di bawah tenda. Bagian yang sulit adalah mencari tahu berapa banyak baris yang perlu Anda masukkan ke dalam setiap file.
Drew
3

Dari apa yang saya kumpulkan setelah memeriksa google-sphere, dan selanjutnya menguji .gzfile 7,8 GiB , tampaknya metadata ukuran file asli yang tidak terkompresi tidak akurat (mis. Salah ) untuk .gzfile besar (lebih besar dari 4Gb (mungkin 2Gb untuk beberapa versi dari gzip)
Re. pengujian metadata gzip saya:

* 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

Jadi sepertinya tidak mungkin untuk menentukan ukuran yang tidak terkompresi tanpa benar-benar mengompresnya (yang agak kasar, untuk sedikitnya!)

Bagaimanapun, berikut adalah cara untuk membagi file yang tidak terkompresi pada batas rekaman, di mana setiap catatan berisi 4 baris .

Ini menggunakan ukuran file dalam byte (via stat), dan dengan awkmenghitung byte (bukan karakter). Apakah akhir baris adalah LF| CR| CRLF, skrip ini menangani panjang akhir baris melalui variabel builtin 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

Di bawah ini adalah tes yang saya gunakan untuk memeriksa apakah jumlah baris setiap file 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

Hasil tes:

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 dihasilkan oleh:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile
Peter.O
sumber
2

Ini tidak dimaksudkan sebagai jawaban serius! Saya baru saja mempermainkan flexdan ini kemungkinan besar tidak akan bekerja pada file input dengan ~ 50GB (jika sama sekali, pada data input yang lebih besar daripada file pengujian saya):

Ini bekerja untuk saya pada file input.txt ~ 1Gb :

Diberikan flexfile input 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;
    }
}

menghasilkan lex.yy.c dan mengompilasinya ke splitterbiner dengan:

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

Pemakaian:

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

Waktu berjalan untuk 1Gb input.txt :

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

real    2m43.640s
user    0m48.100s
sys     0m1.084s
FloHimself
sumber
Lexing sebenarnya di sini sangat sederhana, Anda benar-benar tidak mendapat manfaat dari lex. Panggil saja getc(stream)dan terapkan beberapa logika sederhana. Juga, tahukah Anda bahwa. (dot) karakter regex di (f) lex cocok dengan karakter apa pun kecuali baris baru , bukan? Padahal catatan ini adalah multi-line.
Kaz
@Kaz Walaupun pernyataan Anda umumnya benar, ini benar-benar berfungsi dengan data yang disediakan di Q.
FloHimself
Hanya secara tidak sengaja, karena ada aturan default ketika tidak ada yang cocok: mengkonsumsi karakter dan mencetaknya ke output! Di rword lain, Anda bisa melakukan peralihan file hanya dengan aturan yang mengenali @karakter, dan kemudian membiarkan aturan default menyalin data. Sekarang Anda memiliki aturan Anda menyalin bagian dari data sebagai satu token besar, dan kemudian aturan default mendapatkan baris kedua satu karakter sekaligus.
Kaz
Terima kasih telah mengklarifikasi. Saya bertanya-tanya, bagaimana Anda akan menyelesaikan tugas ini dengan txr.
FloHimself
Saya tidak yakin akan melakukannya karena tugasnya adalah melakukan hal yang sangat sederhana dengan sejumlah besar data, secepat mungkin.
Kaz
1

Berikut adalah solusi dalam Python yang membuat satu melewati file input menulis file output seiring berjalannya waktu.

Fitur tentang menggunakan wc -ladalah Anda mengasumsikan setiap catatan di sini berukuran sama. Itu mungkin benar di sini, tetapi solusi di bawah ini berfungsi bahkan ketika itu tidak terjadi. Itu pada dasarnya menggunakan wc -catau jumlah byte dalam file. Dengan Python, ini dilakukan melalui os.stat ()

Jadi, inilah cara kerjanya. Kami pertama menghitung titik perpecahan yang ideal sebagai offset byte. Kemudian Anda membaca baris penulisan file input ke file output yang sesuai. Ketika Anda melihat bahwa Anda telah melampaui titik pemisahan berikutnya yang optimal dan Anda berada pada batas catatan, tutup file keluaran terakhir dan buka berikutnya.

Program ini optimal dalam hal ini, ia membaca byte dari file input sekali; Mendapatkan ukuran file tidak perlu membaca data file. Penyimpanan yang dibutuhkan sebanding dengan ukuran garis. Tapi Python atau sistem mungkin memiliki buffer file yang masuk akal untuk mempercepat I / O.

Saya telah menambahkan parameter untuk berapa banyak file untuk dipecah dan berapa ukuran rekaman jika Anda ingin menyesuaikan ini di masa depan.

Dan jelas ini bisa diterjemahkan ke bahasa pemrograman lain juga.

Satu hal lagi, saya tidak yakin apakah Windows dengan crlf-nya menangani panjang garis dengan benar seperti pada sistem Unix-y. Jika len () mati satu per satu di sini, saya harap jelas bagaimana menyesuaikan program.

#!/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))
berbatu
sumber
Itu tidak terpecah pada batas rekor. misalnya. printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Pemecahan
1

Pengguna FloHimself sepertinya penasaran dengan solusi TXR . Ini adalah salah satu yang menggunakan TXR Lisp yang disematkan :

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

Catatan:

  1. Untuk alasan yang sama, popsetiap tuple dari daftar malas tuple adalah penting, sehingga daftar malas dikonsumsi. Kita tidak boleh mempertahankan referensi ke awal daftar itu karena kemudian memori akan tumbuh ketika kita berjalan melalui file.

  2. (seek-stream fo 0 :from-current)adalah kasus no-op seek-stream, yang menjadikan dirinya berguna dengan mengembalikan posisi saat ini.

  3. Kinerja: jangan menyebutkannya. Dapat digunakan, tetapi tidak akan membawa pulang piala apa pun.

  4. Karena kami hanya melakukan pengecekan ukuran setiap 1000 tuple, kami hanya bisa membuat tuple ukuran 4000 baris.

Kaz
sumber
0

Jika Anda tidak membutuhkan file baru untuk menjadi potongan yang berdekatan dari file asli, Anda dapat melakukan ini sepenuhnya dengan sedcara berikut:

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

The -nberhenti dari mencetak setiap baris, dan masing-masing -escript pada dasarnya melakukan hal yang sama. 1~16cocok dengan baris pertama, dan setiap baris ke 16 sesudahnya. ,+3berarti mencocokkan tiga baris berikutnya setelah masing-masing. w1.txtmengatakan menulis semua baris itu ke file 1.txt. Ini mengambil setiap grup ke-4 dari 4 baris dan menulisnya ke file, dimulai dengan grup pertama dari 4 baris. Tiga perintah lainnya melakukan hal yang sama, tetapi masing-masing digeser ke depan sebanyak 4 baris, dan menulis ke file yang berbeda.

Ini akan rusak parah jika file tidak persis sesuai dengan spesifikasi yang Anda buat, tetapi jika tidak, itu akan berfungsi seperti yang Anda inginkan. Saya belum membuat profil, jadi saya tidak tahu seberapa efisiennya, tetapi sedcukup efisien saat mengedit aliran.

Erik
sumber