División de archivos por cada 10000 números (no líneas)

8

Tengo un archivo que se parece a lo siguiente:

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    

Quiero dividir este archivo por cada intervalo de 10000 del segundo campo (NO líneas, sino intervalo de números). Entonces, para este archivo, me gustaría dividir desde la primera línea (la línea con 61336212) a la línea que tiene o hasta 61346211 (61336212 + 9999), luego de 61346212 a 61356211, y así sucesivamente. Como puede ver, los números en el segundo campo / columna no están 'llenos'.

¿Hay alguna forma de hacer esto?

agathusia
fuente
En su ejemplo, si el siguiente número después de 61346211 es 61346220, por ejemplo, ¿esperaría que el segundo archivo de salida cubra el rango que comienza en 61346212 o 61346220?
Joe Lee-Moyet
el segundo rango debe cubrir desde 61346212.
agathusia

Respuestas:

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

Escribiría a file.0000, file.0001... (el número que se está int(($2-n)/10000)donde nestá $2para la primera línea).

Tenga en cuenta que cerramos los archivos una vez que dejamos de escribirles, de lo contrario, alcanzaría el límite en el número de archivos abiertos simultáneamente después de unos cientos de archivos (GNU awkpuede evitar ese límite, pero luego el rendimiento se degrada rápidamente).

Asumimos que esos números siempre están subiendo.

Stéphane Chazelas
fuente
3
¿podrías explicar lo que está pasando?
Fiximan
¿Podría explicar qué está pasando aquí? También, como en el comentario a continuación, ¿hay distancia para que la longitud del nombre del archivo de salida sea constante, como file.0000, file.0001 en lugar de file.1 file.2 .. file.100 .. file..2320?
agathusia
1
@Fiximan, no creo que pueda explicar mucho más sin parafrasear el código. ¿Qué parte encuentras poco clara?
Stéphane Chazelas
Bueno, entiendo la generación del nombre de archivo file = ..., pero ¿cómo funciona la iteración? No hay parte que diga n = n + 10000ni una lower_boundary <= $2 < upper_boundaryparte. En general, todo if (file != last_file) { close(last_file) ; last_file = file }está fuera de mi
alcance
1
@Fixman, bueno, sí, eso es lo que llamaría parafrasear if (file != last_file): si el archivo actual no es el mismo que el archivo anterior, cierre el archivo anterior (así que tenga solo un archivo abierto a la vez (no necesitamos mantenerlos) todo abierto como lo hacen otras soluciones))
Stéphane Chazelas
7

Hackear la versión de una sola línea. Sin embargo, quizás sea más adecuado para Code Golf que este foro. Esto genera split1, split2, split3, etc., como nombres de archivo.

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

Tener archivos de salida llamados split001, split002, split003, implica este extra sprintf:

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

Para evitar el problema de desaceleración de gawk 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
fuente
1
Para este método, ¿hay alguna forma de que los nombres de los archivos sean más ... consecutivamente? Esto genera split1 .... split100 ... split1000, pero algo más en la línea de split00001 ... split 00100 .. split01000 ..?
agathusia
1
Claro, sprintfahora se agrega magia extra .
Steve
Tenga en cuenta que si la entrada tiene 0, 9999, 12000, 19999, 21000, 22000, eso pone 0, 9999 en el archivo1, pero 12000, 19999, 21000 en el archivo2, lo que parece extraño con los requisitos.
Stéphane Chazelas
1
Tenga en cuenta que esto alcanzaría el límite en el número de archivos abiertos simultáneamente después de unos cientos de archivos (GNU awk puede evitar ese límite, pero luego el rendimiento se degrada rápidamente).
Stéphane Chazelas
1
Si. Acabo de notar el problema que mencionaste.
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

Prueba con intervalo establecido en 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: producirá archivos vacíos para intervalos vacíos; para eliminar archivos vacíos, agregue:

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

Se ejecutará sobre el archivo para cada paso en el forbucle, por lo tanto, no es el más eficiente.

Fiximan
fuente
3

Si quiere decir solo cálculo, no recuento de líneas:

awk 'NR==1 || n+10000<$2{n=$2; portion++}{print > FILENAME "." portion}' file
Costas
fuente
Tenga en cuenta que si la entrada tiene 0, 9999, 12000, 19999, 21000, 22000, eso pone 0, 9999 en el archivo1, pero 12000, 19999, 21000 en el archivo2, lo que parece extraño con los requisitos.
Stéphane Chazelas
Tenga en cuenta que esto alcanzaría el límite en el número de archivos abiertos simultáneamente después de unos cientos de archivos (GNU awk puede evitar ese límite, pero luego el rendimiento se degrada rápidamente).
Stéphane Chazelas
@ StéphaneChazelas No estoy seguro de que claro te entiendo. Si quiere 21000 en el 3er archivo, use 9999 en lugar de 10000.
Costas
Según tengo entendido la pregunta, el OP quiere líneas con 0 a 9999 en el primer archivo, 10000 a 19999 en el segundo archivo.
Stéphane Chazelas