Membagi file untuk setiap 10.000 angka (bukan baris)

8

Saya memiliki file yang terlihat seperti berikut:

chr19   61336212        +       0       0       CG      CGT    
chr19   61336213        -       0       0       CG      CGG    
chr19   61336218        +       0       0       CG      CGG    
chr19   61336219        -       0       0       CG      CGC    
chr19   61336268        +       0       0       CG      CGG    
chr19   61336269        -       0       0       CG      CGA    
chr19   61336402        +       0       0       CG      CGG    
chr19   61336403        -       0       0       CG      CGT    

Saya ingin membagi file ini untuk setiap interval 10.000 bidang ke-2 (BUKAN garis, tetapi interval angka). Jadi untuk file ini saya ingin memisahkan dari baris pertama (baris dengan 61336212) ke baris yang memiliki atau hingga 61346211 (61336212 + 9999), kemudian dari 61346212 hingga 61356211, dan seterusnya dan seterusnya. Seperti yang Anda lihat angka-angka di kolom 2 / kolom tidak 'diisi'.

Apakah ada cara untuk melakukan ini?

agathusia
sumber
Dalam contoh Anda, jika angka berikutnya setelah 61346211 adalah 61346220, katakanlah, apakah Anda mengharapkan file output kedua untuk mencakup kisaran mulai dari 61346212 atau 61346220?
Joe Lee-Moyet
kisaran kedua harus mencakup dari 61346212.
agathusia

Jawaban:

13
awk 'NR==1 {n=$2}
     {
       file = sprintf("file.%.4d", ($2-n)/10000)
       if (file != last_file) {
         close(last_file)
         last_file = file
       }
       print > file
     }'

Akan menulis ke file.0000, file.0001... (nomor yang int(($2-n)/10000)mana nadalah $2untuk baris pertama).

Perhatikan bahwa kami menutup file setelah kami berhenti menulis kepada mereka seperti sebaliknya, Anda akan mencapai batas jumlah file yang dibuka secara bersamaan setelah beberapa ratus file (GNU awkdapat mengatasi batas itu, tetapi kemudian kinerjanya menurun dengan cepat).

Kami mengasumsikan angka-angka itu selalu naik.

Stéphane Chazelas
sumber
3
dapatkah Anda menjelaskan apa yang terjadi?
Fiximan
Bisakah Anda jelaskan apa yang terjadi di sini? Juga seperti komentar di bawah ini apakah ada jauh untuk memiliki panjang nama file output menjadi konstan, seperti file.0000, file.0001 bukannya file.1 file.2 .. file.100 .. file..2320?
agathusia
1
@Fiximan, saya merasa saya tidak bisa menjelaskan lebih banyak tanpa memparafrasekan kode. Bagian apa yang Anda temukan tidak jelas?
Stéphane Chazelas
Yah, saya mengerti generasi nama file file = ..., tetapi bagaimana iterasi bekerja? Tidak ada bagian yang mengatakan n = n + 10000atau lower_boundary <= $2 < upper_boundarybagian. Secara umum keseluruhan if (file != last_file) { close(last_file) ; last_file = file }keluar dari liga saya
Fiximan
1
@Fixman, ya, itulah yang saya sebut parafrase if (file != last_file): jika file saat ini tidak sama dengan file sebelumnya, tutup file sebelumnya (jadi hanya buka satu file pada satu waktu (kita tidak perlu menyimpannya) semua terbuka seperti solusi lain lakukan))
Stéphane Chazelas
7

Meretas versi satu-liner. Mungkin lebih cocok untuk Code Golf daripada forum ini. Ini menghasilkan split1, split2, split3 dan seterusnya, sebagai nama file.

awk '{if($2>b+9999){a++;b=$2}print >"split" a}' file.txt

Untuk memiliki file output bernama split001, split002, split003, melibatkan tambahan ini sprintf:

awk '{if($2>b+9999){a++;b=$2}print >sprintf("split%03d",a)}' file.txt

Untuk menghindari masalah pelambatan gawk yang diidentifikasi oleh @ Stéphane Chazelas, gunakan perl:

perl -ne '(undef,$a)=split(/\s+/,$_);if($a>$b+9999){$c++;$b=$a}open(D,sprintf(">>ysplit%03d",$c));print D' <file.txt
steve
sumber
1
Untuk metode ini, apakah ada cara agar nama file menjadi lebih .. berturut-turut? Outputs ini split1 .... split100 ... split1000, tetapi sesuatu yang lebih dalam garis split00001 ... split 00100 .. split01000 ..?
agathusia
1
Tentu, sprintfsihir ekstra sekarang ditambahkan.
steve
Perhatikan bahwa jika input memiliki 0, 9999, 12000, 19999, 21000, 22000, yang menempatkan 0, 9999 dalam file1, tetapi 12000, 19999, 21000 dalam file2 yang tampaknya aneh dengan persyaratan.
Stéphane Chazelas
1
Perhatikan bahwa ini akan mencapai batas jumlah file yang dibuka secara simultan setelah beberapa ratus file (GNU awk dapat mengatasi batasan itu, tetapi kemudian kinerjanya menurun dengan cepat).
Stéphane Chazelas
1
Ya. Saya baru saja memperhatikan masalah yang Anda sebutkan.
agathusia
4
#!/bin/bash
first=$( head -n1 file | awk -F" +" '{print $2}' )
last=$( tail -n1 file | awk -F" +" '{print $2}' )
for (( i=$first ; i<=$last ; i=i+10000 )) ; do
   awk -v start=$i -v end=$(($i+10000)) 'BEGIN { FS == " +" } { if ( $2 >= start && $2 < end ) print $0 }' file \
   >> interval_"$i"_to_"$(( $i+10000 ))"
done

Tes dengan interval yang diatur ke 100:

more inter*
::::::::::::::
interval_61336212_to_61346212
::::::::::::::
chr19   61336212        +       0       0       CG      CGT    
chr19   61336213        -       0       0       CG      CGG    
chr19   61336218        +       0       0       CG      CGG    
chr19   61336219        -       0       0       CG      CGC    
chr19   61336268        +       0       0       CG      CGG    
chr19   61336269        -       0       0       CG      CGA    
::::::::::::::
interval_61336312_to_61346312
::::::::::::::
chr19   61336402        +       0       0       CG      CGG    
chr19   61336403        -       0       0       CG      CGT  

Catatan: akan menghasilkan file kosong untuk interval kosong; untuk menghapus file kosong, tambahkan:

for file in interval* ; do
  if [ ! -s "$file" ] ; then
    rm "$file"
  fi
done

Akan melindas file untuk setiap langkah dalam forloop, sehingga bukan yang paling efisien.

Fiximan
sumber
3

Jika yang Anda maksud hanya perhitungan bukan penghitungan baris:

awk 'NR==1 || n+10000<$2{n=$2; portion++}{print > FILENAME "." portion}' file
Costas
sumber
Perhatikan bahwa jika input memiliki 0, 9999, 12000, 19999, 21000, 22000, yang menempatkan 0, 9999 dalam file1, tetapi 12000, 19999, 21000 dalam file2 yang tampaknya aneh dengan persyaratan.
Stéphane Chazelas
Perhatikan bahwa ini akan mencapai batas jumlah file yang dibuka secara simultan setelah beberapa ratus file (GNU awk dapat mengatasi batasan itu, tetapi kemudian kinerjanya menurun dengan cepat).
Stéphane Chazelas
@ StéphaneChazelas Saya tidak yakin yang jelas mengerti Anda. Jika Anda ingin 21000 dalam file ke-3 gunakan 9999 sebagai gantinya 10.000.
Costas
dari pemahaman saya tentang pertanyaan, OP ingin baris dengan 0 hingga 9999 di file pertama, 10000 hingga 19999 di file kedua.
Stéphane Chazelas