Divisão de arquivo para cada 10000 números (não linhas)

8

Eu tenho um arquivo que se parece com o seguinte:

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    

Eu quero dividir esse arquivo para cada intervalo de 10000 do 2º campo (NÃO linhas, mas intervalo numérico). Portanto, para este arquivo, gostaria de dividir da primeira linha (a linha com 61336212) para a linha que tem ou até 61346211 (61336212 + 9999), depois de 61346212 a 61356211 e assim por diante. Como você pode ver, os números no 2º campo / coluna não são 'preenchidos'.

Existe uma maneira de fazer isso?

agathusia
fonte
No seu exemplo, se o próximo número após 61346211 for 61346220, digamos, você esperaria que o segundo arquivo de saída abranja o intervalo começando em 61346212 ou 61346220?
Joe Lee-Moyet
o segundo intervalo deve abranger o número 61346212.
agathusia 17/08/2015

Respostas:

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
     }'

Iria escrever para file.0000, file.0001... (o número estar int(($2-n)/10000)onde nestá $2para a primeira linha).

Observe que fechamos os arquivos quando paramos de gravá-los, caso contrário, você atingia o limite do número de arquivos abertos simultaneamente após algumas centenas de arquivos (o GNU awkpode contornar esse limite, mas as performances se degradam rapidamente).

Estamos assumindo que esses números estão sempre subindo.

Stéphane Chazelas
fonte
3
você poderia explicar o que está acontecendo?
Fiximan
Você poderia explicar o que está acontecendo aqui? Também como o comentário abaixo, é possível que o comprimento do nome do arquivo de saída seja constante, como arquivo.0000, arquivo.0001 em vez de arquivo.1 arquivo.2 .. arquivo.100 .. arquivo..2320?
agathusia
1
@Fiximan, não acho que possa explicar muito mais sem parafrasear o código. Que parte você acha incerta?
Stéphane Chazelas 17/08/2015
Bem, eu entendo a geração do nome do arquivo file = ..., mas como a iteração funciona? Não há parte que diga n = n + 10000nem lower_boundary <= $2 < upper_boundaryparte. Em geral, o todo if (file != last_file) { close(last_file) ; last_file = file }está fora do meu
alcance.
1
@Fixman, bem, sim, isso é o que eu chamaria de parafraseando if (file != last_file): se o arquivo atual não for o mesmo que o arquivo anterior, feche o arquivo anterior (tenha apenas um arquivo aberto por vez (não precisamos mantê-los todas abertas como outras soluções))
Stéphane Chazelas
7

Corte a versão one-liner. Talvez mais adequado para o Code Golf do que este fórum. Isso gera split1, split2, split3 e assim por diante, como nomes de arquivos.

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

Para ter arquivos de saída denominados split001, split002, split003, envolve este extra sprintf:

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

Para evitar o problema de desaceleração gritante identificado por @ Stéphane Chazelas, use 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
fonte
1
Para esse método, existe uma maneira de os nomes dos arquivos serem mais consecutivos? Isso gera split1 .... split100 ... split1000, mas algo mais na linha de split00001 ... split 00100 .. split01000 ..?
agathusia
1
Claro, sprintfmágica extra agora adicionada.
21315 Steve
Observe que, se a entrada tiver 0, 9999, 12000, 19999, 21000, 22000, isso coloca 0, 9999 no arquivo1, mas 12000, 19999, 21000 no arquivo2, o que parece estranho aos requisitos.
Stéphane Chazelas
1
Observe que isso atingiria o limite do número de arquivos abertos simultaneamente após algumas centenas de arquivos (o GNU awk pode contornar esse limite, mas as performances diminuem rapidamente).
Stéphane Chazelas
1
Sim. Acabei de perceber o problema que você mencionou.
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

Teste com intervalo definido como 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  

Nota: produzirá arquivos vazios por intervalos vazios; para remover arquivos vazios, adicione:

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

Será executado sobre o arquivo para cada etapa do forloop, portanto não é o mais eficiente.

Fiximan
fonte
3

Se você quer dizer apenas cálculo, não contagem de linhas:

awk 'NR==1 || n+10000<$2{n=$2; portion++}{print > FILENAME "." portion}' file
Costas
fonte
Observe que, se a entrada tiver 0, 9999, 12000, 19999, 21000, 22000, isso coloca 0, 9999 no arquivo1, mas 12000, 19999, 21000 no arquivo2, o que parece estranho aos requisitos.
Stéphane Chazelas 17/08/2015
Observe que isso atingiria o limite do número de arquivos abertos simultaneamente após algumas centenas de arquivos (o GNU awk pode contornar esse limite, mas as performances diminuem rapidamente).
Stéphane Chazelas 17/08/2015
@ StéphaneChazelas Não tenho certeza de que você entenda claramente. Se seus desejos para 21000 no 3º uso de arquivos 9999 em vez 10000.
Costas
do meu entendimento da questão, o OP deseja linhas com 0 a 9999 no primeiro arquivo, 10000 a 19999 no segundo arquivo.
Stéphane Chazelas 17/08/2015