Saya punya file teks besar (~ 50Gb saat gz'ed). File tersebut berisi 4*N
garis atau N
catatan; 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 -l
untuk 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.
zcat file > /dev/null
waktu?@
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?Jawaban:
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,
gzip
tidak 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
gzip
arsip, dan menyaring konten melalui beberapadd
penyangga pipa eksplisit dengancount=$rpt
argumen tertentu , sebelum meneruskannyalz4
untuk mendekompresi / mengkompres ulang setiap file dengan cepat. Saya juga melemparkan beberapatee
trik pipa kecil untuk mencetak empat baris terakhir untuk setiap segmen ke stderr juga.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:
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 saturead
baris per iterasi. Kamiread
satu 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 membuatgzip
arsip 4+ gb dengan beberapa data acak untuk mengujinya.Data acak didapat seperti ini:
... 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 dilakukanzcat
- menyalurkan aliran yang tidak terkompresi, dandd
buffer yang menghasilkan blok-blok tulis yang berukuran khusus pada kelipatan 354-byte. Loop akanread
menjadi$line
sekali setiap iterasi untuk menguji bahwa masukan masih tiba, yang akanprintf
kemudianprintf
dilz4
sebelum laindd
dipanggil untuk membaca blok berukuran khusus di kelipatan dari 354-byte - untuk sinkronisasi dengan penyanggadd
proses - untuk durasi. Akan ada satu bacaan pendek per iterasi karena inisialread $line
- tapi itu tidak masalah, karena kami mencetaknya dilz4
- proses kolektor kami -.Saya telah mengaturnya sehingga setiap iterasi akan membaca sekitar 1gb data yang tidak terkompresi dan kompres sela itu menjadi sekitar 650MB.
lz4
jauh lebih cepat daripada hampir semua metode kompresi berguna lainnya - itulah alasan saya memilihnya di sini karena saya tidak suka menunggu.xz
akan melakukan pekerjaan yang jauh lebih baik di kompresi yang sebenarnya, mungkin. Namun, satu hal yanglz4
sering terjadi adalah dekompres pada kecepatan mendekati RAM - yang berarti banyak kali Anda dapat mendekompreslz4
arsip dengan cepat karena Anda tetap dapat menuliskannya ke dalam memori.Yang besar melakukan beberapa laporan per iterasi. Kedua loop akan mencetak
dd
laporan 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 olehls
direktori yang saya tulislz4
arsipnya. Berikut adalah beberapa putaran output:sumber
gzip -l
hanya berfungsi untuk <Rp2GiB file terkompresi IIRC (sesuatu yang lebih kecil dari file OP).Memisahkan file pada batas rekaman sebenarnya sangat mudah, tanpa kode apa pun:
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
10000
dengan 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 semuanyawc
.) Jika rekaman Anda berukuran kira-kira sama (atau setidaknya, didistribusikan secara kasar), Anda dapat mencoba membuat perkiraan seperti ini: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:
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.)
sumber
Dari apa yang saya kumpulkan setelah memeriksa google-sphere, dan selanjutnya menguji
.gz
file 7,8 GiB , tampaknya metadata ukuran file asli yang tidak terkompresi tidak akurat (mis. Salah ) untuk.gz
file besar (lebih besar dari 4Gb (mungkin 2Gb untuk beberapa versi darigzip
)Re. pengujian metadata gzip saya:
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 denganawk
menghitung byte (bukan karakter). Apakah akhir baris adalahLF
|CR
|CRLF
, skrip ini menangani panjang akhir baris melalui variabel builtinRT
).Di bawah ini adalah tes yang saya gunakan untuk memeriksa apakah jumlah baris setiap file
mod 4 == 0
Hasil tes:
myfile
dihasilkan oleh:sumber
Ini tidak dimaksudkan sebagai jawaban serius! Saya baru saja mempermainkanflex
dan 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
flex
file input splitter.l :menghasilkan lex.yy.c dan mengompilasinya ke
splitter
biner dengan:Pemakaian:
Waktu berjalan untuk 1Gb input.txt :
sumber
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.@
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.txr
.Berikut adalah solusi dalam Python yang membuat satu melewati file input menulis file output seiring berjalannya waktu.
Fitur tentang menggunakan
wc -l
adalah 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 menggunakanwc -c
atau 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.sumber
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Pengguna FloHimself sepertinya penasaran dengan solusi TXR . Ini adalah salah satu yang menggunakan TXR Lisp yang disematkan :
Catatan:
Untuk alasan yang sama,
pop
setiap 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.(seek-stream fo 0 :from-current)
adalah kasus no-opseek-stream
, yang menjadikan dirinya berguna dengan mengembalikan posisi saat ini.Kinerja: jangan menyebutkannya. Dapat digunakan, tetapi tidak akan membawa pulang piala apa pun.
Karena kami hanya melakukan pengecekan ukuran setiap 1000 tuple, kami hanya bisa membuat tuple ukuran 4000 baris.
sumber
Jika Anda tidak membutuhkan file baru untuk menjadi potongan yang berdekatan dari file asli, Anda dapat melakukan ini sepenuhnya dengan
sed
cara berikut:The
-n
berhenti dari mencetak setiap baris, dan masing-masing-e
script pada dasarnya melakukan hal yang sama.1~16
cocok dengan baris pertama, dan setiap baris ke 16 sesudahnya.,+3
berarti mencocokkan tiga baris berikutnya setelah masing-masing.w1.txt
mengatakan menulis semua baris itu ke file1.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
sed
cukup efisien saat mengedit aliran.sumber