Divida un archivo grande en fragmentos sin dividir la entrada

Divida un archivo grande en fragmentos sin dividir la entrada

Tengo un archivo .msg bastante grande formateado en formato UIEE.

$ wc -l big_db.msg
8726593 big_db.msg

Básicamente, el archivo se compone de entradas de varias longitudes que se parecen a esta:

UR|1
AA|Condon, Richard
TI|Prizzi's Family
CN|Collectable- Good/Good
MT|FICTION
PU|G.P. Putnam & Sons
DP|1986
ED|First Printing.
BD|Hard Cover
NT|0399132104
KE|MAFIA
KE|FICTION
PR|44.9
XA|4
XB|1
XC|BO
XD|S

UR|10
AA|Gariepy, Henry
TI|Portraits of Perseverance
CN|Good/No Jacket
MT|SOLD
PU|Victor Books
DP|1989
BD|Mass Market Paperback
NT|1989 tpb g 100 meditations from the Book of Job "This book...help you
NT| persevere through the struggles of your life..."
KE|Bible
KE|religion
KE|Job
KE|meditations
PR|28.4
XA|4
XB|5
XC|BO
XD|S

Este es un ejemplo de dos entradas, separadas por una línea en blanco. Deseo dividir este archivo grande en archivos más pequeños sin dividir una entrada en dos archivos.

Cada entrada individual está separada por una nueva línea (una línea completamente en blanco) en el archivo. Deseo dividir este archivo de 8,7 millones de líneas en 15 archivos. Entiendo que splitexisten herramientas como ésta, pero no estoy muy seguro de cómo dividir el archivo, sino solo dividirlo en una nueva línea para que una sola entrada no se divida en varios archivos.

Respuesta1

Usando la sugerencia de csplit:

División basada en números de línea

$ csplit file.txt <num lines> "{repetitions}"

Ejemplo

Digamos que tengo un archivo con 1000 líneas.

$ seq 1000 > file.txt

$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405

da como resultado archivos como este:

$ wc -l xx*
  99 xx00
 100 xx01
 100 xx02
 100 xx03
 100 xx04
 100 xx05
 100 xx06
 100 xx07
 100 xx08
 101 xx09
   1 xx10
1001 total

Puede evitar la limitación estática de tener que especificar el número de repeticiones calculando previamente los números en función del número de líneas de su archivo particular con anticipación.

$ lines=100
$ echo $lines 
100

$ rep=$(( ($(wc -l file.txt | cut -d" " -f1) / $lines) -2 ))
$ echo $rep
8

$ csplit file.txt 100 "{$rep}"
288
400
400
400
400
400
400
400
400
405

División basada en líneas en blanco

Si, por otro lado, desea simplemente dividir un archivo en líneas en blanco contenidas en el archivo, puede usar esta versión de split:

$ csplit file2.txt '/^$/' "{*}"

Ejemplo

Digamos que agregué 4 líneas en blanco a lo file.txtanterior y creé el archivo file2.txt. Puedes ver que se han agregado manualmente de esta manera:

$ grep -A1 -B1 "^$" file2.txt
20

21
--
72

73
--
112

113
--
178

179

Lo anterior muestra que los agregué entre los números correspondientes dentro de mi archivo de muestra. Ahora cuando ejecuto el csplitcomando:

$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290

Puedes ver que ahora tengo 4 archivos que se han dividido según la línea en blanco:

$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179

Referencias

Respuesta2

Si no te importa el orden de los registros, puedes hacer:

gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in

De lo contrario, primero deberá obtener la cantidad de registros para saber cuántos colocar en cada archivo de salida:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in

Respuesta3

Aquí hay una solución que podría funcionar:

seq 1 $(((lines=$(wc -l </tmp/file))/16+1)) $lines |
sed 'N;s|\(.*\)\(\n\)\(.*\)|\1d;\1,\3w /tmp/uptoline\3\2\3|;P;$d;D' |
sed -ne :nl -ne '/\n$/!{N;bnl}' -nf - /tmp/file

Funciona permitiendo que el primero sedescriba el sedguión del segundo. El segundo sedprimero reúne todas las líneas de entrada hasta que encuentra una línea en blanco. Luego escribe todas las líneas de salida en un archivo. El primero sedescribe un script para el segundo indicándole dónde escribir su salida. En mi caso de prueba, ese script se veía así:

1d;1,377w /tmp/uptoline377
377d;377,753w /tmp/uptoline753
753d;753,1129w /tmp/uptoline1129
1129d;1129,1505w /tmp/uptoline1505
1505d;1505,1881w /tmp/uptoline1881
1881d;1881,2257w /tmp/uptoline2257
2257d;2257,2633w /tmp/uptoline2633
2633d;2633,3009w /tmp/uptoline3009
3009d;3009,3385w /tmp/uptoline3385
3385d;3385,3761w /tmp/uptoline3761
3761d;3761,4137w /tmp/uptoline4137
4137d;4137,4513w /tmp/uptoline4513
4513d;4513,4889w /tmp/uptoline4889
4889d;4889,5265w /tmp/uptoline5265
5265d;5265,5641w /tmp/uptoline5641

Lo probé así:

printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file

Esto me proporcionó un archivo de 6000 líneas, que se veía así:

<iteration#>
and
more
lines
here
#blank

...repetido 1000 veces.

Después de ejecutar el script anterior:

set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
    echo $splitfile
    wc -l <$splitfile
    tail -n6 $splitfile
done    

PRODUCCIÓN

15 total splitfiles
/tmp/uptoline1129
378
188
and
more
lines
here

/tmp/uptoline1505
372
250
and
more
lines
here

/tmp/uptoline1881
378
313
and
more
lines
here

/tmp/uptoline2257
378
376
and
more
lines
here

/tmp/uptoline2633
372
438
and
more
lines
here

/tmp/uptoline3009
378
501
and
more
lines
here

/tmp/uptoline3385
378
564
and
more
lines
here

/tmp/uptoline3761
372
626
and
more
lines
here

/tmp/uptoline377
372
62
and
more
lines
here

/tmp/uptoline4137
378
689
and
more
lines
here

/tmp/uptoline4513
378
752
and
more
lines
here

/tmp/uptoline4889
372
814
and
more
lines
here

/tmp/uptoline5265
378
877
and
more
lines
here

/tmp/uptoline5641
378
940
and
more
lines
here

/tmp/uptoline753
378
125
and
more
lines
here

Respuesta4

Intentarawk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg

información relacionada