Tengo un archivo de texto grande (~ 50 Gb cuando gz'ed). El archivo contiene 4*N
líneas o N
registros; Es decir, cada registro consta de 4 líneas. Me gustaría dividir este archivo en 4 archivos más pequeños, cada uno con un tamaño aproximado del 25% del archivo de entrada. ¿Cómo puedo dividir el archivo en el límite de registro?
Un enfoque ingenuo sería zcat file | wc -l
obtener el recuento de líneas, dividir ese número entre 4 y luego usarlo split -l <number> file
. Sin embargo, esto pasa dos veces por el archivo y el conteo de línea es extremadamente lento (36 minutos). ¿Hay una mejor manera?
Esto se acerca pero no es lo que estoy buscando. La respuesta aceptada también cuenta una línea.
EDITAR:
El archivo contiene datos de secuencia en formato fastq. Dos registros se ven así (anonimizados):
@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
La primera línea de cada registro comienza con a @
.
EDIT2:
zcat file > /dev/null
toma 31 minutos
EDITAR3:
Solo comienza la primera línea @
. Ninguno de los otros lo hará nunca. Ver aquí . Los registros deben mantenerse en orden. No está bien agregar nada al archivo resultante.
zcat file > /dev/null
?@
y también que hay 4 líneas por registro. ¿Son ambos absolutos? - ¿y pueden comenzar las líneas 2,3,4@
? ¿y hay algún encabezado sin registro de líneas de pie de página en el archivo?Respuestas:
No creo que puedas hacer esto, no de manera confiable, y no de la manera que lo pides. La cuestión es que la relación de compresión del archivo probablemente no se distribuirá de manera uniforme de la cabeza a la cola: el algoritmo de compresión se aplicará mejor a algunas partes que a otras. Así es como funciona. Por lo tanto, no puede factorizar su división en el tamaño del archivo comprimido.
Además,
gzip
simplemente no admite el almacenamiento del tamaño original de archivos comprimidos de más de 4 gbs de tamaño, no puede manejarlo. Por lo tanto, no puede consultar el archivo para obtener un tamaño confiable, porque lo engañará.Lo de 4 líneas: eso es bastante fácil, de verdad. Lo de los 4 archivos: simplemente no sé cómo podría hacerlo de manera confiable y con una distribución uniforme sin extraer primero el archivo para obtener su tamaño sin comprimir. No creo que puedas porque lo intenté.
Sin embargo, lo que puede hacer es establecer un tamaño máximo para los archivos de salida divididos y asegurarse de que siempre se rompan en las barreras de registro. Eso puedes hacer fácilmente. Aquí hay una pequeña secuencia de comandos que lo hará extrayendo el
gzip
archivo y canalizando el contenido a través de algunosdd
buffers de tubería explícitos concount=$rpt
argumentos específicos , antes de pasarlolz4
para descomprimir / recomprimir cada archivo sobre la marcha. También agregué algunos pequeñostee
trucos de tubería para imprimir las últimas cuatro líneas para cada segmento para stderr también.Eso continuará hasta que haya manejado todas las entradas. No intenta dividirlo en algún porcentaje, que no puede obtener, sino que lo divide por un recuento máximo de bytes sin procesar por división. Y de todos modos, una gran parte de su problema es que no puede obtener un tamaño confiable en su archivo porque es demasiado grande, haga lo que haga, no vuelva a hacer eso, haga que las divisiones de menos de 4 gbs por pieza sean redondas. , tal vez. Este pequeño script, al menos, le permite hacer esto sin tener que escribir un byte sin comprimir en el disco.
Aquí hay una versión más corta despojada de lo esencial: no agrega todas las cosas del informe:
Hace todo lo mismo que el primero, principalmente, simplemente no tiene mucho que decir al respecto. Además, hay menos desorden, por lo que es más fácil ver lo que está sucediendo, tal vez.
La
IFS=
cuestión es solo manejar unaread
línea por iteración. Somosread
uno porque necesitamos que nuestro ciclo finalice cuando finaliza la entrada. Esto depende del tamaño de su registro , que, según su ejemplo, es de 354 bytes por. Creé ungzip
archivo de 4 + gb con algunos datos aleatorios para probarlo.Los datos aleatorios se obtuvieron de esta manera:
... pero tal vez no tenga que preocuparse tanto por eso, ya que ya tiene los datos y todo. De vuelta a la solución ...
Básicamente
pigz
, que parece descomprimirse un poco más rápido que lo hacezcat
, canaliza el flujo sin comprimir ydd
almacena en búfer la salida en bloques de escritura de un tamaño específico en un múltiplo de 354 bytes. El bucleread
una$line
vez cada iteración a la prueba de que la entrada todavía está llegando, que seráprintf
despuésprintf
enlz4
antes de que otrodd
se llama para leer bloques de tamaño específicamente a un múltiplo de 354 bytes - para sincronizar con el almacenamiento en búferdd
proceso - para la duración. Habrá una breve lectura por iteración debido a la inicialread $line
, pero eso no importa, porque delz4
todos modos estamos imprimiendo eso en nuestro proceso de recopilación.Lo configuré para que cada iteración lea aproximadamente 1 gb de datos sin comprimir y comprima ese in-stream a alrededor de 650Mb más o menos.
lz4
es mucho más rápido que casi cualquier otro método de compresión útil, razón por la cual lo elegí aquí porque no me gusta esperar.xz
Sin embargo, probablemente haría un trabajo mucho mejor en la compresión real. Sinlz4
embargo, una cosa es que a menudo puede descomprimirse a velocidades cercanas a la RAM, lo que significa que muchas veces puede descomprimir unlz4
archivo tan rápido como podría escribirlo en la memoria de todos modos.El grande hace algunos informes por iteración. Ambos bucles imprimirán
dd
el informe sobre la cantidad de bytes sin procesar transferidos y la velocidad, etc. El bucle grande también imprimirá las últimas 4 líneas de entrada por ciclo, y un recuento de bytes para el mismo, seguido de unols
del directorio en el que escribo loslz4
archivos. Aquí hay un par de rondas de salida:fuente
gzip -l
solo funciona para <2GiB archivos sin comprimir IIRC (de todos modos, algo más pequeño que el archivo OP).Dividir archivos en los límites de registro es realmente muy fácil, sin ningún código:
Esto creará archivos de salida de 10000 líneas cada uno, con nombres output_name_aa, output_name_ab, output_name_ac, ... Con una entrada tan grande como la suya, esto le dará muchos archivos de salida. Reemplace
10000
con cualquier múltiplo de cuatro, y puede hacer que los archivos de salida sean tan grandes o pequeños como desee. Desafortunadamente, como con las otras respuestas, no hay una buena manera de garantizar que obtendrá el número deseado de archivos de salida (aproximadamente) del mismo tamaño sin hacer algunas suposiciones sobre la entrada. (O, de hecho, pasar todo el procesowc
). Si sus registros tienen aproximadamente el mismo tamaño (o al menos, se distribuyen de manera más o menos uniforme), puede intentar obtener una estimación como esta:Eso le dirá el tamaño comprimido de los primeros 1000 registros de su archivo. Basado en eso, probablemente pueda llegar a una estimación de cuántas filas desea en cada archivo para terminar con cuatro archivos. (Si no desea que quede un quinto archivo degenerado, asegúrese de aumentar un poco su estimación, o esté preparado para pegar el quinto archivo en la cola del cuarto).
Editar: Aquí hay un truco más, suponiendo que desea archivos de salida comprimidos:
Esto creará muchos archivos más pequeños y luego los juntará rápidamente. (Es posible que tenga que ajustar el parámetro -l dependiendo de la longitud de las líneas en sus archivos). Se supone que tiene una versión relativamente reciente de los coreutils de GNU (para split --filter) y aproximadamente el 130% del tamaño de su archivo de entrada en espacio libre en disco. Sustituya pigz / unpigz por gzip / zcat si no los tiene. He oído que algunas bibliotecas de software (¿Java?) No pueden manejar archivos gzip concatenados de esta manera, pero hasta ahora no he tenido ningún problema. (Pigz usa el mismo truco para paralelizar la compresión).
fuente
Por lo que deduzco después de verificar la esfera de google, y luego de probar un
.gz
archivo de 7.8 GiB , parece que los metadatos del tamaño original del archivo sin comprimir no son precisos (es decir, incorrectos ) para.gz
archivos grandes (mayores de 4GiB (quizás 2GiB para algunos versiones degzip
).Re. mi prueba de metadatos de gzip:
Por lo tanto, parece que no es posible determinar el tamaño sin comprimir sin descomprimirlo (¡lo cual es un poco tosco, por decir lo menos!)
De todos modos, aquí hay una manera de dividir un archivo sin comprimir en los límites del registro, donde cada registro contiene 4 líneas .
Utiliza el tamaño del archivo en bytes (vía
stat
) yawk
contando bytes (no caracteres). Si los finales de línea son o noLF
|CR
El |CRLF
, este script maneja la longitud final de la línea a través de la variable incorporadaRT
).A continuación se muestra la prueba que utilicé para verificar que el recuento de líneas de cada archivo sea
mod 4 == 0
Prueba de salida:
myfile
fue generado por:fuente
¡Esto no pretende ser una respuesta seria! Solo he estado jugandoflex
y esto probablemente no funcionará en un archivo de entrada con ~ 50 Gb (si es que lo hace, en datos de entrada más grandes que mi archivo de prueba):Esto funciona para mí en un archivo ~ 1Gb input.txt :
Dado el
flex
archivo de entrada splitter.l :generando lex.yy.c y compilándolo al
splitter
binario con:Uso:
Tiempo de ejecución para 1Gb input.txt :
fuente
getc(stream)
y aplique una lógica simple. Además, ¿sabes que el. El carácter (punto) regex en (f) lex coincide con cualquier carácter excepto la nueva línea , ¿verdad? Mientras que estos registros son de varias líneas.@
carácter y luego dejar que la regla predeterminada copie los datos. Ahora tiene su regla copiando parte de los datos como un token grande, y luego la regla predeterminada obtiene la segunda línea de un carácter a la vez.txr
.Aquí hay una solución en Python que hace que una pasada sobre el archivo de entrada escriba los archivos de salida a medida que avanza.
Una característica sobre el uso
wc -l
es que está asumiendo que cada uno de los registros aquí tiene el mismo tamaño. Eso puede ser cierto aquí, pero la solución a continuación funciona incluso cuando ese no es el caso. Básicamente está utilizandowc -c
o el número de bytes en el archivo. En Python, esto se hace a través de os.stat ()Así es como funciona el programa. Primero calculamos los puntos de división ideales como desplazamientos de bytes. Luego, lee las líneas del archivo de entrada que se escriben en el archivo de salida apropiado. Cuando vea que ha excedido el siguiente punto de división óptimo y se encuentra en un límite de registro, cierre el último archivo de salida y abra el siguiente.
El programa es óptimo en este sentido, lee los bytes del archivo de entrada una vez; Obtener el tamaño del archivo no requiere leer los datos del archivo. El almacenamiento necesario es proporcional al tamaño de una línea. Pero Python o el sistema presumiblemente tienen buffers de archivos razonables para acelerar la E / S.
He agregado parámetros para cuántos archivos dividir y cuál es el tamaño del registro en caso de que desee ajustar esto en el futuro.
Y claramente esto también podría traducirse a otros lenguajes de programación.
Otra cosa, no estoy seguro de si Windows con su crlf maneja la longitud de la línea correctamente como lo hace en los sistemas Unix-y. Si len () está desactivado por uno aquí, espero que sea obvio cómo ajustar el programa.fuente
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
El usuario FloHelf mismo parecía curioso acerca de una solución TXR . Aquí hay uno que usa el TXR Lisp incorporado :
Notas:
Por la misma razón,
pop
es importante agregar cada tupla de la lista perezosa de tuplas, de modo que se consuma la lista perezosa. No debemos retener una referencia al comienzo de esa lista porque la memoria crecerá a medida que avancemos por el archivo.(seek-stream fo 0 :from-current)
es un caso no operativoseek-stream
, que se vuelve útil al devolver la posición actual.Rendimiento: no lo menciones. Utilizable, pero no traerá trofeos a casa.
Como solo hacemos la verificación del tamaño cada 1000 tuplas, podríamos hacer que el tamaño de la tupla sea de 4000 líneas.
fuente
Si no necesita que los nuevos archivos sean fragmentos contiguos del archivo original, puede hacerlo completamente
sed
de la siguiente manera:Esto
-n
evita que imprima cada línea, y cada uno de los-e
scripts está esencialmente haciendo lo mismo.1~16
coincide con la primera línea y cada 16 líneas después.,+3
significa unir las siguientes tres líneas después de cada una de ellas.w1.txt
dice escribir todas esas líneas en el archivo1.txt
. Esto toma cada 4to grupo de 4 líneas y lo escribe en un archivo, comenzando con el primer grupo de 4 líneas. Los otros tres comandos hacen lo mismo, pero cada uno se desplaza hacia adelante por 4 líneas y escribe en un archivo diferente.Esto se romperá horriblemente si el archivo no coincide exactamente con la especificación que estableció, pero de lo contrario debería funcionar como usted lo pretendía. No lo he perfilado, así que no sé qué tan eficiente será, pero
sed
es razonablemente eficiente en la edición de secuencias.fuente